困惑的源地址
Cloud Native
pod 創(chuàng)建后一段時間一直是正常運行,突然有一天發(fā)現(xiàn)沒有新的連接創(chuàng)建了,業(yè)務(wù)上是通過 pod A 訪問 svc B 的 svc name 的方式,進入 pod 手動去 wget 一下,發(fā)現(xiàn)報錯了 Address not available,為何會報錯這個呢?
大概示例圖如下:
為什么會出現(xiàn) Address not available,是什么地址不可用,查了很多資料,根據(jù) POSIX(Portable Operating System Interface for UNIX)標(biāo)準(zhǔn)的錯誤定義中找到了相關(guān)的定義,同樣說的還不是很清楚。
錯誤代碼參考連接:[errno.3[1]]
EADDRNOTAVAIL Address not available (POSIX.1-2001).
容易被忽視的內(nèi)核參數(shù)
Cloud Native
通過 netstat -an 查看到連接 svc 的地址,其中 estab 狀態(tài)的連接數(shù),已經(jīng)到達了可用的隨機端口數(shù)量閾值,無法在新建連接了。
最后通過修改了內(nèi)核參數(shù)隨機端口 net.ipv4.ip_local_port_range 端口范圍才得以解決的。 我們可以知道 Linux 的內(nèi)核定義的隨機端口 32768 ~ 60999,可能在業(yè)務(wù)設(shè)計場景中,比較容易被忽略的,我們都知道,每一個 TCP 連接都是由四元組(源 IP,源端口,目的 IP,目的端口)構(gòu)成的,只要四元組中其中一個元組發(fā)生了變化,就可以創(chuàng)建一個 TCP 連接的。當(dāng)一個 POD 要訪問一個固定的目的 IP + 目的端口的時候,那么每一個 TCP 連接的變量就只剩下源端口是隨機的了,所以如果在需求就是需要創(chuàng)建大量長連接的話,要么就調(diào)大內(nèi)核隨機端口,要么就調(diào)整業(yè)務(wù)。
相關(guān)內(nèi)核參考連接:[ip-sysctl.txt[2]]
ip_local_port_range - 2 INTEGERS Defines the local port range that is used by TCP and UDP to choose the local port. The first number is the first, the second the last local port number. If possible, it is better these numbers have different parity (one even and one odd value). Must be greater than or equal to ip_unprivileged_port_start. The default values are 32768 and 60999 respectively.
同樣的問題還可能出現(xiàn)什么類型的報錯呢?
Cloud Native
手動調(diào)小了 net.ipv4.ip_local_port_range,之后進行復(fù)現(xiàn)。
同樣的問題,分別嘗試了 curl,nc,wget 命令,報錯都不一樣,這就犯難了。 難道就不能統(tǒng)一一下嗎?
curl: (7) Couldn't connect to server
nc: bind: Address in use
wget: can't connect to remote host (1.1.1.1): Address not available
那么就通過 strace 命令進程分析一下看看,跟蹤指定系統(tǒng)調(diào)用名稱 它們都會創(chuàng)建 socket(), 然后發(fā)現(xiàn) wget/curl 命令是通過 connect() 函數(shù),而 nc 命令先是是通過 bind() 函數(shù)調(diào)用, 如果報錯就不會繼續(xù)調(diào)用 connect() 函數(shù)了。
如圖,通過對 B/S 架構(gòu)的分析如下,connect() 是在客戶端創(chuàng)建 socket 后建立的。
引發(fā)思考
Cloud Native
為什么 wget/curl 同樣調(diào)用的是 connect() 函數(shù)報錯的,為何報錯還是不一樣的?
每一個客戶端程序都會有自定義的 errorcode,在同樣的 connect() 函數(shù)報錯后 ,wget 是直接輸出了 POSIX 標(biāo)準(zhǔn)的錯誤定義 Address not available,而 curl 會輸出自己的定義錯誤碼和對應(yīng)的提示信息 curl: (7) Couldn't connect to server,錯誤代碼是 7,curl 的報錯定義在 lib/strerror.c。
為什么 connect() 函數(shù)和 bind() 函數(shù)報錯不一樣?
函數(shù)不同,錯誤的定義也就不同,從 POSIX 標(biāo)準(zhǔn)的錯誤定義都能找到。
EADDRINUSE Address already in use (POSIX.1-2001). EADDRNOTAVAIL Address not available (POSIX.1-2001).
是不是所有情況下都是這樣輸出呢?
Cloud Native
那么直接找了一臺 Centos7.9 的系統(tǒng),安裝 curl 、wget、 nc 等工具,同樣改小端口范圍的情況下會出現(xiàn)如下報錯 Cannot assign requested address,從這里可以得知某些鏡像(alpine、busybox) 里,使用相同的命令工具對相同的情況下報錯會不同。因為這些鏡像里可能為了縮小整個鏡像大小,對于一些基礎(chǔ)命令都會選擇 busybox 工具箱(上面的 wget 和 nc 就來自于 busybox 工具箱里的,參考 busybox 文檔:Busybox Command Help[3])來使用,所以就造成在問題定位方面困擾了。
Linux 系統(tǒng)中用于包含與錯誤碼相關(guān)的定義:/usr/include/asm-generic/errno.h
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
容器環(huán)境下,端口配置最佳實踐
Cloud Native
可修改范圍
理論上來是 0~65535 都能使用, 但是 0~1023 是特權(quán)端口,已經(jīng)預(yù)留給一下標(biāo)準(zhǔn)服務(wù),如 HTTP:80,SSH:22 等,只能特權(quán)用戶使用,同時也避免未授權(quán)的用戶通過流量特征攻擊等所以建議端口調(diào)大的話可以將隨機端口范圍限制在 1024-65535 之間。
如何正確配置 Pod 源端口
普通 Pod 源端口修改方法
從 kubernetes 社區(qū)得知可以通過安全上下文修改 securityContext[4],還有可以通過 initContainers 容器給特權(quán)模式 mount -o remount rw /proc/sys 的方式修改,此修改方式只會在 pod 的網(wǎng)絡(luò)命名空間中生效。
securityContext
... securityContext: sysctls: - name: net.ipv4.ip_local_port_range value: 1024 65535
initContainers
initContainers: - command: - /bin/sh - '-c' - | sysctl -w net.core.somaxconn=65535 sysctl -w net.ipv4.ip_local_port_range="1024 65535" securityContext: privileged: true ...
hostnetwork 模式 pod 修改注意事項
1.22+ 集群以上就不建議修改 net.ipv4.ip_local_port_range,因為這會和 ServiceNodePortRange 產(chǎn)生沖突。 Kubernetes 的 ServiceNodePortRange 默認(rèn)是 30000~32767,Kubernetes 1.22 及以后的版本,去除了 kube-proxy 監(jiān)聽 NodePort 的邏輯,如果有監(jiān)聽的話,應(yīng)用程序在選用隨機端口的時候,會避開這些監(jiān)聽中的端口。如果 net.ipv4.ip_local_port_range 的范圍和 ServiceNodePortRange 存在重疊,由于去掉了監(jiān)聽 NodePort 的邏輯,應(yīng)用程序在選用隨機端口的時候就可能選中重疊部分,比如 30000~32767,在當(dāng) NodePort 與內(nèi)核 net.ipv4.ip_local_port_range 范圍有沖突的情況下,可能會導(dǎo)致偶發(fā)的 TCP 無法連接的情況,可能導(dǎo)致健康檢查失敗、業(yè)務(wù)訪問異常等問題。更多信息,請參見Kubernetes 社區(qū) PR[5]。
大量創(chuàng)建 svc 的時候減少創(chuàng)建監(jiān)聽的步驟只是提交 ipvs/iptables 規(guī)則,這樣可以優(yōu)化連接性能 。另一個就解決某些場景下出現(xiàn)大量的 CLOSE_WAIT 占用 TCP 連接等問題。在 1.22 版本之后就去掉了 PortOpener 邏輯。
kubernetes/pkg/proxy/iptables/proxier.go Line 1304 in f98f27b[6]
1304 proxier.openPort(lp, replacementPortsMap)
具體是如何沖突的呢? 測試環(huán)境是 k8s 1.22.10,kube-proxy 網(wǎng)絡(luò)模式 ipvs。以 kubelet 健康檢查為例,調(diào)整了節(jié)點的內(nèi)核參數(shù) net.ipv4.ip_local_port_range 為1 024~65535。
部署 tcpdump 抓包,抓到有健康檢查失敗的事件后,停止抓包。
看到 kubelet 是用節(jié)點 IP(192.168.66.27)+隨機端口 32582 向 pod 發(fā)起了 TCP 握手 podIP(192.168.66.65)+80,但是 pod 在 TCP 握手時回 SYN ACK 給 kubelet 的時候,目標(biāo)端口是 32582,卻一直在重傳。因為這個隨機端口剛好是某一個服務(wù)的nodeport,所以優(yōu)先被 IPVS 攔截給規(guī)則后端的服務(wù),但這個后端服務(wù) (192.168.66.9) 并沒有發(fā)起和 podIP(192.168.66.65)TCP 建連,所以后端服務(wù) (192.168.66.9) 直接是丟棄的。那么 kubelet 就不會收到 SYN ACK 回應(yīng),TCP 無法建聯(lián),所以導(dǎo)致健康檢查失敗。 這個報文看 kubelet 發(fā)起 TCP 握手,pod 回 syn ack 的時候一直重傳。
實際是發(fā)送到了 32582 這個 svc 的后端 pod 了,直接是丟棄。
增加前置判斷
所以 hostnework 可以加上一個判斷,通過 initContainers 容器修改的時候,如果 podIP 和 hostIP 不相等才修改 net.ipv4.ip_local_port_range 參數(shù),避免誤操作導(dǎo)致修改節(jié)點的內(nèi)核參數(shù)。
initContainers: - command: - /bin/sh - '-c' - | if [ "$POD_IP" != "$HOST_IP" ]; then mount -o remount rw /proc/sys sysctl -w net.ipv4.ip_local_port_range="1024 65535" fi env: - name: POD_IP valueFrom: fieldRef: apiVersion: v1 fieldPath: status.podIP - name: HOST_IP valueFrom: fieldRef: apiVersion: v1 fieldPath: status.hostIP securityContext: privileged: true ...
如何正確配置 NodePort 范圍
在 Kubernetes中,APIServer 提供了 ServiceNodePortRange 參數(shù)(命令行參數(shù) --service-node-port-range),該參數(shù)是用于限制 NodePort 或 LoadBalancer 類型的 Service 在節(jié)點上所監(jiān)聽的 NodePort 端口范圍,該參數(shù)默認(rèn)值為 30000~32767。在 ACK Pro 集群中,您可以通過自定義 Pro 集群的管控面參數(shù)修改該端口范圍。具體操作,請參見自定義 ACK Pro 集群的管控面參數(shù)[7]。
在修改 NodePort 端口范圍時必須十分謹(jǐn)慎。務(wù)必保證 NodePort 端口范圍與集群節(jié)點上 Linux 內(nèi)核提供的 net.ipv4.ip_local_port_range 參數(shù)中的端口范圍不沖突。該內(nèi)核參數(shù) ip_local_port_range 控制了 Linux 系統(tǒng)上任意應(yīng)用程序可以使用的本地端口號范圍。ip_local_port_range 的默認(rèn)值為 32768~60999,Nodeport 默認(rèn)值為 30000~32767。
ACK 集群在默認(rèn)配置情況下,ServiceNodePortRange 參數(shù)和 ip_local_port_range 參數(shù)不會產(chǎn)生沖突。如果您此前為了提升端口數(shù)量限制調(diào)整了這兩個參數(shù)中任意一個,導(dǎo)致兩者范圍出現(xiàn)重合,則可能會產(chǎn)生節(jié)點上的偶發(fā)網(wǎng)絡(luò)異常,嚴(yán)重時會導(dǎo)致業(yè)務(wù)健康檢查失敗、集群節(jié)點離線等。建議您恢復(fù)默認(rèn)值或同時調(diào)整兩個端口范圍到完全不重合。
調(diào)整端口范圍后,集群中可能存在部分 NodePort 或 LoadBalancer 類型的 Service 仍在使用 ip_local_port_range 參數(shù)端口范圍內(nèi)的端口作為 NodePort。此時您需要對這部分 Service 進行重新配置以避免沖突,可通過 kubectl edit
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1351瀏覽量
40159 -
容器
+關(guān)注
關(guān)注
0文章
491瀏覽量
22015 -
代碼
+關(guān)注
關(guān)注
30文章
4698瀏覽量
68100 -
Address
+關(guān)注
關(guān)注
0文章
6瀏覽量
7556
原文標(biāo)題:記一次容器環(huán)境下出現(xiàn)Address not available
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論