解決pod健康檢查問題
引自:Solving the mystery of pods health checks failures in Kubernetes。原文中的某些描述並不清晰,本文作了調整。
很早以前,環境中的pod有時候會遇到健康檢查失敗的問題,但並沒有什麼明顯表徵,且幾乎是立馬就會恢復。由於這種情況很少發生,且不會對業務造成影響,因此起初並沒有人關注該問題。
但後來發生的頻率越來越高,導致開發人員頻繁接收到deployment的健康告警。
第1步:檢視日誌
- Kubernetes worker的系統日誌 -- 無異常
- kubelet 日誌 -- 無異常
- Containerd 日誌 -- 無異常
- CNI 日誌 -- 無異常
- 檢查最近失敗的pod日誌 -- 無異常
透過檢查相關日誌,並沒有發現什麼異常
第2步:tcpdump
在抓取的流量中發現,當kubelet給pod傳送TCP SYN之後,pod會回覆SYN-ACK,但kubelet並沒有傳送TCP ACK。在一段時間的重試之後,Kubelet會建立起一條TCP會話,因此該問題是隨機發生的。
為以防萬一,我們檢查了TCP中的seq和ack序列號,並沒有發現問題。
此時懷疑worker可能存在問題:是不是Kubelet沒有處理接收到的報文?
第3步:ss
每秒呼叫一次"ss -natp"來檢視kubelet程式連線,此時發現失敗的連線卡在了SYN-SENT階段,說明kubelet並沒有接收到pod發來的SYN-ACK報文。
第4步:conntrack
使用conntrack檢視TCP網路連線跟蹤,發現有的連線卡在SYN-SENT狀態(kubelet側),有的連線卡在SYN-RECV(pod側),但連線的源埠號看起來都類似。
在我們的環境中,設定了一個較大的源埠可選範圍:
net.ipv4.ip_local_port_range=12000 65001
出現問題的源埠為30XXX或31XXX,非常類似。
第5步:ipvs
透過ipvsadm命令檢視ipvs配置發現,所有卡住的連線都使用了Kubernetes的nodeport 保留埠
根因分析
至此,問題已經明瞭。當Kubelet初始化一條TCP連線時,會隨機選擇一個源埠號,例如31055。當TCP SYN到達pod之後,pod會向31055埠號回覆一個TCP SYN-ACK報文。當該報文到達IPVS之後,由於已經存在一個埠號為31055的nodeport(Kubernetes loadbalance service),此時會將TCP SYN-ACK報文轉發到對應的後端(其他pod),這樣就導致Kubelet無法接收到回覆的報文,無法建立連線。
解決辦法
解決方式也很簡單,設定如下核心引數即可,這樣Kubelet在建立連線時就不會選擇30000–32768的埠作為TCP源埠:
net.ipv4.ip_local_reserved_ports="30000–32768"
Kubernetes的nodeport保留埠為30000-32767,因此設定的
net.ipv4.ip_local_reserved_ports
為30000–32768
TIPs
net.ipv4.ip_local_port_range
的預設值為32768 60999
,正好和Kubernetes的nodeport保留埠錯開,本文中描述的問題的源頭也是因為修改了該核心引數,因此非必要不要修改核心引數!