Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

前言

    最近在测试 Kubernetes 应用的时候,发现了一个非常蛋疼的问题:同一个 Node 节点内的 Pod 不能通过 Service 互访

    各种百度、google,都没有查到有效的解决方法,一度怀疑是我部署的集群有问题,经过多天的折腾,终于找到问题所在,下面进行一下记录,作为一个实验报告吧~~

 

环境信息

Kubernetes版本 v1.18.8
网络组件 flannel:v0.12.0-amd64
组件部署方式 flannel 使用 DaemonSet 部署,coreDns 使用Deployment 部署,其他组件(etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy)使用二进制部署
kube-proxy运行模式 ipvs

 

资源名称 资源类型 Cluster-IP 网关 所在节点 监听端口
mars1 Pod 10.244.0.18 10.244.0.1 k8s1 /
mars2 Pod 10.244.2.31 10.244.2.1 k8s2 /
mars3 Pod 10.244.1.43 10.244.1.1 k8s3 /
nginx-app Deployment /   / /
nginx-app-57679cfb75-6z8nq Pod 10.244.0.19 10.244.0.1 / 80
nginx Service 10.98.171.178   k8s1 8080
kubectl get pod -o wide
kubectl get svc
kubectl get deployment

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

 

现象

1、mars1、mars2、mars3 三个 Pod 访问 nginx-app-57679cfb75-6z8nq 这个 Pod 的 80 端口是正常的

kubectl exec mars1 -- nc -v -z -w 5 10.244.0.19 80
kubectl exec mars2 -- nc -v -z -w 5 10.244.0.19 80
kubectl exec mars3 -- nc -v -z -w 5 10.244.0.19 80

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

 2、mars1 不能访问 nginx 这个 Service 的 8080 端口,mars2、mars3 可以访问 nginx 这个Service 的 8080 端口

kubectl exec mars1 -- nc -v -z -w 5 10.98.171.178 8080
kubectl exec mars2 -- nc -v -z -w 5 10.98.171.178 8080
kubectl exec mars3 -- nc -v -z -w 5 10.98.171.178 8080

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

 3、多次尝试后,发现当两个 Pod 处于同一个 Node 节点时,就不能通过 Service 的 IP+端口互访 

即如果把 nginx 的 Pod 调度到 k8s2 节点的话,就会变成 mars2 不能访问 10.98.171.178:8080,调度到 k8s3 节点的话,mars3 不能访问 10.98.171.178:8080。

 

排查过程

1、首先在 Google 、百度上面各种查,大多数的文章主要是针对 kubelet 的 hairpinMode 配置,该配置必须设置为 hairpin-veth 或者 promiscuous-bridge,查看了一下,我当前的配置是 hairpinMode: promiscuous-bridge,没有问题,尝试修改为 hairpin-veth 并重启 kubelet ,问题依旧。

#在所有 Node 节点执行
sed -i s/hairpinMode: promiscuous-bridge/hairpinMode: hairpin-veth/ /var/lib/kubelet/config.yaml
systemctl restart kubelet

 

2、同时需要 /sys/devices/virtual/net/docker0/brif/veth-xxx/hairpin_mod 内容设置为1,查了配置已经均为1,没有问题。

 

3、尝试把 kube-proxy 的运行模式改为 iptables 并重启 kube-proxy ,问题依旧。

 

4、没办法之下只能抓包分析,由于一开始使用的镜像是 nginx官方镜像、busybox官方镜像,不方便进行抓包,于是自行用 centos 的官方镜像安装了一些测试工具进行调试,上面的 mars1、mars2、mars3、nginx-app 都是使用该镜像生成的。

     (1)首先看看正常访问的抓包结果:在 mars2 及 nginx-app-57679cfb75-6z8nq 上面抓包(两者在不同的 Node 节点上),mars2 上面使用自身的 IP为条件,nginx-app-57679cfb75-6z8nq 上面使用 80 端口为条件,然后在 mars2 上面使用 nc 命令访问 nginx 这个 Service 的 8080 端口,结果如下:

mars2 的抓包结果

tcpdump -vnn -i eth0 host 10.244.2.31

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

   nginx-app-57679cfb75-6z8nq 的抓包结果

