在学习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
.
数据包三层目标IP应该是Service的IP,假设Service也是一个pod,按照正常的网络接收发送数据包.
Service可以正常收到这个包,因为目标IP是他自己,但是他不具备处理里面数据的能力,所以这个包最终是要交给pod去处理的.
但是这个包还不能直接发出去.因为目标IP是自己,数据包发出去即便能到对应的pod,那么pod也会将它丢弃.因为目标IP不是自己.
所以Service在此处要对数据包进行处理.而处理过程很简单 ,使用**DNAT**.
DNAT很简单,就是修改三层数据包中的目标IP,然后就变成了下面的样子:
这么一来,pod接收到数据包后发现目标IP是自己,就会继续拆包获取里面的数据.同时他还知道这个数据包的源IP,如果需要相应的话目标IP直接写数据包的源IP10.244.235.138
即可.
Service基于iptables(或IPVS)实现,所以DNAT这个步骤是由iptables来完成的.
访问ClusterIP类型的Service名称解析到Service的IP,然后对这个IP发起请求.而headless是没有这个IP的.他解析Service名字拿到的直接就是一组POD的IP.
直接少了一层转发,pod可以直接对pod发起请求.
在上面ClusterIP类型的Service请求中,可以看到一个targetPort
字段和port
字段.
targetPort : 监听从此端口来的流量
Port: 转发到pod的那个端口
在ClusterIP类型中,你请求Service时只能请求被targetPort
定义的端口.这个很简单就能理解,同样把Service想象成一个主机,这个主机监听了8080
端口,然后把这个端口接收到的请求转发到另一个主机的80
端口.如果你请求一个Service没有定义的端口那么则和主机一样返回Connection refused
(是Service返回的).
如果这个Service是headless类型的,那么则没有了这个端口的限制.headless类型的Service的yaml文件中的port
字段实际也没什么意义.因为你解析这个Service的名字得到的是一组pod的IP,发起请求时数据包会全部被StatefulSet的 pod所接收到.只需要pod监听了对应的端口那么就能互相通信.
直接解析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类型同样有相同作用.
以上内容纯属个人总结理解,如有错误恳请大佬的斧正和指点,感激不尽.