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