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

巨鹿網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應式網(wǎng)站等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)建站從2013年成立到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。
就在不久前我也遇到了類似的問題,看似是玄學事件,剛開始歸結(jié)于網(wǎng)絡(luò)鏈路抖動,一段時間后依然存在,雖然影響都是 P99.99 以后的數(shù)據(jù),但是擾人心智,最后通過多方面定位,解決了該問題。最后發(fā)現(xiàn)跟業(yè)務(wù)、網(wǎng)絡(luò)都沒有什么關(guān)系,而是基礎(chǔ)設(shè)施自身出了問題,如下文給了一個具體排查方案,并從一定程度上解釋了容器、cgroup、CPU 會給網(wǎng)絡(luò)延遲帶來怎樣的影響。
隨著 Kubernetes 集群規(guī)模不斷增長,我們對于服務(wù)延遲的要求越來越嚴苛。我們開始觀察到一些運行在我們 Kubernetes 平臺上的服務(wù)正在面臨偶發(fā)的延遲問題,這些斷斷續(xù)續(xù)的問題并不是由于應用本身的性能問題導致的。
我們發(fā)現(xiàn),Kubernetes 集群上的應用產(chǎn)生的延遲問題看上去似乎是隨機的,對于某些網(wǎng)絡(luò)連接的建立可能會超過 100ms,從而使得下游的服務(wù)產(chǎn)生超時或者重試。這些服務(wù)本身處理業(yè)務(wù)的響應時間都能夠很好地保持在 100ms 以內(nèi),而建立連接就需要花費 100ms 以上對我們來說是不可忍受的。另外,我們也發(fā)現(xiàn)對于一些應該執(zhí)行非常快的 SQL 查詢(毫秒量級),從應用的角度看居然超過 100ms,但是在 MySQL 數(shù)據(jù)庫的角度看又是完全正常的,并沒有發(fā)現(xiàn)可能出現(xiàn)的慢查詢問題。
通過排查,我們將問題縮小到與 Kubernetes 節(jié)點建立連接的這個環(huán)節(jié),包括集群內(nèi)部的請求或者是涉及到外部的資源和外部的訪問者的請求。最簡單的重現(xiàn)這個問題的方法是:在任意的內(nèi)部節(jié)點使用 Vegeta 對一個以 NodePort 暴露的服務(wù)發(fā)起 HTTP 壓測,我們就能觀察到不時會產(chǎn)生一些高延遲請求。在這篇文章中,我們將聊一聊我們是如何追蹤定位到這個問題的。
撥開迷霧找到問題的關(guān)鍵
我們想用一個簡單的例子來復現(xiàn)問題,那么我們希望能夠把問題的范圍縮小,并移除不必要的復雜度。起初,數(shù)據(jù)在 Vegeta 和 Kubernetes Pods 之間的流轉(zhuǎn)的過程中涉及了太多的組件,很難確定這是不是一個更深層次的網(wǎng)絡(luò)問題,所以我們需要來做一個減法。
Vegeta 客戶端會向集群中的某個 Kube 節(jié)點發(fā)起 TCP 請求。在我們的數(shù)據(jù)中心的 Kubernetes 集群使用 Overlay 網(wǎng)絡(luò)(運行在我們已有的數(shù)據(jù)中心網(wǎng)絡(luò)之上),會把 Overlay 網(wǎng)絡(luò)的 IP 包封裝在數(shù)據(jù)中心的 IP 包內(nèi)。當請求抵達第一個 kube 節(jié)點,它會進行 NAT 轉(zhuǎn)換,從而把 kube 節(jié)點的 IP 和端口轉(zhuǎn)換成 Overlay 的網(wǎng)絡(luò)地址,具體來說就是運行著應用的 Pod 的 IP 和端口。在請求響應的時候,則會發(fā)生相應的逆變換(SNAT/DNAT)。這是一個非常復雜的系統(tǒng),其中維持著大量可變的狀態(tài),會隨著服務(wù)的部署而不斷更新。
在最開始利用 Vegeta 進行進行壓測的時候,我們發(fā)現(xiàn)在 TCP 握手的階段(SYN 和 SYN-ACK 之間)存在延遲。為了簡化 HTTP 和 Vegeta 帶來的復雜度,我們使用 hping3 來發(fā)送 SYN 包,并觀測響應的包是否存在延遲的情況,然后把連接關(guān)閉。我們能夠過濾出那些延遲超過 100ms 的包,來簡單地重現(xiàn) Vegeta 的 7 層壓力測試或是模擬一個服務(wù)暴露在 SYN 攻擊中。以下的一段日志顯示的是以 10ms 間隔向 kube-node 的 30927 端口發(fā)送 TCP SYN/SYN-ACK 包并過濾出慢請求的結(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ù)日志中的序列號以及時間,我們首先觀察到的是這種延遲并不是單次偶發(fā)的,而是經(jīng)常聚集出現(xiàn),就好像把積壓的請求最后一次性處理完似的。
接著,我們想要具體定位到是哪個組件有可能發(fā)生了異常。是 kube-proxy 的 NAT 規(guī)則嗎,畢竟它們有幾百行之多?還是 IPIP 隧道或類似的網(wǎng)絡(luò)組件的性能比較差?排查的一種方式是去測試系統(tǒng)中的每一個步驟。如果我們把 NAT 規(guī)則和防火墻邏輯刪除,僅僅使用 IPIP 隧道會發(fā)生什么?
如果你同樣也在一個 kube 節(jié)點上,那么 Linux 允許你直接和 Pod 進行通訊,非常簡單:
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é)果看到,問題還是在那里!這排除了 iptables 以及 NAT 的問題。那是不是 TCP 出了問題?我們來看下如果我們用 ICMP 請求會發(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 仍然能夠復現(xiàn)問題。那是不是 IPIP 隧道導致了問題?讓我們來進一步簡化問題。
那么有沒有可能這些節(jié)點之間任意的通訊都會帶來這個問題?
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在這個復雜性的背后,簡單來說其實就是兩個 kube 節(jié)點之間的任何網(wǎng)絡(luò)通訊,包括 ICMP。如果這個目標節(jié)點是“異常的”(某些節(jié)點會比另一些更糟糕,比如延遲更高,問題出現(xiàn)的頻率更高),那么當問題發(fā)生時,我們?nèi)匀荒芸吹筋愃频难舆t。
那么現(xiàn)在的問題是,我們顯然沒有在所有的機器上發(fā)現(xiàn)這個問題,為什么這個問題只出現(xiàn)在那些 kube 節(jié)點的服務(wù)器上?是在 kube 節(jié)點作為請求發(fā)送方還是請求接收方時會出現(xiàn)呢?幸運的是,我們能夠輕易地把問題的范圍縮小:我們可以用一臺集群外的機器作為發(fā)送方,而使用相同的“已知故障”的機器作為請求的目標。我們發(fā)現(xià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然后重復以上操作,這次我們從 kube 節(jié)點發(fā)送請求到外部節(jié)點。
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
通過查看抓包中的延遲數(shù)據(jù), 我們獲得了更多的信息。具體來說,從發(fā)送端觀察到了延遲(下圖),然而接收端的服務(wù)器沒有看到延遲(上圖)——注意圖中的 Delta 列(單位是秒):
另外,通過查看接收端的 TCP 以及 ICMP 網(wǎng)絡(luò)包的順序的區(qū)別(基于序列 ID), 我們發(fā)現(xiàn) ICMP 包總是按照他們發(fā)送的順序抵達接收端,但是送達時間不規(guī)律,而 TCP 包的序列 ID 有時會交錯,其中的一部分會停頓。尤其是,如果你去數(shù) SYN 包發(fā)送/接收的端口,這些端口在接收端并不是順序的,而他們在發(fā)送端是有序的。
目前我們服務(wù)器所使用的網(wǎng)卡,比如我們在自己的數(shù)據(jù)中心里面使用的那些硬件,在處理 TCP 和 ICMP 網(wǎng)絡(luò)報文時有一些微妙的區(qū)別。當一個數(shù)據(jù)報抵達的時候,網(wǎng)卡會對每個連接上傳遞的報文進行哈希,并且試圖將不同的連接分配給不同的接收隊列,并為每個隊列(大概)分配一個 CPU 核心。對于 TCP 報文來說,這個哈希值同時包含了源 IP、端口和目標 IP、端口。換而言之,每個連接的哈希值都很有可能是不同的。對于 ICMP 包,哈希值僅包含源 IP 和目標 IP,因為沒有端口之說。這也就解釋了上面的那個發(fā)現(xiàn)。
另一個新的發(fā)現(xiàn)是一段時間內(nèi)兩臺主機之間的 ICMP 包都發(fā)現(xiàn)了停頓,然而在同一段時間內(nèi) TCP 包卻沒有問題。這似乎在告訴我們,是接收的網(wǎng)卡隊列的哈希在“開玩笑”,我們幾乎確定停頓是發(fā)生在接收端處理 RX 包的過程中,而不是發(fā)送端的問題。
這排除了 kube 節(jié)點之間的傳輸問題,所以我們現(xiàn)在知道了這是在處理包的階段發(fā)生了停頓,并且是一些作為接收端的 kube 節(jié)點。
深入挖掘 Linux 內(nèi)核的網(wǎng)絡(luò)包處理過程
為了理解為什么問題會出現(xiàn)在 kube 節(jié)點服務(wù)的接收端,我們來看下 Linux 是如何處理網(wǎng)絡(luò)包的。
在最簡單原始的實現(xiàn)中,網(wǎng)卡接收到一個網(wǎng)絡(luò)包以后會向 Linux 內(nèi)核發(fā)送一個中斷,告知有一個網(wǎng)絡(luò)包需要被處理。內(nèi)核會停下它當前正在進行的其他工作,將上下文切換到中斷處理器,處理網(wǎng)絡(luò)報文然后再切換回到之前的工作任務(wù)。
上下文切換會非常慢,對于上世紀 90 年代 10Mbit 的網(wǎng)卡可能這個方式?jīng)]什么問題,但現(xiàn)在許多服務(wù)器都是萬兆網(wǎng)卡,最大的包處理速度可能能夠達到 1500 萬包每秒:在一個小型的 8 核心服務(wù)器上這意味著每秒會產(chǎn)生數(shù)以百萬計的中斷。
許多年前,Linux 新增了一個 NAPI,Networking API 用于代替過去的傳統(tǒng)方式,現(xiàn)代的網(wǎng)卡驅(qū)動使用這個新的 API 可以顯著提升高速率下包處理的性能。在低速率下,內(nèi)核仍然按照如前所述的方式從網(wǎng)卡接受中斷。一旦有超過閾值的包抵達,內(nèi)核便會禁用中斷,然后開始輪詢網(wǎng)卡,通過批處理的方式來抓取網(wǎng)絡(luò)包。這個過程是在“softirq”中完成的,或者也可以稱為軟件中斷上下文(software interrupt context)。這發(fā)生在系統(tǒng)調(diào)用的最后階段,此時程序運行已經(jīng)進入到內(nèi)核空間,而不是在用戶空間。
這種方式比傳統(tǒng)的方式快得多,但也會帶來另一個問題。如果包的數(shù)量特別大,以至于我們將所有的 CPU 時間花費在處理從網(wǎng)卡中收到的包,但這樣我們就無法讓用戶態(tài)的程序去實際處理這些處于隊列中的網(wǎng)絡(luò)請求(比如從 TCP 連接中獲取數(shù)據(jù)等)。最終,隊列會堆滿,我們會開始丟棄包。為了權(quán)衡用戶態(tài)和內(nèi)核態(tài)運行的時間,內(nèi)核會限制給定軟件中斷上下文處理包的數(shù)量,安排一個“預算”。一旦超過這個"預算"值,它會喚醒另一個線程,稱為“ksoftiqrd”(或者你會在 ps 命令中看到過這個線程),它會在正常的系統(tǒng)調(diào)用路徑之外繼續(xù)處理這些軟件中斷上下文。這個線程會使用標準的進程調(diào)度器,從而能夠?qū)崿F(xiàn)公平的調(diào)度。
通過整理 Linux 內(nèi)核處理網(wǎng)絡(luò)包的路徑,我們發(fā)現(xiàn)這個處理過程確實有可能發(fā)生停頓。如果 softirq 處理調(diào)用之間的間隔變長,那么網(wǎng)絡(luò)包就有可能處于網(wǎng)卡的 RX 隊列中一段時間。這有可能是由于 CPU 核心死鎖或是有一些處理較慢的任務(wù)阻塞了內(nèi)核去處理 softirqs。
將問題縮小到某個核心或者方法
到目前為止,我們相信這個延遲確實是有可能發(fā)生的,并且我們也知道我們似乎觀察到一些非常類似的跡象。下一步是需要確認這個理論,并嘗試去理解是什么原因?qū)е碌膯栴}。
讓我們再來看一下發(fā)生問題的網(wǎng)絡(luò)請求:
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文章題目:如何調(diào)試Kubernetes集群中的網(wǎng)絡(luò)延遲問題
分享地址:http://www.5511xx.com/article/djpcjch.html


咨詢
建站咨詢
