容器-10-Kubernetes实战-Service

Deployment只是保证了支撑服务的Pod的数量,但是没有解决如何访问这些服务的问题。一个Pod只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的IP启动一个新的Pod。因此不能以确定的IP和端口号提供服务。
要稳定地提供服务,需要服务发现和负载均衡能力。服务发现是一个微服务中很基础的概念,即当服务提供者网络发生变化时,服务消费者能及时获得最新的位置信息。对于k8s来说,服务提供者就是Pod,提供服务发现能力的是Service。Deployment和Service分别负责Pod的部署和访问策略,互不相关。
所以从下面这张图也可以看出来,Service和Deployment并不是上下层级的关系。

Kubernetes Service与Deployment

1. Service YAML

一个简单的Service YAML模板如下:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: <endpoint-name>
spec:
ports:
- port: <port>
name: <port-name>
targetPort: <target-port-number>
selector:
app: <app-name>

可以看到它是由selector.app来选择需要暴露的Pod。spec.ports.port是service对外暴露的端口,而targetPort是Pod的端口。默认使用TCP协议。
对于我们的POC的网站,service YAML定义如下:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: poc-web-service
spec:
ports:
- port: 80
name: web
targetPort: 80
selector:
app: poc-web

当我们查看已部署的service的时候,可以看到该service对应的ip:

1
2
3
4
5
[root@docker-4 poc]# kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
hostnames ClusterIP 10.99.149.202 <none> 80/TCP 14d app=hostnames
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28d <none>
poc-web-service ClusterIP 10.110.52.238 <none> 80/TCP 4m43s app=poc-web

甚至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。
以下这张就是的示意图:
kube-proxy

3. Service对外发布服务的方式

Service可以通过type属性,以不同的方式对外发布服务,包括:

  • ClusterIP
  • NodePort
  • LoadBalancer
  • ExternalName

3.1 ClusterIP

这是不指定type时的默认方式。暴露为一个集群内部的ip,只能在集群内部访问。这个也是我们POC采用的方式。
Kubernetes Service ClusterIP

3.2 NodePort

通过NAT的方式,在选定的数个节点上以IP形式暴露。可以通过那几个宿主机节点中的任意一个的ip+端口访问。该模式经常用于外部还有一个独立的负载均衡服务的时候使用。
Kubernetes Service NodePort

如果我们现在就急不可待想从集群外访问看一下,就可以稍微改一下配置,在ports的同一级加一个type: NodePort

1
2
3
4
[root@docker-4 poc]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28d
poc-web-service NodePort 10.105.188.12 <none> 80:30611/TCP 24s

如果没有指定端口的话,会随机分配一个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
2
3
4
5
6
7
8
9
          client
\ ^
\ \
v \
node 1 <--- node 2
| ^ SNAT
| | --->
v |
endpoint

3.3 LoadBalancer

借用外部云服务的负载均衡能力,暴露一个固定的ip。使用公有云服务基本使用该方式。
Kubernetes Service LoadBalancer

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

本文永久链接 [ https://galaxyyao.github.io/2019/06/26/容器-10-Kubernetes实战-Service/ ]