新聞中心
如何調(diào)試Kubernetes集群中的網(wǎng)絡(luò)延遲問(wèn)題
作者:Theo Julienne 2022-03-07 10:41:09
云計(jì)算
運(yùn)維 本文深入研究和解決了 Kubernetes 平臺(tái)上的服務(wù)零星延遲問(wèn)題。

巨鹿網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、響應(yīng)式網(wǎng)站等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站從2013年成立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專(zhuān)注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。
就在不久前我也遇到了類(lèi)似的問(wèn)題,看似是玄學(xué)事件,剛開(kāi)始?xì)w結(jié)于網(wǎng)絡(luò)鏈路抖動(dòng),一段時(shí)間后依然存在,雖然影響都是 P99.99 以后的數(shù)據(jù),但是擾人心智,最后通過(guò)多方面定位,解決了該問(wèn)題。最后發(fā)現(xiàn)跟業(yè)務(wù)、網(wǎng)絡(luò)都沒(méi)有什么關(guān)系,而是基礎(chǔ)設(shè)施自身出了問(wèn)題,如下文給了一個(gè)具體排查方案,并從一定程度上解釋了容器、cgroup、CPU 會(huì)給網(wǎng)絡(luò)延遲帶來(lái)怎樣的影響。
隨著 Kubernetes 集群規(guī)模不斷增長(zhǎng),我們對(duì)于服務(wù)延遲的要求越來(lái)越嚴(yán)苛。我們開(kāi)始觀察到一些運(yùn)行在我們 Kubernetes 平臺(tái)上的服務(wù)正在面臨偶發(fā)的延遲問(wèn)題,這些斷斷續(xù)續(xù)的問(wèn)題并不是由于應(yīng)用本身的性能問(wèn)題導(dǎo)致的。
我們發(fā)現(xiàn),Kubernetes 集群上的應(yīng)用產(chǎn)生的延遲問(wèn)題看上去似乎是隨機(jī)的,對(duì)于某些網(wǎng)絡(luò)連接的建立可能會(huì)超過(guò) 100ms,從而使得下游的服務(wù)產(chǎn)生超時(shí)或者重試。這些服務(wù)本身處理業(yè)務(wù)的響應(yīng)時(shí)間都能夠很好地保持在 100ms 以內(nèi),而建立連接就需要花費(fèi) 100ms 以上對(duì)我們來(lái)說(shuō)是不可忍受的。另外,我們也發(fā)現(xiàn)對(duì)于一些應(yīng)該執(zhí)行非??斓?SQL 查詢(毫秒量級(jí)),從應(yīng)用的角度看居然超過(guò) 100ms,但是在 MySQL 數(shù)據(jù)庫(kù)的角度看又是完全正常的,并沒(méi)有發(fā)現(xiàn)可能出現(xiàn)的慢查詢問(wèn)題。
通過(guò)排查,我們將問(wèn)題縮小到與 Kubernetes 節(jié)點(diǎn)建立連接的這個(gè)環(huán)節(jié),包括集群內(nèi)部的請(qǐng)求或者是涉及到外部的資源和外部的訪問(wèn)者的請(qǐng)求。最簡(jiǎn)單的重現(xiàn)這個(gè)問(wèn)題的方法是:在任意的內(nèi)部節(jié)點(diǎn)使用 Vegeta 對(duì)一個(gè)以 NodePort 暴露的服務(wù)發(fā)起 HTTP 壓測(cè),我們就能觀察到不時(shí)會(huì)產(chǎn)生一些高延遲請(qǐng)求。在這篇文章中,我們將聊一聊我們是如何追蹤定位到這個(gè)問(wèn)題的。
撥開(kāi)迷霧找到問(wèn)題的關(guān)鍵
我們想用一個(gè)簡(jiǎn)單的例子來(lái)復(fù)現(xiàn)問(wèn)題,那么我們希望能夠把問(wèn)題的范圍縮小,并移除不必要的復(fù)雜度。起初,數(shù)據(jù)在 Vegeta 和 Kubernetes Pods 之間的流轉(zhuǎn)的過(guò)程中涉及了太多的組件,很難確定這是不是一個(gè)更深層次的網(wǎng)絡(luò)問(wèn)題,所以我們需要來(lái)做一個(gè)減法。
Vegeta 客戶端會(huì)向集群中的某個(gè) Kube 節(jié)點(diǎn)發(fā)起 TCP 請(qǐng)求。在我們的數(shù)據(jù)中心的 Kubernetes 集群使用 Overlay 網(wǎng)絡(luò)(運(yùn)行在我們已有的數(shù)據(jù)中心網(wǎng)絡(luò)之上),會(huì)把 Overlay 網(wǎng)絡(luò)的 IP 包封裝在數(shù)據(jù)中心的 IP 包內(nèi)。當(dāng)請(qǐng)求抵達(dá)第一個(gè) kube 節(jié)點(diǎn),它會(huì)進(jìn)行 NAT 轉(zhuǎn)換,從而把 kube 節(jié)點(diǎn)的 IP 和端口轉(zhuǎn)換成 Overlay 的網(wǎng)絡(luò)地址,具體來(lái)說(shuō)就是運(yùn)行著應(yīng)用的 Pod 的 IP 和端口。在請(qǐng)求響應(yīng)的時(shí)候,則會(huì)發(fā)生相應(yīng)的逆變換(SNAT/DNAT)。這是一個(gè)非常復(fù)雜的系統(tǒng),其中維持著大量可變的狀態(tài),會(huì)隨著服務(wù)的部署而不斷更新。
在最開(kāi)始利用 Vegeta 進(jìn)行進(jìn)行壓測(cè)的時(shí)候,我們發(fā)現(xiàn)在 TCP 握手的階段(SYN 和 SYN-ACK 之間)存在延遲。為了簡(jiǎn)化 HTTP 和 Vegeta 帶來(lái)的復(fù)雜度,我們使用 hping3 來(lái)發(fā)送 SYN 包,并觀測(cè)響應(yīng)的包是否存在延遲的情況,然后把連接關(guān)閉。我們能夠過(guò)濾出那些延遲超過(guò) 100ms 的包,來(lái)簡(jiǎn)單地重現(xiàn) Vegeta 的 7 層壓力測(cè)試或是模擬一個(gè)服務(wù)暴露在 SYN 攻擊中。以下的一段日志顯示的是以 10ms 間隔向 kube-node 的 30927 端口發(fā)送 TCP SYN/SYN-ACK 包并過(guò)濾出慢請(qǐng)求的結(jié)果,
theojulienne@shell ~ $ sudo hping3 172.16.47.27 -S -p 30927 -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}\.'
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1485 win=29200 rtt=127.1 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1486 win=29200 rtt=117.0 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1487 win=29200 rtt=106.2 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1488 win=29200 rtt=104.1 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=5024 win=29200 rtt=109.2 ms
len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=5231 win=29200 rtt=109.2 ms根據(jù)日志中的序列號(hào)以及時(shí)間,我們首先觀察到的是這種延遲并不是單次偶發(fā)的,而是經(jīng)常聚集出現(xiàn),就好像把積壓的請(qǐng)求最后一次性處理完似的。
接著,我們想要具體定位到是哪個(gè)組件有可能發(fā)生了異常。是 kube-proxy 的 NAT 規(guī)則嗎,畢竟它們有幾百行之多?還是 IPIP 隧道或類(lèi)似的網(wǎng)絡(luò)組件的性能比較差?排查的一種方式是去測(cè)試系統(tǒng)中的每一個(gè)步驟。如果我們把 NAT 規(guī)則和防火墻邏輯刪除,僅僅使用 IPIP 隧道會(huì)發(fā)生什么?
如果你同樣也在一個(gè) kube 節(jié)點(diǎn)上,那么 Linux 允許你直接和 Pod 進(jìn)行通訊,非常簡(jiǎn)單:
theojulienne@kube-node-client ~ $ sudo hping3 10.125.20.64 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}\.'
len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7346 win=0 rtt=127.3 ms
len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7347 win=0 rtt=117.3 ms
len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7348 win=0 rtt=107.2 ms從我們的結(jié)果看到,問(wèn)題還是在那里!這排除了 iptables 以及 NAT 的問(wèn)題。那是不是 TCP 出了問(wèn)題?我們來(lái)看下如果我們用 ICMP 請(qǐng)求會(huì)發(fā)生什么。
theojulienne@kube-node-client ~ $ sudo hping3 10.125.20.64 --icmp -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}\.'
len=28 ip=10.125.20.64 ttl=64 id=42594 icmp_seq=104 rtt=110.0 ms
len=28 ip=10.125.20.64 ttl=64 id=49448 icmp_seq=4022 rtt=141.3 ms
len=28 ip=10.125.20.64 ttl=64 id=49449 icmp_seq=4023 rtt=131.3 ms
len=28 ip=10.125.20.64 ttl=64 id=49450 icmp_seq=4024 rtt=121.2 ms
len=28 ip=10.125.20.64 ttl=64 id=49451 icmp_seq=4025 rtt=111.2 ms
len=28 ip=10.125.20.64 ttl=64 id=49452 icmp_seq=4026 rtt=101.1 ms
len=28 ip=10.125.20.64 ttl=64 id=50023 icmp_seq=4343 rtt=126.8 ms
len=28 ip=10.125.20.64 ttl=64 id=50024 icmp_seq=4344 rtt=116.8 ms
len=28 ip=10.125.20.64 ttl=64 id=50025 icmp_seq=4345 rtt=106.8 ms
len=28 ip=10.125.20.64 ttl=64 id=59727 icmp_seq=9836 rtt=106.1 ms結(jié)果顯示 ICMP 仍然能夠復(fù)現(xiàn)問(wèn)題。那是不是 IPIP 隧道導(dǎo)致了問(wèn)題?讓我們來(lái)進(jìn)一步簡(jiǎn)化問(wèn)題。
那么有沒(méi)有可能這些節(jié)點(diǎn)之間任意的通訊都會(huì)帶來(lái)這個(gè)問(wèn)題?
theojulienne@kube-node-client ~ $ sudo hping3 172.16.47.27 --icmp -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}\.'
len=46 ip=172.16.47.27 ttl=61 id=41127 icmp_seq=12564 rtt=140.9 ms
len=46 ip=172.16.47.27 ttl=61 id=41128 icmp_seq=12565 rtt=130.9 ms
len=46 ip=172.16.47.27 ttl=61 id=41129 icmp_seq=12566 rtt=120.8 ms
len=46 ip=172.16.47.27 ttl=61 id=41130 icmp_seq=12567 rtt=110.8 ms
len=46 ip=172.16.47.27 ttl=61 id=41131 icmp_seq=12568 rtt=100.7 ms
len=46 ip=172.16.47.27 ttl=61 id=9062 icmp_seq=31443 rtt=134.2 ms
len=46 ip=172.16.47.27 ttl=61 id=9063 icmp_seq=31444 rtt=124.2 ms
len=46 ip=172.16.47.27 ttl=61 id=9064 icmp_seq=31445 rtt=114.2 ms
len=46 ip=172.16.47.27 ttl=61 id=9065 icmp_seq=31446 rtt=104.2 ms在這個(gè)復(fù)雜性的背后,簡(jiǎn)單來(lái)說(shuō)其實(shí)就是兩個(gè) kube 節(jié)點(diǎn)之間的任何網(wǎng)絡(luò)通訊,包括 ICMP。如果這個(gè)目標(biāo)節(jié)點(diǎn)是“異常的”(某些節(jié)點(diǎn)會(huì)比另一些更糟糕,比如延遲更高,問(wèn)題出現(xiàn)的頻率更高),那么當(dāng)問(wèn)題發(fā)生時(shí),我們?nèi)匀荒芸吹筋?lèi)似的延遲。
那么現(xiàn)在的問(wèn)題是,我們顯然沒(méi)有在所有的機(jī)器上發(fā)現(xiàn)這個(gè)問(wèn)題,為什么這個(gè)問(wèn)題只出現(xiàn)在那些 kube 節(jié)點(diǎn)的服務(wù)器上?是在 kube 節(jié)點(diǎn)作為請(qǐng)求發(fā)送方還是請(qǐng)求接收方時(shí)會(huì)出現(xiàn)呢?幸運(yùn)的是,我們能夠輕易地把問(wèn)題的范圍縮?。何覀兛梢杂靡慌_(tái)集群外的機(jī)器作為發(fā)送方,而使用相同的“已知故障”的機(jī)器作為請(qǐng)求的目標(biāo)。我們發(fā)現(xiàn)在這個(gè)方向上的請(qǐng)求仍然存在問(wèn)題。
theojulienne@shell ~ $ sudo hping3 172.16.47.27 -p 9876 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}\.'
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=312 win=0 rtt=108.5 ms
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=5903 win=0 rtt=119.4 ms
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=6227 win=0 rtt=139.9 ms
len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=7929 win=0 rtt=131.2 ms然后重復(fù)以上操作,這次我們從 kube 節(jié)點(diǎn)發(fā)送請(qǐng)求到外部節(jié)點(diǎn)。
theojulienne@kube-node-client ~ $ sudo hping3 172.16.33.44 -p 9876 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}\.'
^C
--- 172.16.33.44 hping statistic ---
22352 packets transmitted, 22350 packets received, 1% packet loss
round-trip min/avg/max = 0.2/7.6/1010.6 ms
通過(guò)查看抓包中的延遲數(shù)據(jù), 我們獲得了更多的信息。具體來(lái)說(shuō),從發(fā)送端觀察到了延遲(下圖),然而接收端的服務(wù)器沒(méi)有看到延遲(上圖)——注意圖中的 Delta 列(單位是秒):
另外,通過(guò)查看接收端的 TCP 以及 ICMP 網(wǎng)絡(luò)包的順序的區(qū)別(基于序列 ID), 我們發(fā)現(xiàn) ICMP 包總是按照他們發(fā)送的順序抵達(dá)接收端,但是送達(dá)時(shí)間不規(guī)律,而 TCP 包的序列 ID 有時(shí)會(huì)交錯(cuò),其中的一部分會(huì)停頓。尤其是,如果你去數(shù) SYN 包發(fā)送/接收的端口,這些端口在接收端并不是順序的,而他們?cè)诎l(fā)送端是有序的。
目前我們服務(wù)器所使用的網(wǎng)卡,比如我們?cè)谧约旱臄?shù)據(jù)中心里面使用的那些硬件,在處理 TCP 和 ICMP 網(wǎng)絡(luò)報(bào)文時(shí)有一些微妙的區(qū)別。當(dāng)一個(gè)數(shù)據(jù)報(bào)抵達(dá)的時(shí)候,網(wǎng)卡會(huì)對(duì)每個(gè)連接上傳遞的報(bào)文進(jìn)行哈希,并且試圖將不同的連接分配給不同的接收隊(duì)列,并為每個(gè)隊(duì)列(大概)分配一個(gè) CPU 核心。對(duì)于 TCP 報(bào)文來(lái)說(shuō),這個(gè)哈希值同時(shí)包含了源 IP、端口和目標(biāo) IP、端口。換而言之,每個(gè)連接的哈希值都很有可能是不同的。對(duì)于 ICMP 包,哈希值僅包含源 IP 和目標(biāo) IP,因?yàn)闆](méi)有端口之說(shuō)。這也就解釋了上面的那個(gè)發(fā)現(xiàn)。
另一個(gè)新的發(fā)現(xiàn)是一段時(shí)間內(nèi)兩臺(tái)主機(jī)之間的 ICMP 包都發(fā)現(xiàn)了停頓,然而在同一段時(shí)間內(nèi) TCP 包卻沒(méi)有問(wèn)題。這似乎在告訴我們,是接收的網(wǎng)卡隊(duì)列的哈希在“開(kāi)玩笑”,我們幾乎確定停頓是發(fā)生在接收端處理 RX 包的過(guò)程中,而不是發(fā)送端的問(wèn)題。
這排除了 kube 節(jié)點(diǎn)之間的傳輸問(wèn)題,所以我們現(xiàn)在知道了這是在處理包的階段發(fā)生了停頓,并且是一些作為接收端的 kube 節(jié)點(diǎn)。
深入挖掘 Linux 內(nèi)核的網(wǎng)絡(luò)包處理過(guò)程
為了理解為什么問(wèn)題會(huì)出現(xiàn)在 kube 節(jié)點(diǎn)服務(wù)的接收端,我們來(lái)看下 Linux 是如何處理網(wǎng)絡(luò)包的。
在最簡(jiǎn)單原始的實(shí)現(xiàn)中,網(wǎng)卡接收到一個(gè)網(wǎng)絡(luò)包以后會(huì)向 Linux 內(nèi)核發(fā)送一個(gè)中斷,告知有一個(gè)網(wǎng)絡(luò)包需要被處理。內(nèi)核會(huì)停下它當(dāng)前正在進(jìn)行的其他工作,將上下文切換到中斷處理器,處理網(wǎng)絡(luò)報(bào)文然后再切換回到之前的工作任務(wù)。
上下文切換會(huì)非常慢,對(duì)于上世紀(jì) 90 年代 10Mbit 的網(wǎng)卡可能這個(gè)方式?jīng)]什么問(wèn)題,但現(xiàn)在許多服務(wù)器都是萬(wàn)兆網(wǎng)卡,最大的包處理速度可能能夠達(dá)到 1500 萬(wàn)包每秒:在一個(gè)小型的 8 核心服務(wù)器上這意味著每秒會(huì)產(chǎn)生數(shù)以百萬(wàn)計(jì)的中斷。
許多年前,Linux 新增了一個(gè) NAPI,Networking API 用于代替過(guò)去的傳統(tǒng)方式,現(xiàn)代的網(wǎng)卡驅(qū)動(dòng)使用這個(gè)新的 API 可以顯著提升高速率下包處理的性能。在低速率下,內(nèi)核仍然按照如前所述的方式從網(wǎng)卡接受中斷。一旦有超過(guò)閾值的包抵達(dá),內(nèi)核便會(huì)禁用中斷,然后開(kāi)始輪詢網(wǎng)卡,通過(guò)批處理的方式來(lái)抓取網(wǎng)絡(luò)包。這個(gè)過(guò)程是在“softirq”中完成的,或者也可以稱(chēng)為軟件中斷上下文(software interrupt context)。這發(fā)生在系統(tǒng)調(diào)用的最后階段,此時(shí)程序運(yùn)行已經(jīng)進(jìn)入到內(nèi)核空間,而不是在用戶空間。
這種方式比傳統(tǒng)的方式快得多,但也會(huì)帶來(lái)另一個(gè)問(wèn)題。如果包的數(shù)量特別大,以至于我們將所有的 CPU 時(shí)間花費(fèi)在處理從網(wǎng)卡中收到的包,但這樣我們就無(wú)法讓用戶態(tài)的程序去實(shí)際處理這些處于隊(duì)列中的網(wǎng)絡(luò)請(qǐng)求(比如從 TCP 連接中獲取數(shù)據(jù)等)。最終,隊(duì)列會(huì)堆滿,我們會(huì)開(kāi)始丟棄包。為了權(quán)衡用戶態(tài)和內(nèi)核態(tài)運(yùn)行的時(shí)間,內(nèi)核會(huì)限制給定軟件中斷上下文處理包的數(shù)量,安排一個(gè)“預(yù)算”。一旦超過(guò)這個(gè)"預(yù)算"值,它會(huì)喚醒另一個(gè)線程,稱(chēng)為“ksoftiqrd”(或者你會(huì)在 ps 命令中看到過(guò)這個(gè)線程),它會(huì)在正常的系統(tǒng)調(diào)用路徑之外繼續(xù)處理這些軟件中斷上下文。這個(gè)線程會(huì)使用標(biāo)準(zhǔn)的進(jìn)程調(diào)度器,從而能夠?qū)崿F(xiàn)公平的調(diào)度。
通過(guò)整理 Linux 內(nèi)核處理網(wǎng)絡(luò)包的路徑,我們發(fā)現(xiàn)這個(gè)處理過(guò)程確實(shí)有可能發(fā)生停頓。如果 softirq 處理調(diào)用之間的間隔變長(zhǎng),那么網(wǎng)絡(luò)包就有可能處于網(wǎng)卡的 RX 隊(duì)列中一段時(shí)間。這有可能是由于 CPU 核心死鎖或是有一些處理較慢的任務(wù)阻塞了內(nèi)核去處理 softirqs。
將問(wèn)題縮小到某個(gè)核心或者方法
到目前為止,我們相信這個(gè)延遲確實(shí)是有可能發(fā)生的,并且我們也知道我們似乎觀察到一些非常類(lèi)似的跡象。下一步是需要確認(rèn)這個(gè)理論,并嘗試去理解是什么原因?qū)е碌膯?wèn)題。
讓我們?cè)賮?lái)看一下發(fā)生問(wèn)題的網(wǎng)絡(luò)請(qǐng)求:
len=46 ip=172.16.53.32 ttl=61 id=29573 icmp_seq=1953 rtt=99.3 ms
len=46 ip=172.16.53.32 ttl=61 id=29574 icmp_seq=1954 rtt=89.3 ms
len=46 ip=172.16.53.32 ttl=61 id=29575 icmp_seq=1955 rtt=79.2 ms
len=46 ip=172.16.53.32 ttl=61 id=29576 icmp_seq=1956 rtt=69.1 ms
len=46 ip=172.16.53.32 ttl=61 id=29577 icmp_seq=1957 rtt=59.1 ms當(dāng)前標(biāo)題:如何調(diào)試Kubernetes集群中的網(wǎng)絡(luò)延遲問(wèn)題
URL鏈接:http://fisionsoft.com.cn/article/djpcjch.html


咨詢
建站咨詢
