1. 程式人生 > 其它 >K8s為啥要啟用bridge-nf-call-iptables核心引數?用案例給你講明白!

K8s為啥要啟用bridge-nf-call-iptables核心引數?用案例給你講明白!

點選“終碼一生”,關注,置頂公眾號

每日技術乾貨,第一時間送達!

使用 kubernetes 遇到最多的 70%問題都可以歸於網路問題,最近發現如果核心引數: bridge-nf-call-iptables設定不當的話會影響 kubernetes 中 Node 節點上的 Pod 通過 ClusterIP 去訪問同 Node上的其它 pod 時會有超時現象,覆盤記錄一下排查的前因後因。

1、問題現象

叢集環境為 K8s v1.15.9,cni 指定了 flannel-vxlan 跟 portmap, kube-proxy 使用 mode 為 ipvs

問題現象是,某個 Node 節點上的 pod 通過 service 訪問其它服務時,有些能通,有些不通,不通的都提示 timeout

異常的請求:

$ curl -v http://panorama-v2-frontend-service.spring-prod.svc.cluster.local:8080
* Rebuilt URLto: http://panorama-v2-frontend-service.spring-prod.svc.cluster.local:8080/
* Trying10.233.53.172...
* TCP_NODELAYset
# 這裡一直等待直到超時

這個節點上的很多 pod 都存在這個問題, 其它節點未發現異常.

正常請求:

