Kubernetes Service iptables 网络通信验证
Kubernetes gives Pods their own IP addresses and a single DNS name for a setof Pods, and can load-balance across them.
K8s Service会为每个 Pod 都设置一个它自己的 IP,并为一组 Pod 提供一个统一的 DNS 域名,还可以提供在它们间做负载均衡的能力。这篇文章会对 kube-proxy 的 iptables 模式内部的机制做一个验证。大体上涉及的内容如下:
实验配置
创建一个 Service,配置如下:
apiVersion: v1 |
创建后的 service 如下:
$ k get svc -o wide -A |
注意其中的 spring-test 和 kube-dns 两项,后面会用到。另外 service 对应的 podIP 如下:
$ k get ep |
DNS
K8s 会为 Service 创建一个 DNS域名,格式为 <svc>.<namespace>.svc.<cluster-domain>
,例如我们创建的 spring-test
Service 则会有 spring-test.default.svc.cluster.local
[1] 域名。
我们首先进入 pod,看一下 /etc/resolv.conf
文件,关于域名解析的配置:
nameserver 10.1.0.10 |
-
这里的
10.1.0.10
是 kube-dns service 的 cluster IP -
文件中配置了多个 search 域,因此我们写
spring-test
或spring-test.default
或spring-test.default.svc
都是可以解析的,另外注意解析后的 IP 也不是具体哪个 POD 的地址,而是为 Service 创建的虚拟地址ClusterIP。root@spring-test-77d9d6dcb5-m9mvr:/# nslookup spring-test
Server: 10.1.0.10
Address: 10.1.0.10#53
Name: spring-test.default.svc.cluster.local
Address: 10.1.68.7
root@spring-test-77d9d6dcb5-m9mvr:/# nslookup spring-test.default
Server: 10.1.0.10
Address: 10.1.0.10#53
Name: spring-test.default.svc.cluster.local
Address: 10.1.68.7
root@spring-test-77d9d6dcb5-m9mvr:/# nslookup spring-test.default.svc
Server: 10.1.0.10
Address: 10.1.0.10#53
Name: spring-test.default.svc.cluster.local
Address: 10.1.68.7 -
ndots:5
指的是如果域名中的.
大于等于 5 个,则不走 search 域,目的是减少常规域名的解析次数 [2]
iptables 转发
DNS 里创建的记录解决了域名到 ClusterIP 的转换问题,发送到 ClusterIP 的请求,如何转发到对应的 POD 里呢?K8s Service 有几种实现方式,这里验证的是 iptables 的实现方式:kube-proxy 会监听 etcd 中关于 k8s 的事件,并动态地对 iptables 做配置,最终由 iptables 来完成转发。先看看跟这个 Service 相关的规则如下:
0. -A PREROUTING -j KUBE-SERVICES |
我们先用 iptables-save
打印出所有的规则,筛选出和 spring-test
service相关的规则,删除了一些 comment,并对名字做了简化。可以看到有这么几类:
-
KUBE-NODEPORTS
,这类规则用来将发送到 NodePort 的报文转到KUBE-SVC-*
-
KUBE-SERVICES
:是识别目标地址为 ClusterIP(10.1.68.7
),命中的报文转到KUBE-SVC-*
做处理 -
KUBE-SVC
的作用是做负载均衡,将请求分配到KUBE-SEP
中 -
KUBE-SEP
通过 DNAT 替换目标地址为 Pod IP,转发到具体的 POD 中
另外经常看到 -j KUBE-MARK-MASQ
,它的作用是在请求里加上 mark,在 POSTROUTING
规则中做 SNAT,这点后面再细说。
我们开启 iptables 的 trace 模式 [3],并在其中一个 pod 发送一个请求,检查 TRACE 中规则的命中情况(由于输出特别多,这里挑选了重要的输出并做了精简):
0: nat:PREROUTING IN=cni0 OUT= SRC=10.244.1.7 DST=10.1.68.7 DPT=8080 |
- 在
PREROUTING
时,进入第 6 条进判定 -
KUBE-SERVICES
判断目标地址为10.1.68.7
且目标端口为8080
,于是跳转进入KUBE-SVC-S
链的判断 -
KUBE-SVC-S
有多条规则,从日志看最终是从第 10 条退出,进入KUBE-SEP-A
链 -
KUBE-SEP-A
最终命中第 3 条规则退出,但此时会进行 DNAT 转换目标地址 - 下一条日志显示,
DST
目标地址已经变成 pod 地址10.244.2.3
了
类似的,如果我们是通过 NodePort 来访问 Service,则 Trace 日志如下:
0: nat:PREROUTING: IN=eth0 OUT= SRC=192.168.50.135 DST=192.168.50.238 DPT=31080 |
iptables 负载均衡
上一节我们比较关注 iptables 转发的内容,那么如何做负载均衡?这部分是比较纯粹的iptables 知识 [4]:
首先:iptables 对于规则的解析是严格顺序的,所以如果只是单纯列出两个条目,则会永远命中第一条:
-A KUBE-SVC-S -j KUBE-SEP-A |
于是,我们需要第一条规则在某些条件下不命中。这样 iptables 就有机会执行后面的规则。iptables 提供了两种方法,第一种是有随机数,也是上一节我们看到的:
-A KUBE-SVC-S -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-B |
这条规则在执行时,iptables 会随机生成一个数,并以 probability
的概率命中当前规则。换句话说,第一条命中的概率是 p
,则第二条规则就是 1-p
。如果有 3 个副本,则会类似下面这样的规则,大家可以计算下最后三个 Pod 是不是平均分配:
-A KUBE-SVC-S --mode random --probability 0.33333333349 -j KUBE-SEP-A |
另外一种模式是 round-robin,但是 kubernetes 的 iptables 模式不支持,这里就不细说了。猜想 kubernetes iptables 模式下不支持的原因是虽然单机 iptables 能支持round-robin,但多机模式下,无法做到全局的 round-robin。
SNAT
前面我们提到 KUBE 系列的规则经常看到 -j KUBE-MARK-MASQ
,和它相关的规则有这些:
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000 |
首先 KUBE-MARK-MASQ
的作用是把报文打上 0x4000/0x4000
的标记,在 KUBE-POSTROUTING
时,如果报文中包含这个标记,会执行 -j MASQUERADE
操作,而这个操作的作用就是做源地址转换(SNAT)。那 SNAT 是什么,为什么要做 SNAT 呢?
这里引用 这篇文章里的图做说明:
如果没有 SNAT,被转发到 POD 的请求返回时,会尝试把请求直接返回给 Client,我们知道一个 TCP 连接的依据是(src_ip, src_port, dst_ip, dst_port),现在client 在等待 eIP/NP
返回的报文,等到的却是 pod IP
的返回,client 不认这个报文。换句话说,经过 proxy 的流量都正常情况下都应该原路返回才能工作。
在一些情况下可能希望关闭 SNAT,K8S 提供 externalTrafficPolicy: Local
的配置项,但流量的流转也会发生变化,这里不深入。
小结
这篇文章和上一篇 Flannel 网络通信验证类似,都是尝试搭建环境,在学习 kube-proxy 工作机制的同时,对 kube-proxy 的产出iptables 做一些验证。文章中验证了这些内容:
- 验证了 service ClusterIP 和 domain 的创建,及 pod 中
/etc/resolv.conf
中搜索域的设置 - 验证了 kube-proxy 生成的 iptables 规则,并验证请求在这些规则中的流转
- 学习了 iptables 负载均衡的工作机制
- 了解了 SNAT 是什么,kube-proxy 需要做 SNAT 的原因
这篇文章的信息量不大,希望读者也撸起袖子,实打实地做一些验证,能让我们对kube-proxy 涉及的 iptables 的操作有更深刻的理解。
参考
- A Guide to the Kubernetes Networking Model 讲解了 K8S 的网络模型,有一些(动)图描述网络包的走向
- Deep Dive kube-proxy with iptables mode 深挖 kube-proxy 在 iptables 模式下的工作原理,比本文更深入
- Debug Service K8S 官方文档,讲解 Service 不工作时常见的 Debug 方法