記一次 Kubernetes 網絡故障深度追蹤
某天晚上,客戶碰到了 Kubernetes 集群一直擴容失敗,所有的節點都無法正常加入集群。在經過多番折騰無解后,反饋到我們這里進行技術支持。這個問題的整個排查過程比較有意思,所以對其中的排查思路和用到的方法進行整理分享。
問題現象
運維同學在對客戶的 Kubernetes 集群進行節點擴容時,發現新增的節點一直添加失敗。該同學進行了初步的排查如下:
- 在新增節點上,訪問 Kubernetes master service vip 網絡不通
- 在新增節點上,直接訪問 Kubernetes master hostIP + 6443 網絡正常
- 在新增節點上,訪問其他節點的容器 IP 可以正常 ping 通
- 在新增節點上,訪問 coredns service vip 網絡正常
該客戶使用的 Kubernetes 版本是 1.13.10,宿主機的內核版本是 4.18(CentOS 8.2)。
問題排查過程
收到該一線同事的反饋,我們已經初步懷疑是 IPVS 的問題。根據以往網絡問題排查的經驗,先對現場做了些常規排查:
- 確認內核模塊 ip_tables 是否加載(正常)
- 確認 iptable forward 是否默認 accpet (正常)
- 確認宿主機網絡是否正常(正常)
- 確認容器網絡是否正常(正常)
- ……
排除了常規的問題之后,基本可以縮小范圍,再繼續基于 IPVS 相關層面進行排查。
通過 ipvsadm 命令排查
10.96.0.1 是客戶集群 Kubernetes master service vip。
可以發現有異常連接,處于 SYN_RECV 的狀態,并且可以觀察到,啟動時 kubelet + kube-proxy 是有正常建連的,說明是在啟動之后,Kubernetes service 網絡出現異常。
tcpdump 抓包分析
兩端進行抓包,并通過 telnet 10.96.0.1 443 命令進行確認。
結論:發現 SYN 包在本機沒有發送出去。
初步總結
通過上面的排查,可以再次縮小范圍,問題基本就在 kube-proxy 身上。我們采用了 IPVS 模式,也會依賴了 iptables 配置實現一些網絡的轉發、SNAT、drop 等等。
根據上面的排查過程,我們又縮小了范圍,開始分析懷疑對象 kube-proxy。
查看 kube-proxy 日志
發現異常日志,iptables-restore 命令執行異常。通過 Google、社區查看,確認問題。
繼續深入
通過代碼查看(1.13.10 版本 pkg/proxy/ipvs/proxier.go:1427),可以發現該版本確實沒有判斷 KUBE-MARK-DROP 是否存在并創建的邏輯。當出現該鏈不存在時,會出現邏輯缺陷,導致 iptable 命令執行失敗。
Kubernetes master service vip 不通,但是實際容器相關的 IP 是通的原因,與下面這條 iptable 規則有關:
- iptable -t nat -A KUBE-SERVICES ! -s 9.0.0.0/8 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
根因探究
前面我們已經知道了 kube-proxy 1.13.10 版本存在缺陷,在沒有創建 KUBE-MARK-DROP 鏈的情況下,執行 iptables-restore 命令配置規則。但是為何 Kubernetes 1.13.10 版本跑在 CentOS 8.2 4.18 內核的操作系統上會報錯,跑在 CentOS 7.6 3.10 內核的操作系統上卻正常呢?
查看下 kube-proxy 的源碼,可以發現 kube-proxy 其實也就是執行 iptables 命令進行規則配置。那既然 kube-proxy 報錯 iptables-restore 命令失敗,我們就找一臺 4.18 內核的機器,進入 kube-proxy 容器看下情況。
到容器內執行下 iptables-save 命令,可以發現 kube-proxy 容器內確實沒有創建 KUBE-MARK-DROP 鏈(符合代碼預期)。繼續在宿主機上執行下 iptables-save 命令,卻發現是有 KUBE-MARK-DROP 鏈。
這里有兩個疑問:
- 為何 4.18 內核宿主機的 iptables 有 KUBE-MARK-DROP 鏈?
- 為何 4.18 內核宿主機的 iptables 規則和 kube-proxy 容器內的規則不一致?
第一個疑惑,憑感覺懷疑除了 kube-proxy,還會有別的程序在操作 iptables,繼續擼下 Kubernetes 代碼。
結論:發現確實除了 kube-proxy,還有 kubelet 也會修改 iptables 規則。具體代碼可以查看 pkg/kubelet/kubelet_network_linux.go
第二個疑惑,繼續憑感覺吧。Google 一發撈一下為何 kube-proxy 容器掛載了宿主機 /run/xtables.lock 文件的情況下,宿主機和容器 iptables 查看的規則不一致。
結論:CentOS 8 在網絡方面摒棄 iptables 采用 nftables 框架作為默認的網絡包過濾工具。
至此,所有的謎團都解開了。
團隊完成過大量的客戶項目交付,還是有些問題可以再解答下:
- 問題一:為何這么多客戶環境第一次碰到該情況?因為需要 Kubernetes 1.13.10 + centos 8.2 的操作系統,這個組合罕見,且問題是必現。升級 Kubernetes 1.16.0+ 就不會有該問題。
- 問題二:為何使用 Kubernetes 1.13.10 + 5.5 內核卻沒有該問題?
因為那是與 CentOS 8 操作系統有關,我們手動升級 5.5 版本后,默認還是使用的 iptables 框架。
可以通過 iptables -v 命令來確認,是否使用了 nftables。
nftables 是何方神圣?比 iptables 好么?這是另一個值得進一步學習的點,這里就不再深入了。
解決方法
針對以上的排查問題,我們總結下解決方法:
- 調整內核版本到 3.10(CentOS 7.6+),或者手動升級內核版本到 5.0 +;
- 升級 Kubernetes 版本,當前確認 1.16.10+ 版本沒有該問題。