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

 

 

現象

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

 

 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

 

 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

 

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

tcpdump -vnn -i eth0 port 80

 

       可以看到,在 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

 

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

tcpdump -vnn -i eth0 port 80

  

      以上兩個截圖進一步驗證了我的猜想, 原因就在於 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 。

 

      根據字面意思,是把流量進行偽裝,這不就是 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

 

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

tcpdump -vnn -i eth0 port 80

 

       根據以上截圖,可以看到,這次 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 。

       這個故障排查了好幾天,主要耗費時間的地方在於一直想在網上找到現成的解決辦法,而一直找不到。其實抓包分析是很基礎的排查方法,但由於是容器環境,鏡像里面難免會缺少一些必要的工具/命令,這時候我們就需要一個方便用於排查問題的鏡像。其實自己制作一個適合自己使用的鏡像並非難事,對於日常問題的排查還是非常有用的。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM