Kubernetes Service的headless类型

headless 类型

在学习StatefulSet的时候发现他有个好伙伴 headless.

在创建headless类型的Service并进行测试后发现一个问题: headless和普通的Service(后面统称为 clusterIP类型)的差距似乎只是没有一个自己的IP.

稳定的网络标识,也就是通过集群DNS来访问指定的pod是StatefulSet提供的功能,并不是headless.ClusterIP绑定StatefulSet同样具有稳定的网络标识,不知道为什么很多博客都说这是headless提供的功能.

接下来就是漫长的百度了,基本都是千篇一律说ClusterIP是负载,headless没有负载,然后更加具体的基本就不见了,然后测试了下直接访问Service不管是ClusterIP还是headless每次处理的pod都是随机的,不过得到部分有用信息,就是DNS解析headless类型的Service名称,得到的是pod的IP,而ClusterIP类型的返回的是Service自己的IP(还是没感觉有啥具体作用).

测试了一下确实是:

root@network-tools-66b6674fd9-vpjc4:/# nslookup nginx.default.svc.cluster.local
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	nginx.default.svc.cluster.local
Address: 10.244.235.129
Name:	nginx.default.svc.cluster.local
Address: 10.244.189.88

root@network-tools-66b6674fd9-vpjc4:/# nslookup nginx-clusterip.default.svc.cluster.local
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	nginxi-clusterip.default.svc.cluster.local
Address: 10.98.85.244

查看Service

[root@master ~]# kubectl get svc 
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes         ClusterIP   10.96.0.1      <none>        443/TCP   16d
nginx              ClusterIP   None           <none>        80/TCP    37m
nginx-clusterip    ClusterIP   10.98.85.244   <none>        80/TCP    8s
[root@master ~]# kubectl get pod -o wide 
NAME                             READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
network-tools-66b6674fd9-vpjc4   1/1     Running   0          22m   10.244.235.138   worker1   <none>           <none>
web-0                            1/1     Running   0          24m   10.244.235.129   worker1   <none>           <none>
web-1                            1/1     Running   0          24m   10.244.189.88    worker2   <none>           <none>

下面用自己的理解描述下headless.

与其说ClusterIP是"负载",更不如说是"转发".为什么这么说呢,首先Service自己拥有一个IP地址的,当对Service发起请求的时候,数据包的三层源IP自然就是自己,目标IP是Service的IP.

假设在上面network-tools容器中(IP地址: 10.244.235.138)访问Servicenginxi-clusterip.

Kubernetes Service的headless类型_第1张图片

数据包三层目标IP应该是Service的IP,假设Service也是一个pod,按照正常的网络接收发送数据包.

Service可以正常收到这个包,因为目标IP是他自己,但是他不具备处理里面数据的能力,所以这个包最终是要交给pod去处理的.

但是这个包还不能直接发出去.因为目标IP是自己,数据包发出去即便能到对应的pod,那么pod也会将它丢弃.因为目标IP不是自己.

所以Service在此处要对数据包进行处理.而处理过程很简单 ,使用**DNAT**.

DNAT很简单,就是修改三层数据包中的目标IP,然后就变成了下面的样子:

Kubernetes Service的headless类型_第2张图片

这么一来,pod接收到数据包后发现目标IP是自己,就会继续拆包获取里面的数据.同时他还知道这个数据包的源IP,如果需要相应的话目标IP直接写数据包的源IP10.244.235.138即可.

Service基于iptables(或IPVS)实现,所以DNAT这个步骤是由iptables来完成的.

headless

访问ClusterIP类型的Service名称解析到Service的IP,然后对这个IP发起请求.而headless是没有这个IP的.他解析Service名字拿到的直接就是一组POD的IP.

Kubernetes Service的headless类型_第3张图片

直接少了一层转发,pod可以直接对pod发起请求.

headless特点2 无端口限制

在上面ClusterIP类型的Service请求中,可以看到一个targetPort字段和port字段.

targetPort : 监听从此端口来的流量

Port: 转发到pod的那个端口

在ClusterIP类型中,你请求Service时只能请求被targetPort定义的端口.这个很简单就能理解,同样把Service想象成一个主机,这个主机监听了8080端口,然后把这个端口接收到的请求转发到另一个主机的80端口.如果你请求一个Service没有定义的端口那么则和主机一样返回Connection refused(是Service返回的).

Kubernetes Service的headless类型_第4张图片

如果这个Service是headless类型的,那么则没有了这个端口的限制.headless类型的Service的yaml文件中的port字段实际也没什么意义.因为你解析这个Service的名字得到的是一组pod的IP,发起请求时数据包会全部被StatefulSet的 pod所接收到.只需要pod监听了对应的端口那么就能互相通信.

Kubernetes Service的headless类型_第5张图片

总结

直接解析Service的名字,headless返回的是所有pod的IP地址.ClusterIP返回的是自己的IP地址.

ClusterIP类型会DNAT转发数据包(改变目标IP)

在某些特殊的情景中,如服务端(StatefulSet)是一个集群,客户端想要获取所有的节点列表然后从中选取某个或者某些建立连接,那么headless明显更加合适.因为你直接请求他的Service便能得到所有健康的pod的IP地址.

再或者想要直接和pod建立连接,互相访问端口,headless的无端口转发限制的优点则可以更好地发挥.

而Service的IP地址比起pod的IP地址(headless DNS直接指向pod IP)来说更加稳定(排除重启,Service只有在被删除重建后IP地址才会被更改),而DNS实现已久,他不遵守TTL(生存时间值),有些应用程序甚至只执行一次DNS查找然后永久缓存.即便应用进行了适当的时间间隔更新DNS,也有可能给DNS服务带来高负载.

其他注意点

创建的Service是否能提供稳定的网络取决于StatefulSet的spec.serviceName字段.同时要求Service比StatefulSet更先创建出.如果Service的名字和StatefulSet的spec.serviceName字段不同,那么没办法使用"稳定的网络"这一特性.

测试过程步骤

首先创建一个headless类型和普通的Service的资源

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
    targetPort: 8080
  selector:
    app: nginx

接下来创建对应的StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web

创建一个pod,对其发起网络请求

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: network-tools
  name: network-tools
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: network-tools
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: network-tools
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - network-tools
              topologyKey: kubenetes.io/hostname
            weight: 1
      containers:
      - env:
        - name: SYSTEM_INFORMATION
          value: centos
        # 镜像内默认安装了部分网络相关命令
        image: 528909316/check:debian_11
        imagePullPolicy: Always
        name: network-tools
        ports:
        - containerPort: 9000
          protocol: TCP
        resources:
          limits:
            cpu: 100m
            memory: 100Mi
          requests:
            cpu: 10m
            memory: 30Mi
      restartPolicy: Always

稳定的网络标识:

接下来,可以再新启动的容器中,使用域名web-0.nginx.default.svc.cluster.local或者web-0.nginx(pod名称.Service名称)来访问StatefulSet中指定的pod了.

ping -c 1 web-0.nginx.default.svc.cluster.local

稳定的网络标识是StatefulSet的功能,并不是headless提供的.如果将Service替换为ClusterIP类型同样有相同作用.


以上内容纯属个人总结理解,如有错误恳请大佬的斧正和指点,感激不尽.

你可能感兴趣的:(Kubernetes,kubernetes)