写在之前
看这篇文件前,可以先查看 iptables 理论基础及日志记录,再预习一下 LVS 知识点 LVS 基础性实验及抓包分析。
说到 kube-proxy,就不得不说下 Service,它是为 Pod 提供统一访问入口的,Service 资源的实现就是依赖 kube-proxy 提供的功能,Kube-proxy 本身有三种模式 userspace、iptables、ipvs,而 Service 的实现又有四种不同的类型 ClusterIP、NodePort、LoadBlance和HeadLess(无头服务),根据不同的类型定义 Service 时,又会遇到三种不同类型的端口,Port 端口即 Service 对外提供服务的端口,targetport 即目标 Pod 提供服务的端口,nodeport 即当类型为 NodePort 时,使用的端口,可以自定义端口号或者使用默认的。
我们大体知道了这些知识后,我们创建了一个 Service 资源后,我们访问它时,是如何通信的,经过了哪些步骤?这里涉及的知识点比较多,比如 iptables 、ipvs、路由策略等等,今天讲重点总结下。
这里说明下,本次总结使用的 CNI 插件是 Calico,由于 CNI 插件不同、CNI 插件使用的模式不同,都有可能造成转发规则有一点点小差别,但不影响整体分析思路。
kube-proxy 三种模式分析
kubernetes 上面的 service 资源的实现方式是由 kube-proxy 提供的模式决定的,kube-proxy 提供三种模式:userspace (Kubernetes1.2版本之前)、iptables、ipvs(推荐的),如果不满足ipvs时,会自动降为iptables模式,再讲三种模式前,先简单说下Service的工作原理;
在 kubernetes 平台之上,Pod 是由生命周期的,为了给客户端提供一个固定的访问端点,因此我们在客户端与服务端(pod)之间加了一层固定的中间层,这个中间层,我们称它为 Service,当某个 service 服务背后资源发生了改变时,service 根据标签选择器,会立即判断是 pod 多了一个还是少了一个,这种信息会立即反应到 API server 上,因为多个 pods 的信息是需要存在 kube-apiserver 的 etcd 中的,而 kube-proxy 能够 watch(监控) 到这种变化,并立即将其转化为 ipvs 规则或者 iptables 规则 ,它的转换是动态的,并且是实时的,同理如果一个pod 被删除了,它的状态信息也会反馈到 kube-apiserver 的 etcd 当中,而后这种变化会被kube-proxy watch监控到,它会立即将其转化为 iptables 或者 IPVS 规则,基本上都是立即同步的。
userspace
(图片来源于网络)
当client pod 访问某个服务时,会先到达当前节点的内核空间 iptables 规则(我们知道 iptables规则就是 service 的规则),这个 service 规则的工作方式是 ,请求到达 service 以后,由 service 把它转化为本地监听到某个套接字上面的用户空间程序 kube-proxy,然后由它来处理,处理完以后再转给 service IP,最终代理到这个 service 关联和各个 pod, 实现调度,大家发现,这个请求由 client Pod 发给 service,service 还要回到监听这个端口的 kube-proxy, 再由 kube-proxy 再进行分发,kube-proxy 是工作在用户空间的进程,所以被称为userspace,这种方式,效率很低,原因在于,先要到内核空间,回到 kube-proxy 的用户空间,由各种 kube-proxy 封装成报文代理完成以后,再推到内核空间,然后有 iptables 规则进行转发,需要在内核空间与用户空间来回转换 2 次,效率低下。
iptables
(图片来源于网络)
IPVS
由于 kube-proxy 的 ipvs 模式需要提供 iptables 包过滤、SNAT、masquared(伪装)等,具体来说,ipvs 将使用 ipset 来存储需要 DROP 或 masquared 的流量的源或目标地址的规则集,以确保 iptables 规则的数量是恒定的,不会因为集群规模的扩大,而 iptables 条目增加,这里有一个关键是 SNAT,端口转换,通过上面的分析,我们只能使用 ipvs 的 NAT 模式。
很显然我们在安装并配置 kubernetes 的时候,你设定service工作在什么模式下,它就应该会生成对应的模式的规则,kubernetes 1.10 之前的版本,使用的是 iptables 规则,Kubernetes 1.2 版本之前是使用的 userspace 的模式,kubernetes 1.11 及以后默认使用的是 ipvs,如果ipvs 没有被激活,它将自动降级为 iptables 模式。
ipvs与iptables模式对比
ipvs 在大型集群规模部署时,性能更好;
ipvs 可以动态修改 ipset 集合;
ipvs 支持更为复杂的调度算法(最小负载、最少连接、加权等);
ipvs 支持服务器健康检查和连接重试等;
ipvs 是否可以满足所有场景
kube-proxy 在 iptables 模式下 ClusterIP 类型 Service 实现原理
1. 创建一个 ClusterIP 类型的 Service
[root@master01 ~]# cat a.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test3
namespace: default
spec:
replicas: 3
selector:
matchLabels:
run: nginx-test3
template:
metadata:
labels:
run: nginx-test3
spec:
containers:
- name: nginx-test3
image: nginx:1.7.9
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-deployment-nginx-test3
namespace: default
spec:
selector:
run: nginx-test3
type: ClusterIP
ports:
- name: svc-deployment-nginx-test3
port: 80
protocol: TCP
targetPort: 80
[root@master01 ~]#
2. 查看创建资源情况
[root@master01 ~]# kubectl get pods -o wide |grep nginx-test3
nginx-test3-844448d78f-2t7jm 1/1 Running 1 3h32m 192.168.244.7 master03 <none> <none>
nginx-test3-844448d78f-gvv5p 1/1 Running 1 3h32m 192.168.244.205 master01 <none> <none>
nginx-test3-844448d78f-vb69t 1/1 Running 1 3h32m 192.168.244.6 master03 <none> <none>
[root@master01 ~]#
[root@master01 ~]# kubectl get svc|grep nginx-test3
svc-deployment-nginx-test3 ClusterIP 10.102.145.229 <none> 80/TCP 3h33m
[root@master01 ~]#
[root@master01 ~]# ping 10.102.145.229 -c 1
PING 10.102.145.229 (10.102.145.229) 56(84) bytes of data.
--- 10.102.145.229 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
[root@master01 ~]#
kube-proxy 工作在 iptables 模式下时,不要尝试去ping ClusterIP,它是不通的,因为这个 IP 在整个集群中根本不存在,当然也就无法通过 IP 协议栈路由,底层 underlay 设备也无法感知这个 IP 的存在,这个 ClusterIP 只能是每一个 Node 节点可见(它的作用域是单主机),但要知道一点,这个 IP 是存在于 iptables 规则表中的,当访问这个 Cluster IP 时,会进行 SNAT 转换,然后再转发,下面分析转发逻辑。
[root@master01 ~]# curl 10.102.145.229
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@master01 ~]#
4. 数据流的访问流程
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
由 OUTPUT 链得知,它跳转到 KUBE-SERVICES 链,如下:
-A KUBE-SERVICES ! -s 192.168.224.0/24 -d 10.102.145.229/32 -p tcp -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3 cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.102.145.229/32 -p tcp -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3 cluster IP" -m tcp --dport 80 -j KUBE-SVC-S5Q5UWDES6IQLKVI
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
所有访问 Cluster IP 的源IP,如果源IP不是 Pod IP 段,一律进行打标记MARK 0x4000/0x4000,KUBE-MARK-MASQ 链的本质就是使用 iptables 的 MARK 命令,对所有跳转到 KUBE-MARK-MASQ 链中的规则,设置 Kubernetes 独有的 MARK 标记,方便数据包在离开本节点到达 POSTROUTING 链中时,对匹配有此标签的数据包做一次 SNAT,即MASQUERADE(用节点IP替换包的源IP)这里侧面也说明了 Cluster IP 的作用域是本 Node 节点。打完标签后再转到KUBE-SVC-*子链中,如下:
[root@master01 ~]# iptables -S -t nat |grep -- '-A KUBE-SVC-S5Q5UWDES6IQLKVI'
-A KUBE-SVC-S5Q5UWDES6IQLKVI -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-J2PGWC66YETLDU5R
-A KUBE-SVC-S5Q5UWDES6IQLKVI -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-U3UEG3JCI7XV4TFO
-A KUBE-SVC-S5Q5UWDES6IQLKVI -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3" -j KUBE-SEP-UKYVD2CWS6HA4G4C
[root@master01 ~]#
由于我们创建了 3 个 Pod,KUBE-SVC-* 会向后面三个 Pod 分发流量,iptables 模式下只能使用简单的轮询,这是使用 iptables 的 random 模块均等的分发流量,33.3% 的流量转发到 KUBE-SEP-J2PGWC66YETLDU5R 子链,将 (1-1/3)*0.5=33.33% 的流量转发到 KUBE-SEP-U3UEG3JCI7XV4TFO 子链中,剩余的 33.33% 流量转发到 KUBE-SEP-UKYVD2CWS6HA4G4C 子链中,转发子链的规则如下(这里只举一条子链):
[root@master01 ~]# iptables -S -t nat |grep -- '-A KUBE-SEP-J2PGWC66YETLDU5R'
-A KUBE-SEP-J2PGWC66YETLDU5R -s 192.168.244.205/32 -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3" -j KUBE-MARK-MASQ
-A KUBE-SEP-J2PGWC66YETLDU5R -p tcp -m comment --comment "default/svc-deployment-nginx-test3:svc-deployment-nginx-test3" -m tcp -j DNAT --to-destination 192.168.244.205:80
[root@master01 ~]#
KUBE-SEP-J2PGWC66YETLDU5R 子链它会先判断下是否为 Pod IP 自身是源IP,通过访问 Cluster IP 分发流量时,又分发到了自己,此时需要对 Pod IP进行打标签,转后再进行DNAT,否则子链 KUBE-SEP-J2PGWC66YETLDU5R 的功能就是 DNAT 到其中一个Endpoint IP,即 Pod IP,这里是192.168.244.205。DNAT 后,数据包进入来到POSTROUTING 链如下:
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
为了实验,后端 Pod 变为一台,方便抓包测试,环境构建测试
[root@master01 ~]# kubectl get pods -o wide |grep nginx-test3
nginx-test3-844448d78f-529kr 1/1 Running 0 161m 192.168.244.169 node01 <none> <none>
[root@master01 ~]# kubectl get svc |grep nginx-test3
svc-deployment-nginx-test3 ClusterIP 10.106.226.22 <none> 80/TCP 161m
[root@master01 ~]# kubectl get ep |grep nginx-test3
svc-deployment-nginx-test3 192.168.244.169:80 161m
[root@master01 ~]#
详情测试图
kube-proxy 在 iptables 模式下 NodePort 类型 Service 实现原理
1. 创建一个 NodePort 类型的 Service
[root@master01 ~]# cat a.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test4
namespace: default
spec:
replicas: 3
selector:
matchLabels:
run: nginx-test4
template:
metadata:
labels:
run: nginx-test4
spec:
containers:
- name: nginx-test4
image: nginx:1.7.9
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-deployment-nginx-test4
namespace: default
spec:
selector:
run: nginx-test4
type: NodePort
ports:
- name: svc-deployment-nginx-test4
port: 80
protocol: TCP
targetPort: 80
[root@master01 ~]#
2. 查看创建资源情况
[root@master01 ~]# kubectl get pods -o wide |grep nginx-test4
nginx-test4-64fb9cb9dc-4w4ks 1/1 Running 0 67s 192.168.244.8 master03 <none> <none>
nginx-test4-64fb9cb9dc-5jg4h 1/1 Running 0 67s 192.168.244.73 master02 <none> <none>
nginx-test4-64fb9cb9dc-9jx5s 1/1 Running 0 67s 192.168.244.168 node01 <none> <none>
[root@master01 ~]# kubectl get svc |grep nginx-test4
svc-deployment-nginx-test4 NodePort 10.103.222.243 <none> 80:32357/TCP 75s
[root@master01 ~]#
3. 测试 ClusterIP 连通性
[root@master02 ~]# curl master01:32357
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@master02 ~]#
4. 数据流的访问流程
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
PREROUTING 链,把所有到我的规则链,都跳转到 KUBE-SERVICES 规则链, KUBE-SERVICES 再跳转到 KUBE-NODEPORTS 链,如下:
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -m tcp --dport 32357 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -m tcp --dport 32357 -j KUBE-SVC-6Y7O4VBRRWZ7EG3T
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
KUBE-NODEPORTS链,把所有经过它的,都一律进行打标记MARK 0x4000/0x4000,KUBE-MARK-MASQ 链的本质就是使用 iptables 的 MARK 命令,对所有跳转到 KUBE-MARK-MASQ 链中的规则,设置 Kubernetes 独有的 MARK 标记,方便数据包在离开本节点到达 POSTROUTING 链中时,对匹配有此标签的数据包做一次 SNAT,即MASQUERADE(用节点IP替换包的源IP)。打完标签后再转到 KUBE-SVC-* 子链中,如下:
-A KUBE-SVC-6Y7O4VBRRWZ7EG3T -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-Q2NOSXUGZLKQI6AX
-A KUBE-SVC-6Y7O4VBRRWZ7EG3T -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-3KL5HE27HYV2W5P7
-A KUBE-SVC-6Y7O4VBRRWZ7EG3T -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -j KUBE-SEP-66VRVT2NYSNSM73Y
由于我们创建了 3 个 Pod,KUBE-SVC-* 会向后面三个 Pod 分发流量,iptables 模式下只能使用简单的轮询,这是使用 iptables 的 random 模块均等的分发流量,33.3% 的流量转发到 KUBE-SEP-Q2NOSXUGZLKQI6AX 子链,将 (1-1/3)*0.5=33.33% 的流量转发到 KUBE-SEP-3KL5HE27HYV2W5P7 子链中,剩余的 33.33% 流量转发到 KUBE-SEP-66VRVT2NYSNSM73Y 子链中,转发子链的规则如下(这里只举一条子链):
-A KUBE-SEP-Q2NOSXUGZLKQI6AX -s 192.168.244.168/32 -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -j KUBE-MARK-MASQ
-A KUBE-SEP-Q2NOSXUGZLKQI6AX -p tcp -m comment --comment "default/svc-deployment-nginx-test4:svc-deployment-nginx-test4" -m tcp -j DNAT --to-destination 192.168.244.168:80
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
KUBE-SEP-Q2NOSXUGZLKQI6AX 子链它会先判断下是否为 Pod IP 自身是源IP,通过访问 NodePort 分发流量时,又分发到了自己,此时需要对 Pod IP进行打标签,转后再进行DNAT,否则子链 KUBE-SEP-Q2NOSXUGZLKQI6AX 的功能就是 DNAT 到其中一个 Endpoint IP,即 Pod IP,这里是192.168.244.168。DNAT 后,数据包有两种选择,根据目标地址是否为本机 Pod IP,如果是访问后返回,如果不是数据包会进入到 Forward 链如下:
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
FORWARD 链很简单,到它的数据包全部转发到 KUBE-FORWARD 链中, KUBE-FORWARD 链对打过 mark 标签的允许通过并记录跟踪状态,否则不允许通过,接下来就进入到 POSTROUTING 链,如下:
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
POSTROUTING规则链相对简单,所有到此链的数据包都跳转到 KUBE-POSTROUTING 链,它对只要标记了0x4000/0x4000 的包就做 SNAT,即MASQUERADE,至此数据包就出去访问了。
5. 数据包流转图
6. IP转换图
为了实验,后端 Pod 变为一台,方便抓包测试,环境构建测试
[root@master01 ~]# kubectl get pods -o wide |grep nginx-test4
nginx-test4-64fb9cb9dc-4w4ks 1/1 Running 0 21h 192.168.244.8 master03 <none> <none>
[root@master01 ~]# kubectl get svc |grep nginx-test4
svc-deployment-nginx-test4 NodePort 10.103.222.243 <none> 80:32357/TCP 21h
[root@master01 ~]# kubectl get ep|grep nginx-test4
svc-deployment-nginx-test4 192.168.244.8:80 21h
[root@master01 ~]#
详情测试图
kube-proxy 在 IPVS 模式下 ClusterIP 类型 Service 实现原理
1. 创建一个 ClusterIP 类型的 Service
[root@k8s-master-1 ~]# cat 8.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test3
namespace: default
spec:
replicas: 3
selector:
matchLabels:
run: nginx-test3
template:
metadata:
labels:
run: nginx-test3
spec:
containers:
- name: nginx-test3
image: nginx:1.7.9
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-deployment-nginx-test3
namespace: default
spec:
selector:
run: nginx-test3
type: ClusterIP
ports:
- name: svc-deployment-nginx-test3
port: 80
protocol: TCP
targetPort: 80
[root@k8s-master-1 ~]#
2. 查看创建资源情况
[root@k8s-master-1 ~]# kubectl get pods -o wide |grep nginx-test3
nginx-test3-96fc9ff75-fbk4l 1/1 Running 0 58s 100.71.40.232 100.72.16.7 <none> <none>
nginx-test3-96fc9ff75-fcpfb 1/1 Running 0 58s 100.71.175.150 100.72.16.14 <none> <none>
nginx-test3-96fc9ff75-h4zd4 1/1 Running 0 58s 100.71.43.55 100.72.16.15 <none> <none>
[root@k8s-master-1 ~]# kubectl get svc |grep nginx-test3
svc-deployment-nginx-test3 ClusterIP 100.72.191.61 <none> 80/TCP 69s
[root@k8s-master-1 ~]#
3. 测试 ClusterIP 连通性
[root@k8s-master-1 ~]# ping 100.72.191.61 -c 2
PING 100.72.191.61 (100.72.191.61) 56(84) bytes of data.
64 bytes from 100.72.191.61: icmp_seq=1 ttl=64 time=0.139 ms
64 bytes from 100.72.191.61: icmp_seq=2 ttl=64 time=0.125 ms
--- 100.72.191.61 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.125/0.132/0.139/0.007 ms
[root@k8s-master-1 ~]# curl 100.72.191.61
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@k8s-master-1 ~]#
这里为什么可以 ping 通 Cluster IP 呢?这是因为在 IPVS 模式下创建 ClusterIP 时需要以下满足以下几点:
1. 确保有一块虚拟的网卡,ipvs 模式下默认为 kube-ipvs0,为什么 kube-proxy 创建一个虚拟网卡?因为 IPVS 的 netfilter 钩子挂载 INPUT 链,我们需要把 Service IP绑定到虚拟网卡上,让内核觉得虚拟IP就是本机的IP,进而进入到 INPUT 规则链;
2. 把 Service IP 绑定在虚拟网卡(kube-ipvs0) 上面;
3. 通过socket 调用,创建 IPVS 的 Virtual Server 和 Real Server,分别对应 kubernetes 的 Service 和 Endpoints;
[root@k8s-master-1 ~]# ipset list KUBE-CLUSTER-IP
Name: KUBE-CLUSTER-IP
Type: hash:ip,port
Revision: 5
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 3384
References: 2
Number of entries: 53
Members:
。。。
100.72.191.61,tcp:80
。。。
[root@k8s-master-1 ~]#
[root@k8s-master-2 ~]# ipvsadm -S -n |grep 100.72.191.61
-A -t 100.72.191.61:80 -s rr
-a -t 100.72.191.61:80 -r 100.71.40.232:80 -m -w 1
-a -t 100.72.191.61:80 -r 100.71.43.55:80 -m -w 1
-a -t 100.72.191.61:80 -r 100.71.175.150:80 -m -w 1
[root@k8s-master-2 ~]#
ipvs 使用的转发使用的是轮询,权重都是1,转发到后后端就是我们刚才创建的 Pod IP。
4. 数据流的访问流程
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
所有到达 OUTPUT 的数据包,都会转发到 KUBE-SERVICES 规则链,KUBE-SERVICES链中使用了 ipset 集合中的 KUBE-CLUSTER-IP 保存着所有的 ClusterIP 以及监听端口信息,把所有访问 ClusterIP 的包都打上 0x4000/0x4000 标识,方便后面做SNAT,地址伪装,接着数据包进入到 POSTROUTING 链如下:
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE
5. 数据包流转图
6. IP转换图
为了实验,后端 Pod 变为一台,方便抓包测试。
[root@k8s-master-1 ~]# kubectl get pods -o wide |grep nginx-test3
nginx-test3-96fc9ff75-h4zd4 1/1 Running 0 21h 100.71.43.55 100.72.16.15 <none> <none>
[root@k8s-master-1 ~]# kubectl get svc |grep nginx-test3
svc-deployment-nginx-test3 ClusterIP 100.72.191.61 <none> 80/TCP 21h
[root@k8s-master-1 ~]# kubectl get ep |grep nginx-test3
svc-deployment-nginx-test3 100.71.43.55:80 21h
[root@k8s-master-1 ~]#
详情测试图
Kubernetes ClusterIP 是通过 IPVS Service实现的,只有 Pod 当作 IPVS Service的 Server时,需要通过NAT MASQUERADE实现转发。
kube-proxy 在 IPVS 模式下 NodePort 类型 Service 实现原理
1. 创建一个 NodePort 类型的 Service
[root@k8s-master-1 ~]# cat 8.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test4
namespace: default
spec:
replicas: 3
selector:
matchLabels:
run: nginx-test4
template:
metadata:
labels:
run: nginx-test4
spec:
containers:
- name: nginx-test4
image: nginx:1.7.9
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-deployment-nginx-test4
namespace: default
spec:
selector:
run: nginx-test4
type: NodePort
ports:
- name: svc-deployment-nginx-test4
port: 80
protocol: TCP
targetPort: 80
[root@k8s-master-1 ~]#
2. 查看创建资源情况
[root@k8s-master-1 ~]# kubectl get pods -o wide |grep nginx-test4
nginx-test4-6487454977-f7nj7 1/1 Running 0 10m 100.71.87.48 100.72.16.19 <none> <none>
nginx-test4-6487454977-s2bs7 1/1 Running 0 10m 100.71.40.233 100.72.16.7 <none> <none>
nginx-test4-6487454977-vp2js 1/1 Running 0 10m 100.71.43.56 100.72.16.15 <none> <none>
[root@k8s-master-1 ~]# kubectl get svc |grep nginx-test4
svc-deployment-nginx-test4 NodePort 100.72.238.35 <none> 80:31169/TCP 10m
[root@k8s-master-1 ~]#
3. 测试 ClusterIP 连通性
[root@k8s-master-2 ~]# ifconfig bond0
bond0: flags=5187<UP,BROADCAST,RUNNING,MASTER,MULTICAST> mtu 1500
inet 100.72.16.8 netmask 255.255.255.0 broadcast 100.72.16.255
ether 14:18:77:38:59:4a txqueuelen 1000 (Ethernet)
RX packets 524189153 bytes 276767216375 (257.7 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 496747981 bytes 201069645644 (187.2 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@k8s-master-2 ~]# curl 100.72.16.4:31169
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@k8s-master-2 ~]#
IPVS 模式下 NodePort 转发规则如下:
[root@k8s-master-1 ~]# ipvsadm -S -n |grep 31169
-A -t 100.72.16.4:31169 -s rr
-a -t 100.72.16.4:31169 -r 100.71.40.233:80 -m -w 1
-a -t 100.72.16.4:31169 -r 100.71.43.56:80 -m -w 1
-a -t 100.72.16.4:31169 -r 100.71.87.48:80 -m -w 1
。。。
[root@k8s-master-1 ~]#
[root@k8s-master-2 ~]# ipvsadm -S -n |grep 31169
-A -t 100.72.16.8:31169 -s rr
-a -t 100.72.16.8:31169 -r 100.71.40.233:80 -m -w 1
-a -t 100.72.16.8:31169 -r 100.71.43.56:80 -m -w 1
-a -t 100.72.16.8:31169 -r 100.71.87.48:80 -m -w 1
。。。
[root@k8s-master-2 ~]#
[root@k8s-master-2 ~]# ipset list KUBE-NODE-PORT-TCP
Name: KUBE-NODE-PORT-TCP
Type: bitmap:port
Revision: 3
Header: range 0-65535
Size in memory: 8300
References: 1
Number of entries: 10
Members:
。。。
31169
。。。
[root@k8s-master-2 ~]#
不同 Node 节点上面的 ipvs转发规则是不一样的,但到后端的Real Server(即Pod IP)都相同。
4. 数据流的访问流程
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT
-A KUBE-NODE-PORT -p tcp -m comment --comment "Kubernetes nodeport TCP port for masquerade purpose" -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
PREROUTING 链,把所有到它的数据包,都跳转到 KUBE-SERVICES 规则链, KUBE-SERVICES 再跳转到 KUBE-NODEPORT 链,KUBE-NODEPORT 链中使用了 ipset 集合中的 KUBE-NODE-PORT-TCP 保存着所有的 NodeIP 以及监听端口信息,把所有访问 NodeIP 的包都打上 0x4000/0x4000 标识,方便后面做SNAT,地址伪装,接着数据包进入到 POSTROUTING 链如下:
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE
5. 数据包流转图
6. IP转换图
为了实验,后端 Pod 变为一台,方便抓包测试。
[root@k8s-master-1 ~]# kubectl get pods -o wide |grep nginx-test4
nginx-test4-6487454977-f7nj7 1/1 Running 0 20h 100.71.87.48 100.72.16.19 <none> <none>
[root@k8s-master-1 ~]# kubectl get svc |grep nginx-test4
svc-deployment-nginx-test4 NodePort 100.72.238.35 <none> 80:31169/TCP 20h
[root@k8s-master-1 ~]# kubectl get ep |grep nginx-test4
svc-deployment-nginx-test4 100.71.87.48:80 20h
[root@k8s-master-1 ~]#
详情测试图
Kubernetes NodePort 是通过 IPVS Service实现的,只有 Pod 当作 IPVS Service的 Server时,需要通过NAT MASQUERADE实现转发。
kube-proxy 中 IPVS 模式中下使用 IPSET 规则集
IP Set 名称 | ip set 集合内容 | 功能说明 |
KUBE-CLUSTER-IP | 所有Service IP + port | 如果启动参数中加了masquerade-all=true或clusterCIDR,用来做masquared伪装 |
KUBE-LOOP-BACK | 所有Service IP + port + IP | 针对hairpin问题做masquerade |
KUBE-EXTERNAL-IP | Service External IP + port | 对访问外部 IP 的流量做masquerade |
KUBE-LOAD-BALANCER | lb型service的ingress IP + port | 对访问load balancer类型service的流量做 masquerade |
KUBE-LOAD-BALANCER-LOCAL | 规定了externalTrafficPolicy=local的lb型的service的ingress IP + port | 接收规定了externalTrafficPolicy=local的lb型service |
KUBE-LOAD-BALANCER-FW | 规定了loadBalancerSourceRanges的lb型的service的ingress IP + port | 针对规定了loadBalancerSourceRanges的lb型service,用于过滤流量 |
KUBE-LOAD-BALANCER-SOURCE-CIDR | lb型的service的ingress IP + port + source CIDR | 针对规定了loadBalancerSourceRanges的lb型service,用于过滤流量 |
KUBE-NODE-PORT-TCP | NodePort型Service TCP port | 对访问NodePort(TCP)的流量作masquerade |
KUBE-NODE-PORT-LOCAL-TCP | 规定了externalTrafficPolicy=local的NodePort型Service TCP port | 接收规定了externalTrafficPolicy=local的NodePort型service |
KUBE-NODE-PORT-UDP | NodePort型Service UDP port | 对访问NodePort(UDP)的流量作masquerade |
KUBE-NODE-PORT-LOCAL-UDP | 规定了externalTrafficPolicy=local的NodePort型Service UDP port | 接收规定了externalTrafficPolicy=local的NodePort型service |
kube-proxy 在 userspace 模式下
由于 userspace 模式使用较少,这里只是简单就下程序的常用规则链,不再进行实验说明。
总结
service 的功能是由 kube-proxy组件实现了,kube-proxy 的模式有三种 userspace、iptables、ipvs。目前常用的是iptables和ipvs,两者也有一定的区别,iptables 规则过多会造成性能下降,因为它是基于链表实现的,查找复杂度是O(n),当节点数量过多时,iptables 规则成几何级增长,内核支撑不住,最终 iptables 将成为性能瓶颈。iptables 是专门用来做防火墙的,而不是做负载均衡(虽然小规模也是可以的),kube-proxy 使用 IPVS NAT模式 作为 kube-proxy 后端,不仅提高了转发性能,再结合 ipset 还使 iptables 规则变得清楚,iptables 规则数量不会变。
iptables、ipvs 都是基于netfilter,但ipvs是专门做负载均衡,支持丰富的调度算法,配置简单,并且它是基于ipset进行散列快速查找,复杂度为O(1),但 ipvs 在某些场景下,也需要用到 iptables NAT 规则,但iptables 规则不会增加,他使用的是 ipset 规则集。
本文重点说明类型为 ClusterIP、NodePort 两种类型的 Service,分别工作在 kube-proxy 模式为 ipvs 和 iptables 下的规则转发,流量抓包分析,其实其它类型的 Service 也可以同样的进行分析,并且要注意根据使用 CNI 插件的不同、CNI 插件工作模式的不同,又会有不同的转发规则策略,本文总结的都是最基础的部分,其它复杂的场景需要自行测试总结。
您的关注是我写作的动力
往期分享
专辑分享