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

[2] 浅谈 kubernetes service 那些事(上篇)

[3] k8s service 概念和原理