23.Service

关于Service需要了解的知识:

  1. Kubernetes将为Service分配一个IP地址,供Service Proxy使用(这个我不了解)

  2. Kubernetes将不断扫描符合该Selector的Pod,并将最新的结果更新到与Service同名my-servie的Endpoint对象中(对Endpoint还不是太熟悉)

  3. Pod的另一种,Port可能被赋予了一个名字,您可以在Service的targetPort 字段引用这些名字,而不是直接写端口号。这种做法可以使得您在将来修改后端程序监听的端口号,而无需影响到前端程序(因为不需要修改Service,而拥有不同name的Pod应该都可以正确接受转发的流量)。

  4. Service的默认传输协议是TCP,您也可以使用其他支持的传输协议。

  5. Kubernetes Service中,可以定义多个端口,不同的端口可以使用相同或不同的传输协议。

其他知识:

Service从自己的IP地址和port端口接受请求,并将请求映射到符合条件的Pod的targetPort。为了方便,默认targetPort的取值与port字段相同。(实际上就是一条lvs转发记录)

高级的应用

Service通常用于提供对Kubernetes Pod的访问,但是您也可以将其用于任何其他形式的后端。例如:

  • 想要在生产环境中使用一个Kubernetes外部的数据库集群,在测试环境中使用Kubernetes内部的数据库
  • 想要将Service指向另一个名称空间中的Service,或者另一个Kubernetes集群中的Service
  • 正在将程序迁移到Kubernetes,但是根据迁移路径,只将一部分后端程序运行在Kubernetes中

在上述这些情况下,可以定义一个没有Pod Selector的Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

apiVersion: v1
kind: Service
metadata:
    name: my-service
spec:
    ports:
        - protocol: TCP
          port: 80
          targetPort: 9376

因为该Service没有selector,相应的Endpoint对象就无法自动创建。可以手动创建一个Endpoint,以便该Service映射到后端服务真实的IP地址和端口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

apiVersion: v1
kind: Endpoints
metadata:
    name: my-service
subsets:
    - addresses:
      - ip: 192.0.2.42
      ports:
      - port: 9376 

对于Service的访问者来说,Service是否有label selector都是一样的。

注意事项:

  1. Endpoint中的IP地址不可以是loopback(127.0.0.0/8 IPv4 或 ::1/128 IPv6),或link-local(169.254.0.0/16 IPv4、224.0.0.0/24 IPv4 或 fe80::/64 IPv6)

  2. Endpoint中的IP地址不可以是集群中其他Service的ClusterIP

Service原理

Kubernetes集群中每个节点都运行了一个kube-proxy,负责为Service提供虚拟IP访问。

Iptables代理模式:

  1. kube-proxy监听kubernetes master以获得添加和移除Service/Endpoint的事件

  2. kube-proxy在其所在的节点上为每一个Service安装iptable规则

  3. iptables将发送到Service的ClusterIP/Port的请求重定向到Service的后端Pod上

    • 对于Service中的每一个Endpoint,kube-proxy安装一个iptable规则
    • 默认情况下,kube-proxy随机选择一个Service的后端Pod

IPVS代码模式:

  1. kube-proxy监听kubernetes master以获得添加和移除Service/Endpoint的事件

  2. kube-proxy根据监听到的事件,调用netlink接口,创建LPVS规则,并且将Service/Endpoint的变化同步到IPVS规则中

  3. 当访问一个Service时,IPVS将请求重定向到后端POD

多端口的Service

Service对象中定义多个端口时,必须为每个端口定义一个名字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

apiVersion: v1
kind: Service
metadata:
    name: my-service
spec:
    selector:
        app: MyApp
    ports:
        - name: http
          protocol: TCP
          port: 80
          targetPort: 9376
        - name: https
          protocol: TCP
          port: 443
          targetPort: 9377

使用自定义的IP地址

创建Service时,如果指定spec.cluserIp字段,可以使用自定义的ClusterIP地址。使用到自定义IP地址的场景:

  • 想要重用某个已经存在的DNS条目(不理解)
  • 遗留系统是通过IP地址寻址,且很难改造(不存在说K8S中的项目还有写死IP地址的情况吧)

服务发现

K8S支持两种主要的服务发现模式:

  • 环境变量
  • DNS

环境变量

kubelet查找有效的Service,并针对每一个Service,向其所在节点的Pod注入一组环境变量。支持的环境变量有:

  • Docker links兼容的环境变量(不理解这个)
  • {SERVICE}_SERVICE_HOST和{SVCNAME}_SERVICE_PROT
    • ServiceName被转换成大写
    • 小数点被转换成下滑线

案例:


REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

我无法理解这种基于环境变量的服务发现方式,这样不会导致后加入的服务不会被发现么?

DNS

CoreDNS监听Kubernetes API上创建和删除Service的事件,并为每一个Service创建一条DNS记录。

需要结合CoreDNS、LVPS理解一波了,不然容易混乱:CoreDNS完成的是ServiceName到ServiceIp的转换,待完成了这个转换过程后,Pod就知道往哪个IP发送消息了。LVPS完成的是ServiceIp到PodId的转换,本质上,这个请求从宿主机发出时,就被路由到了目标机器上。

Kubernetes同样支持DNS SRV记录,用于查找一个命名的端口。假如my-service.my-ns有一个TCP名为http的端口,则可以使用nslookup _http._tcp.my-service.my-ns发现该Service的IP地址及端口。(有点意思,第一次听说,哈哈。)

对于ExternalName类型的Service,只能通过DNS的方式进行服务发现(我对ExternalName类型的Service的理解为:就是手动指定Endpoint的Service)。

Headless Services

HeadlessService不提供负载均衡的特性,也没有自己的IP地址,创建HeadlessService时,只需要指定spec.clusterIP为None。

HeadlessService可以用于对接其他形式的服务发现机制,无需与Kubernetes的实现绑定。对于HeadlessService而言:

  1. 没有ClusterIP
  2. kube-proxy不处理这类Service
  3. kubernetes不提供负载均衡或代理支持

DNS的配置方式取决于该Service是否配置了Selector:

  1. 配置了Selector

    EndpointController创建Endpoints记录,并修改DNS配置,使其直接返回指定Selector选取的Pod的IP地址(为什么要这样设计了,有什么目的么)。

  2. 没有配置Selector

    Endpoints Controller不创建Endpoints记录。DNS服务器返回如下结果中得一种:

    • 对ExternalName类型的Service,返回CNAMNE记录
    • 对于其他类型的Service,返回与Service同名的EndPoints的A记录

我好想理解错ExternalName类型,我以为不设置Selector,然后手动创建一个Endpoints就是ExternalName,看样子不是这样的。

虚拟IP的实现

Iptables方案

假设Service的ClusterIp为10.0.0.1,port为1234,targetPort为4567

Service创建后,kube-proxy设定了一系列的iptables规则,这些规则是可以将虚拟IP映射到per-Service的规则。per-Service规则进一步链接到per-Endpoint规则,并最终将网络请求重定向到后端Pod(使用destination-NAT)。

当一个客户端连接到该Service的虚拟Ip地址时,iptables的规则被触发。一个后端Pod将被选中(基于session affinity或者随机选择),且网络报文被重定向到该后端Pod。

使用node-port或load-balancer类型的Service时,以上的代理处理过程是相同的。

IPVS

IPVS的设计是基于in-kernel hash table执行负载均衡,因此,使用LPVS的kube-proxy在Service数量较多的情况仍然能保持好的性能。同时,基于LPVS的kube-proxy可以使用更复杂的负载均衡算法(最少连接数、基于地址的,基于权重的等)