k8s 入门 2:Service
Service 意为服务,但 K8S 中真正运行服务的是 Pod。但 Pod 数可以增大和缩减,Pod 可能意外退出然后重建且重建后 IP 地址是会变化的。如何将同一组 Pod 作为一个整体对外服务呢?K8S 的解决方案是 Service 抽象。
Service 它提供了一种无状态的、稳定的、可靠的、持续的网络连接。这样,即使Pod的数量、位置发生变化,Service 也可以保持网络连接的稳定。
Endpoints
Service 与 Pods 的关联是通过 Endpoint 建立的。在创建 Service 时,会设置 selector
标签关联相关的 Pod,Endpoint Controller 会自动创建一个与 Service 同名的 Endpoints,用来记录 Service 对应的所有 Pods 的地址,数据存储在 etcd 中。
Service -> Endpoints -> Pod
Endpoint Controller 功能如下:
- 负责生成和维护所有 Endpoint 对象的控制器
- 负责监听 service 和对应 Pod 的变化:
- 监听到 service 被删除,则删除和该 service 同名的 endpoint 对象
- 监听到新的 service 被创建,则根据新建 service 信息获取相关 pod 列表,然后创建对应 endpoint 对象
- 监听到 service 被更新,则根据更新后的service信息获取相关 pod 列表,然后更新对应 endpoint 对象
- 监听到 pod 事件,则更新对应的 service 的 endpoint 对象,将 podIp 记录到 endpoint 中
类型
ClusterIP
没有指定 type 时默认是此类型。Service 会被分配一个集群内部的 IP,这个 IP 是一个虚拟的 IP,并不会经过网络接口,所以只能在集群内部访问。
NodePort
在 ClusterIP 的基础上,会在节点上映射一个端口号,可以使用节点IP+端口号访问 Service。 集群中的每个节点都将自己配置为监听所分配的端口,并将流量转发到与该 Service 关联的某个就绪端点。
LoadBalance
使用云平台的负载均衡器向外部公开 Service。
ExternelName
将服务映射到 externalName
字段的内容(例如,映射到主机名 api.foo.bar.example
)。 该映射将集群的 DNS 服务器配置为返回具有该外部主机名值的 CNAME
记录。 集群不会为之创建任何类型代理。
以上 type 类型是层层递进的关系,每一个层是在上一层的基础上。
流量的流转过程(iptables)
首先进入 PREROUTING 链,所有流量依次进入下面三个子链:
Chain PREROUTING (policy ACCEPT 936 packets, 160K bytes)
pkts bytes target prot opt in out source destination
3107K 548M cali-PREROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* cali:6gwbT8clXdHdC1b1 */
3107K 548M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
16 1148 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Nginx service 地址是 10.106.172.15,所以访问 Nginx 服务的会匹配到下面的规则
Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SVC-2CMXP7HKUVJN7L6M tcp -- * * 0.0.0.0/0 10.106.172.15 /* default/nginx cluster IP */ tcp dpt:80
...
进入 KUBE-SVC-2CMXP7HKUVJN7L6M 子链:
Chain KUBE-SVC-2CMXP7HKUVJN7L6M (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ tcp -- * * !172.16.0.0/16 10.106.172.15 /* default/nginx cluster IP */ tcp dpt:80
0 0 KUBE-SEP-DKSXNBNHBQPR4E25 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx -> 172.16.169.148:80 */ statistic mode random probability 0.33333333349
0 0 KUBE-SEP-W2PCRIIB3CH56EB2 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx -> 172.16.36.77:80 */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-D53NQAPERPOTILQO all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx -> 172.16.36.78:80 */
目的地址不是 Pod 的,会做标记,后续做 SNAT。
对面三条规则,分别对应 Nginx service 的 3 个 Pod。这里使用了 iptables 的 statistics 模块的 random 模式,自上而下,1/3 的请求走匹配的第一条,1/2 的请求的匹配 第二条,剩下的匹配第三条。说明改模式下,流量并不是绝对平均的。
特殊用法
会话保持
Service支持通过设置sessionAffinity实现基于客户端IP的会话保持机制,即:首次将某个客户端来源IP发起的请求转发到后端的某个Pod上,之后从相同的客户端 IP发起的请求都将被转发到相同的后端Pod上。
配置参数为 service.spec.sessionAffinity
,也可以设置会话保持的最长时间(service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
),例如下面的服务将会话保持时间设置为10800s(3h):
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSecondes: 10080
ports:
- protocol: TCP
port: 8080
targetPort: 8080
selector:
app: webapp
将外部服务映射为 Service
普通的Service通过Label Selector对后端Endpoint列表进行了一次抽象,如果后端的Endpoint不是由Pod副本集提供的,则Service还可以抽象定义任意其他服务,将一个Kubernetes集群外部的已知服务定义为Kubernetes内的一个Service, 供集群内的其他应用访问。
常见的应用场景包括:
- 已部署的一个集群外服务:例如数据库服务、缓存服务等;
- 其他Kubernetes集群的某个服务;
- 迁移过程中对某个服务进行Kubernetes内的服务名访问机制的验证。
对于这种应用场景,用户在创建Service资源对象时不设置Label Selector(后端Pod也不存在),同时再定义一个与Service关联的Endpoint资源对象,在Endpoint中设置外部服务的IP地址和端口号,例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80
-----------
apiversion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- IP: 1.2.3.4
ports:
- port: 80
参考
[1] Service