tcpdump -vnn -i eth0 port 80

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

       可以看到,在 mars2 上面是自身的随机端口访问 Service 的 8080 端口,然后收到了 Service 的返回,正常建立了 TCP 三次握手。

       而在 nginx-app-57679cfb75-6z8nq 上面,可以看到源 IP 是 mars2 的 Pod  IP,也就是说,Service 只是对访问的流量进行了 DNAT,没有做 SNAT,但这样的话为什么 nginx-app-57679cfb75-6z8nq 直接给 mars2 的 Pod IP 回包,而 mars2 收到的包的源 IP 却又变成了 Service 的 IP 呢,我理解是因为 mars2 和 nginx-app-57679cfb75-6z8nq 不在同一个 Node 节点上,所以回包会经过网关 10.244.0.1 ,而网关会自动做一次回包的 SNAT ,把 nginx-app-57679cfb75-6z8nq 的 IP 转成 Service 的 IP,所以 mars2 收到的回包源 IP 就变成了 Service 的 IP。

     到了这里,问题的答案似乎已经呼之欲出了,接下来进行第二个抓包试验。

 

     (2)再来看看访问不正常的抓包结果:在 mars1 及 nginx-app-57679cfb75-6z8nq 上面抓包(两者在相同的 Node 节点上),mars1 上面使用自身的 IP为条件,nginx-app-57679cfb75-6z8nq 上面使用 80 端口为条件,然后在 mars1 上面访问 nginx 这个 Service 的 8080 端口,结果如下:

 mars1 的抓包结果

tcpdump -vnn -i eth0 host 10.244.0.18

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

   nginx-app-57679cfb75-6z8nq 的抓包结果

tcpdump -vnn -i eth0 port 80

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

  

      以上两个截图进一步验证了我的猜想, 原因就在于 Service 没有对访问它的流量做 SNAT ,nginx-app-57679cfb75-6z8nq 直接以 mars1 的 Pod IP 为目标 IP ,以自身的 Pod IP 为源 IP 进行回包。而由于两者是在同一个 Node 节点,处于同一个子网,走的是二层通信,没有经过网关,也不会有 NAT 转换。mars1 其实是已经接收到了 nginx-app-57679cfb75-6z8nq 的回包,但因为回包的源 IP+端口(10.244.0.19:80)和它发出去的请求的目标 IP+端口(10.98.171.178:8080)不一致,直接把包丢弃了。

 

      既然问题的原因已经找到了,那么解决的方向就是如何让 Service 对访问它的流量做 SNAT 。又经过一番排查,发现了 kube-proxy 有一个配置项 masqueradeAll ,当前的配置为 false 。

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

      根据字面意思,是把流量进行伪装,这不就是 iptables 里面的 MASQUERADE 吗,又找了 google 爸爸,查到这个配置项的作用:

masquerade-all 如果使用纯 iptables 代理,对所有通过集群 Service IP发送的流量进行 SNAT(通常不配置)

      看起来就是我要的结果,把配置文件里面的值改成 true 并重启 kube-proxy。

#在所有 Node 节点执行
sed -i s/masqueradeAll: false/masqueradeAll: true/ /var/lib/kube-proxy/config.yaml
systemctl restart kube-proxy

 

      在 mars1 及 nginx-app-57679cfb75-6z8nq 上面重新抓包,mars1 上面使用自身的 IP为条件,nginx-app-57679cfb75-6z8nq 上面使用 80 端口为条件,然后在 mars1 上面访问 nginx 这个 Service 的 8080 端口,结果如下:

 mars1 的抓包结果

tcpdump -vnn -i eth0 host 10.244.0.18

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

 nginx-app-57679cfb75-6z8nq 的抓包结果

tcpdump -vnn -i eth0 port 80

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

 

       根据以上截图,可以看到,这次 mars1 正常建立了 TCP 三次握手,因为收到了 Service 的回包。另外,nginx-app-57679cfb75-6z8nq 上面看到访问的源 IP 变成了 mars1 的网关(10.244.0.1),所以 Service 针对访问它的流量做了 SNAT+DNAT,准确来说是做了 MASQUERADE ,nginx-app-57679cfb75-6z8nq 回包的时候就不会产生之前的 IP+端口不对应的问题了。同时测试了 mars2、mars3 都可以正常访问 Service。

 

结论

       这种故障主要出现在 Kubernetes 集群里面应用间需要通过 Service 的 IP 或者域名互访的场景,如果集群里面的应用只提供给外部访问,是不会有这种问题存在的。如果 nginx 是多个 Pod 的话,可能不容易发现,只会出现偶尔访问不通的情况,排查的时候最后只保留一个 Pod 。

       这个故障排查了好几天,主要耗费时间的地方在于一直想在网上找到现成的解决办法,而一直找不到。其实抓包分析是很基础的排查方法,但由于是容器环境,镜像里面难免会缺少一些必要的工具/命令,这时候我们就需要一个方便用于排查问题的镜像。其实自己制作一个适合自己使用的镜像并非难事,对于日常问题的排查还是非常有用的。

 

 

 

Kubernetes 同一个 Node 节点内的 Pod 不能通过 Service 互访

上一篇:js多项内容复制


下一篇:http连接池