Deployment只是保证了支撑服务的Pod的数量,但是没有解决如何访问这些服务的问题。一个Pod只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的IP启动一个新的Pod。因此不能以确定的IP和端口号提供服务。
要稳定地提供服务,需要服务发现和负载均衡能力。服务发现是一个微服务中很基础的概念,即当服务提供者网络发生变化时,服务消费者能及时获得最新的位置信息。对于k8s来说,服务提供者就是Pod,提供服务发现能力的是Service。Deployment和Service分别负责Pod的部署和访问策略,互不相关。
所以从下面这张图也可以看出来,Service和Deployment并不是上下层级的关系。
1. Service YAML
一个简单的Service YAML模板如下:
1 | apiVersion: v1 |
可以看到它是由selector.app来选择需要暴露的Pod。spec.ports.port是service对外暴露的端口,而targetPort是Pod的端口。默认使用TCP协议。
对于我们的POC的网站,service YAML定义如下:
1 | apiVersion: v1 |
当我们查看已部署的service的时候,可以看到该service对应的ip:
1 | [root@docker-4 poc]# kubectl get service -o wide |
甚至YAML中selector也不是必须的。可以额外定义一个映射到外部ip/域名+端口的EndPoint,然后将Service指向这个EndPoint。这个特性我理解是类似于对集群内的反向代理。如果有一个服务在测试环境调用集群内服务,生产环境调用集群外服务,只需要在service里定义两套namespace或env的label就可以解决了。
2. kube-proxy
每个Pod有自己独一无二的ip。但当一组Pod组成了一个Service对外提供服务时,只能保持一个ip对外。当我们请求这个ip时,Kubernetes需要将我们的请求相对平均地分发给每个Pod。
这个听上去像什么?没错,就是虚ip(virtual ip) + 反向代理(reverse proxy)。Kubernetes中为service提供虚ip + 反向代理的就是kube-proxy。
kube-proxy有三种实现方式:
- userspace
- iptables
- ipvs
iptables是当前版本的默认。从名称上就可以猜到是通过在宿主机上设置iptables规则来实现的。由于是内核态,所以性能比用户态的userspace方式高。但节点和Pod多了之后刷新iptables规则就变成了瓶颈。所以待ipvs成熟后应该会改为ipvs。
以下这张就是的示意图:
3. Service对外发布服务的方式
Service可以通过type属性,以不同的方式对外发布服务,包括:
- ClusterIP
- NodePort
- LoadBalancer
- ExternalName
3.1 ClusterIP
这是不指定type时的默认方式。暴露为一个集群内部的ip,只能在集群内部访问。这个也是我们POC采用的方式。
3.2 NodePort
通过NAT的方式,在选定的数个节点上以IP形式暴露。可以通过那几个宿主机节点中的任意一个的ip+端口访问。该模式经常用于外部还有一个独立的负载均衡服务的时候使用。
如果我们现在就急不可待想从集群外访问看一下,就可以稍微改一下配置,在ports
的同一级加一个type: NodePort
。
1 | [root@docker-4 poc]# kubectl get service |
如果没有指定端口的话,会随机分配一个30000-32767之间的端口。查好被分配的端口,然后就可以通过http://任意一个Worker节点的ip:30611/ 访问我们的网站了。
可以自己通过nodePort参数指定固定端口。但如果不在30000-32767的范围,就会报错:
1 | The Service "poc-web-service" is invalid: spec.ports[0].nodePort: Invalid value: 80: provided port is not in the valid range. The range of valid ports is 30000-32767 |
为什么是任何一个Worker节点的ip都可以有效,是因为分为两种情况:
- Pod在该宿主机节点上
- Pod不在该宿主机节点上
如果Pod在该节点上,那么没问题IP包直接给Pod。如果不再该节点上,节点之间会做SNAT,即原地址转换。IP包会由接收到的节点转给带有Pod的宿主机节点。
1 | client |
3.3 LoadBalancer
借用外部云服务的负载均衡能力,暴露一个固定的ip。使用公有云服务基本使用该方式。
3.4 ExternalName
通过一个固定的CNAME记录暴露,kube-dns 1.7版本之后的特性,方便实现上文提到的无selector Service。
4. 参考资料
Kubernetes Service的官方文档
Service - Kubernetes
Using a Service to Expose Your App - Kubernetes
ExternalName的一些范例
Kubernetes Tips - Part 1
各种service type的更详细介绍
Kubernetes service types