$curl -v http://panorama-v2-frontend-service.spring-prod.svc.cluster.local:8080
* Rebuilt URL to: http://panorama-v2-frontend-service.spring-prod.svc.cluster.local:8080/
* Trying 10.233.53.172...
* TCP_NODELAY set
* Connected to panorama-v2-frontend-service.spring-prod.svc.cluster.local (10.233.53.172) port 8080 (#0)
>GET / HTTP/1.1
>Host: panorama-v2-frontend-service.spring-prod.svc.cluster.local:8080
>User-Agent: curl/7.52.1
>Accept: */*
>
< HTTP/1.1 200 OK

2、排查過程

先對異常 Node 進行排查,因為其它節點是正常的,通過對異常 Node 的網路、kube-proxy、 iptables、ipvs 規則、kubelet 等排查後未發現有可疑的地方,就暫時排除嫌疑了。

第二個可疑的是 DNS, coreDNS 負責對 service 解析成 ClusterIP, 在出問題的 Node 上經過多次測試均能正確解析,coreDNS 排除嫌疑。

可疑的地方都篩了一遍,無果, 那就只能再從現象來發現看看有沒有相同點。

首先,訪問路徑為:Service– >ClusterIP– >PodIP

對 service 訪問異常,coreDNS 已經被排除了,那先繞過 service,直接使用 ClusterIP 訪問呢? 測試後現象依舊。另外關注公號“終碼一生”,回覆關鍵詞“資料”,獲取視訊教程和最新的面試資料!

那再繞過 ClusterIP,直接使用 PodIP 呢? Bingo,之前會出問題的訪問都是正常的了。

那麼問題就出在 CluterIP – > PodIP 上, 那麼又有以下可能:

  • ClusterIP 沒有正確轉發到 PodIP 上可能導致超時

  • 如果正確轉發,響應沒有返回也可能導致超時

第一種可能性很容易排查,之前已經確認了 ipvs 規則、iptables 規則都是沒有問題的,且通過 ClusterIP 發起的請求可以到達 PodIP 上, 基本就排除了可能性一

另外,對比正常跟異常請求會發現,異常的請求原 Pod 跟目標 pod 都是在同一個 Node 上,而正常的請求則處於不同的 Node,會是這個影響嗎?

上面的可能性二,只能祭出抓包神器了tcpdump, 通過抓包發現(抓包過程見文未)會發現請求中出現了Reset

那麼問題轉換一下: 為什麼相同 Node 上 podA 通過 service/ClusterIP 訪問 PodB 響應會不返回呢,而通過 PodIP 訪問就沒問題?

補充一句就是,相同 Node 上的 pod 相互訪問是不需要經過 Flannel 的,因此 Flannel 可以排除嫌疑

so, 問題在哪?

回到 tcpdump 的抓包資料, 可以發現,響應的資料沒有按照請求的路徑返回,嗯,Interesting

3、罪魁禍首

不管是 iptables 還是 ipvs 模式,Kubernetes 中訪問 service 都會進行 DNAT,將原本訪問 ClusterIP:Port 的資料包 DNAT 成 service 的某個 Endpoint (PodIP:Port),然後核心將連線資訊插入 conntrack 表以記錄連線,目的端回包的時候核心從 conntrack 表匹配連線並SNAT,這樣原路返回形成一個完整的連線鏈路.

從 tcpdump 看到請求被 reset 了, 沒錯,bridge-nf-call-iptables(如果是 ipv6 的話則是net.bridge.bridge-nf-call-ip6tables)引數

但是不對,這個引數 linux 預設開啟的呢?難道是有人修改了麼?

使用命令檢視該引數是否開啟:

$cat /proc/sys/net/bridge/bridge-nf-call-iptables
#0

返回 0,說明確實沒有開啟(後來被證實是被同事修改了),那這個引數是如何影響的返回路徑的呢?

那就不得不說linux bridge了!

雖然 CNI 使用的是 flannel, 但 flannel 封裝的也是 linux bridge,linux bridge 是虛擬的二層轉發裝置,而 iptables conntrack 是在三層上,所以如果直接訪問同一網橋內的地址(ip 同一網段),就會直接走二層轉發,不經過 conntrack:

結合上面的圖來看,同 Node 通過 service 訪問 pod 的訪問路徑如下:

  • PodA 訪問 service, 經過 coreDNS 解析成 Cluster IP,不是網橋內的地址(ClusterIP 一般跟 PodIP 不在一個網段),走 Conntrack,進行 DNAT,將 ClusterIP 轉換成 PodIP:Port

  • DNAT 後發現是要轉發到了同節點上的 PodB,PodB 回包時發現目的 IP(此時是 PodA 的 IP) 在同一網橋上(PodA 與 PodB 的 IP 段一致),就直接走二層轉發了,不會去調 conntrack,這樣就導致回包時沒有原路返回

沒有返回包就導致請求方一直等直到超時退出.

這樣也解釋了為何訪問在其它節點的應用的 ClusterIP 沒有問題,因為目標 PodIP 與源 PodIP 不在同一個網段上,肯定要走 conntrack.

4、問題解決

總述,開啟引數後問題解決

$echo"net.bridge.bridge-nf-call-iptables=1">> /etc/sysctl.conf
$echo"net.bridge.bridge-nf-call-ip6tables=1">> /etc/sysctl.conf

$sysctl -p /etc/sysctl.conf

5、linux conntrack

關於 conntrack 其實也是個值得好好研究一番的知識點, 各個發行版都有工具可以看到 conntrack 裡的記錄,格式如下:

$conntrack -L

tcp 6 119 SYN_SENT src=10.224.1.34 dst=10.233.53.172 sport=56916 dport=8080 [UNREPLIED] src=10.224.1.56 dst=10.224.1.34 sport=8080 dport=56916 mark=0 use=1

那個著名的DNS 5s timeout[1]的問題就跟 conntrack 機制有關,由於篇幅有限,就不在這裡展開.

6、tcpdump

在容器中的抓包命令

$ tcpdump -vvv host10.224.1.34or10.233.53.172or10.224.1.56

其中的三個 ip 分別對應 podA IP, podB 的 ClusterIP, podB 的 PodIP

這裡由於篇幅的關係,只儲存有關鍵資訊,同時使用註釋是作者加入的,方便理解.

對於異常請求的 tcpdump,如下:

#podA請求PodB
panorama-frontend-deploy-c8f6fd4b6-52tvf.45954>panorama-v2-frontend-service.spring-prod.svc.cluster.local.8080:Flags[S],cksum0x4cc5(incorrect-> 0xba1b),seq1108986852,win28200,options[mss 1410,sackOK,TS val 1345430037 ecr 0,nop,wscale 7],length0
# 10-224-1-56是PodB的podIP, 這裡省略瞭解析過程,可以看到返回資料給PodA
10-224-1-56.panorama-v2-frontend-service.spring-prod.svc.cluster.local.8080>panorama-frontend-deploy-c8f6fd4b6-52tvf.45954:Flags[S.],cksum0x1848(incorrect-> 0x99ac),seq3860576650,ack1108986853,win27960,options[mss 1410,sackOK,TS val 2444502128 ecr 1345430037,nop,wscale 7],length0
# 重點:podA直接reset了請求.
panorama-frontend-deploy-c8f6fd4b6-52tvf.45954> 10-224-1-56.panorama-v2-frontend-service.spring-prod.svc.cluster.local.8080:Flags[R],cksum0xb6b5(correct),seq1108986853,win0,length0

最後會發現 PodA 給 PodB 傳送了個R Flags, 也就是reset, 就是因為當 PodB 返回握手確認給到 PodA,PodA 根本不認識這個請求,所以直接給 reset 掉了, 三手握手都沒有建立,this is why!

而對於net.bridge.bridge-nf-call-iptables=1的正常請求的 tcpdump 如下:

# 能看到正常的三次握手, 這裡省略
# 開啟傳輸資料
panorama-frontend-deploy-c8f6fd4b6-52tvf.36434> panorama-v2-frontend-service.spring-prod.svc.cluster.local.8080: Flags [P.], cksum0x4d3c (incorrect ->0x6f84), seq1:128, ack1, win221,options[nop,nop,TS val1346139372ecr2445211463], length127: HTTP, length:127
GET / HTTP/1.1
Host:panorama-v2-frontend-service.spring-prod.svc.cluster.local:8080
User-Agent:curl/7.52.1
Accept:*/*

panorama-v2-frontend-service.spring-prod.svc.cluster.local.8080> panorama-frontend-deploy-c8f6fd4b6-52tvf.36434: Flags [.], cksum0x4cbd (incorrect ->0xe8a6), seq1, ack128, win219,options[nop,nop,TS val2445211463ecr1346139372], length0

panorama-v2-frontend-service.spring-prod.svc.cluster.local.8080> panorama-frontend-deploy-c8f6fd4b6-52tvf.36434: Flags [P.], cksum0x4dac (incorrect ->0x0421), seq1:240, ack128, win219,options[nop,nop,TS val2445211463ecr1346139372], length239: HTTP, length:239
HTTP/1.1200OK
Server: nginx/1.17.1
Date: Wed,18Aug202115:10:17GMT
Content-Type: text/html
Content-Length:1540
Last-Modified: Fri,09Jul202106:36:53GMT
Connection: keep-alive
ETag:"60e7ee85-604"
Accept-Ranges:bytes

相信這個請求路徑還是很清晰的,就不再囉嗦.

7、結語

禁用net.bridge.bridge-nf-call-ip6tables這個引數當然也有好外,那就是考慮同網段的 IP 訪問沒必要走 conntrack,一定程度有助於效能。

kubernetes 的官方文件[2]中明確提及 Node 節點上需要開啟這個引數,不然碰到各種詭異的現象也只是時間問題,所以還是不要隨意調整。

以防後患的話可以對該引數是否開啟進行監控,防止被人誤修改。

PS:防止找不到本篇文章,可以收藏點贊,方便翻閱查詢哦。