eBay雲計算“網”事:網絡超時篇

導讀
eBay自2016年開始將業務遷往Kubernetes容器平臺,其間遇到了各種網絡問題。雲計算“網”事系列旨在介紹 eBay IE Cloud團隊 巧妙利用eBPF工具進行排查,並解決這些典型網絡問題的事蹟。該系列分爲三篇: 網絡超時篇、網絡丟包篇及網絡重傳篇 。本篇主講網絡超時問題,希望能爲類似問題的定位和解決提供思路。

背景介紹

eBay從 2016年 開始將Kubernetes項目作爲下一代雲平臺方案。經過幾年的發展實踐,已經逐步完成各大主要業務往Kubernetes容器平臺上的遷移。在這過程中,碰到大大小小的問題數不甚數。 其中大規模應用場景下的偶發性問題,一直是這些問題中的關鍵難點。 隨機性,小概率發生,不容易復現,都給這類問題的現場捕捉和定位造成重重困難。定位和解決此類問題,一般需要數天甚至數週的時間。通過 eBPF方式 來對內核進行剖析,是目前業內逐步開始探索的一個定位此類問題的方式,而eBay也在定位諸多問題中,漸漸累積開發了針對某些此類問題的eBPF工具。

雲計算“網”事 系列文章,將從利用eBPF工具來解決eBay雲平臺碰到的幾個典型網絡問題入手,包含 網絡延時、網絡丟包、網絡重傳 三個方面,給出詳細的摸索和排查分析過程,希望能爲定位和解決相關問題提供思路。

應用運行在虛擬機或者物理機上,可以直接通過機器的網絡端口進行數據傳輸,而在Kubernetes的節點上,容器運行在獨立的network namespace,通過veth pair 接口來對外進行數據通信。一個常見的容器網絡配置如圖1所示:

圖1

外部來容器的數據,經過物理機網卡eth0接收後,會經過路由,到達運行在主機network namespace的cali端口,再通過veth pair送到容器network namespace的eth0端口。在這中間會經過iptables或者 ipvs 的處理。容器往外發生數據,是接收數據的反向路徑。

網絡超時篇

案例1

某個運行在Kubernetes集羣上的多實例應用,客戶端發起的請求偶爾會超時。從現象來看,往某些節點上的服務發起的請求,每個小時的超時數會比其他的節點多幾十次或者幾百次。該pod提供對後段數據庫的查詢,然後返回結果給用戶。客戶端的超時時間是 400ms

超時信息:

圖2

由圖2可見,有一個節點的超時次數比較高,而其他節點的超時次數一個小時內也有100次左右。從總體上來看,一個小時內的調用到達萬次以上,因此總體的timeout率不高。

針對這個問題,我們首先做了如下幾個排查工作:

  1. 因爲用戶彙報了自從某個時間段開始,發現timeout的數量增加,所以我們先查看了那個時間段,是否有什麼版本改動引起了問題,並排查了幾個關聯的修改,但沒有發現和此類問題有直接的聯繫。
  2. 檢查出問題機器的網絡配置是否正確。爲了減少數據包發送的亂序,提高性能,針對cali端口,使能了RPS(Receive Packet Steering)。檢查下來,該端口配置都沒有問題。
  3. 節點的CPU使用率和負載並不高。
  4. 檢查了出現超時問題比較多的節點及其相關metrics 的信息,沒有明顯的節點的丟包統計和重傳。從timeout的次數和總體的查詢次數來看,這個也符合預期。
  5. 通過qperf來檢測節點的tcp延時,並沒有看到明顯的延時問題。
  6. 使用tcpdump來抓取容器端口的數據並且同步觀測容器的出錯信息,當日志中打出出錯信息的時候,tcpdump上觀測到有數據重傳,如圖3所示。該連接的RTO在 200ms 左右,所以當鏈路有問題的時候,是很可能發生重傳的。

圖3
  1. 從其他的節點ping該節點,會有間斷的延時突發問題。

圖4

通過ping pod來複現問題,可以從圖4看到有延時突然增多的現象,那就基本可以確定,數據從鏈路到容器的tcp協議棧,已經出現延時突發的問題了。 通過ping物理機的網卡,可以看到延時突發的現象基本消失,因此主機側出問題的可能性比較大。

但是定位主機側的問題,面臨着很大的困難。

首先,在大規模的集羣上,節點的metrics很多都是以分鐘爲間隔來進行抓取的,很多時候並不能反應出問題時間段內發生了什麼。 假如因爲某個時間段內的系統負載突然增高導致了延時問題,從metrics上往往是看不到的。另外,metrics的精細化不夠。例如tcp的丟包和重傳,反映的是一個時間段內總的增長值,但如果問題是在很短的時間內發生的,那就很難說明兩者之間有必然的相關性。
其次,很難直接找到和延時相關的證據。 一般都是看到某些異常,從離散的點去懷疑,不斷修改驗證,最後觀察到問題沒有再出現,說明問題已經解決。這也是目前在衆多公衆號文章看到定位系統問題的普遍解決方式。 因爲Linux系統模塊之間錯綜複雜的聯繫和相互影響,導致很多時候解決問題只能用排除法。 如果排除了懷疑的點後,問題仍舊沒有改善,那就只能找新的點繼續排除。該方法並不能說是不好,但肯定不是最優的解決問題的方式。因爲就算解決了問題,很多時候還是無法找到引起問題的具體原因。
除此之外,利用tcpdump來處理這樣的問題,會比較困難 ,得在主機的eth0, cali端口,容器的eth0同時長時間抓包,之後再將抓到的數據進行合併分析,看看是哪一節點引入的延時。另外tcpdump本身的overhead,是否會導致延時的突發也是個未知數,而且也無法診斷是否是iptables/ipvs引起的延時。 還有tcpdump只能輸出基本的數據包信息,但是有關內核的信息在定位問題的時候還是不夠。 因此tcpdump只能作爲一個輔助工具,無法完全依賴。

在這樣的基礎上,我們只能逐步排查是哪個模塊引起的問題。思路是逐步停掉各種業務,然後進行ping測試,如果沒有出現突發情況,則說明跟停掉的模塊有很大的關係。

通過該方式,我們發現kubelet有很大的嫌疑 ,查看社區問題,找到一個類似的案例:

https://github.com/google/cadvisor/issues/1774#issuecomment-501907566

同我們碰到的問題一樣,可以通過drop cache 來進行修復。在繼續往下深究的過程中,該問題的處理作者發佈了一個博客:

https://github.blog/2019-11-21-debugging-network-stalls-on-kubernetes/

其中詳細闡述了他們定位該問題的過程,以及引起該問題的原因。

簡單來說,就是pod刪除後,cgroup還殘留在cache中,導致kubelet獲取根cgroup內存統計信息的系統調用耗費太長時間,進而影響了ksoftirqd對softirq的處理。而爲何cgroup在容器刪除後,還會有信息遺留呢,感興趣的可以查看鏈接:

https://lwn.net/Articles/787614/

主機的延時問題會是經常碰到的一個難題 ,因此在定位該問題的過程中,我們就在想如何能夠加速該類型問題的定位,而不用像定位本案例的時候,採取逐個排除的方式。主要的想法就是利用工具來快速找到數據產生延時的點,當獲知數據在哪個路徑上產生了延時之後,就可以針對性縮小範圍進行下一步的排查。受開源項目https://github.com/yadutaf/tracepkt的啓發, 我們也開發了基於eBPF的工具,來記錄數據包在內核裏面的處理流程。主要功能包括

  • 通過TRACEEVENT或者kprobe來探測網絡協議棧在二層,三層和四層上的典型路徑,例如net:netif_rx,net:net_dev_queue,net:napi_gro_receive_entry,net:netif_receive_skb_entry等。可以通過指定IP地址或者端口號等來抓取指定的數據包的處理事件。
  • 當對應的函數被調用或者event被觸發,則會從skb中獲取當前的network namespace、端口信息、CPU核、調用時間、進程號、數據包的五元組、數據包長度等信息。
  • 因爲我們的節點上會同時使用iptables和ipvs,所以也抓取了iptables和ipvs 各個處理鏈的入口和出口,用於計算各個處理鏈的花費時間,以及已經處理鏈對數據包的處理策略,例如ACCEPT,DROP等。

案例2

某個應用的實例有部署在虛擬機中,也有以容器部署到kuberntes集羣中。應用的管理員從監控中發現,調用某個第三方API的超時比例不同,前者在 0.001% ,後者在 0.01%-0.04% ,之間差了數十倍,因此彙報出該問題,讓我們去排查爲何會有這樣的差別。

經過溝通了解到,該用戶客戶端的超時時間是 100ms ,所有以容器部署的節點,超時的錯誤率都比運行在虛擬機上要高。於是我們開始着手定位該問題:

  1. 開始復現

    在節點上進行100次的API調用,每次調用可以在30ms-40ms之間完成。

    對節點容器進行1萬次的ping操作,並沒有發現有延時現象。

    因此復現失敗。

  2. 是否是由之前定位過的kubelet蒐集獲取memory.stat引起的呢?(詳見案例1)

    用命令:

    來計算讀取memory.stat的消耗時間,大部分都超過了50ms。因爲客戶端的超時時間設置爲100ms,而調用API的耗時在30-40ms,再加上這個消耗的50ms,是很可能導致超時的。於是執行drop cache的操作,很遺憾的是drop cache後,並沒有讓超時問題得到多少改善。

  3. 於是繼續跑設計的eBPF工具,希望能查找到在主機的哪個位置產生了超時。

在抓取了超十萬條數據記錄後,用腳本對輸出結果進行分析,根據輸出條目的時間戳和數據包信息,過濾出在主機上有異常延時的數據包信息,如圖5所示:

圖5

圖5中的第二列代表了當前處理數據包的network namespace,第三列代表當前數據包處理的端口,第四列顯示了數據包信息,第五列顯示了IPVS或者iptables對數據包的處理,第六列顯示了處理softirq的進程信息,第七列顯示了當前處理數據的CPU核,第八列顯示了處理數據開始的時間戳,是系統的啓動時間,單位爲ns。

從抓取數據信息來看,有數據包從容器發出到主機端口發送,時間使用10865291752989502 - 10865291551167147 = 201822355ns, 也就是超過了200ms,主要的時間消耗在從第3步到第4步之間 ,從容器的network namespace到主機的network namespace,就超過10865291752980718 - 10865291551180388 = 201800330 (> 200ms)。

在分析了多個數據超時的數據包後,發現他們具有同樣的模版,那就是在主機network namespace上處理veth pair端口過來數據包的CPU core,都是 32

通過top命令來查看該核的運行情況,發現該核的中斷處理時間si會間歇性比較高,經常達到30%的使用率,而節點的load很低,整體CPU的使用率也不高。

如果沒有基於eBPF的工具,而是通過類似排查問題1這樣的定位手段,想要定位到這一步,是需要很長的時間的,我們可能會注意到32的si會稍微高點,但是很難和網絡延時問題進行直接的關聯。 而有了eBPF工具,通過抓取數據,很快就得到了這樣的關鍵信息,這些信息對於解決問題可謂是至關重要。

接着通過perf,來查看下具體該核都在做什麼事情,並生成火焰圖(圖6)。

圖6

可以看到該核消耗了很大的一部分時間在處理estimation_timer。而si比較高,要麼estimation_timer的中斷次數很多,要麼處理某個中斷耗時很長。

先看中斷次數是否很多,通過我們自己開發的小工具,從/proc/interrupts獲取一段時間內該核的中斷數量,結果顯示該核上並沒有很多timer中斷數量,其他類型的中斷也很少。

既然沒有很多的中斷,那麼問題大概率是中斷處理時間比較長。於是,我們對bcc的softirq 工具https://github.com/iovisor/bcc/blob/master/tools/softirqs.py 進行了修改,讓它只統計CPU32 上不同類型的softirq耗時信息。在運行一段時間後,從輸出結果中發現有timer的softirq執行時間在 262ms-524ms 之間。

從代碼得知,estimation_timer是由ipvs模塊進行註冊的:

從estimation_timer的函數實現來看,它會調用spin_lock進行鎖的操作,並且會查詢每一條的ipvs rules進行相關操作。我們集羣上的service比較多,因此在節點上,會存在很多的ipvs 規則,導致estimation_timer的執行需要消耗大量的時間。

但是爲什麼只有在核32上會出現問題呢?

通過ftrace,我們抓取了estimation_timer()的function_graph, 從圖7的結果可以看到,estimation_timer()在很多的核上都被執行,但是隻有在32核上執行的時候,消耗了很多的時間,其他核上消耗的時間都很少。

圖7

在kube-proxy使用ipvs模式的節點上,只有主機的host network namespace纔有很多的ipvs rules,而在容器的netowrk namespace裏面,並沒有ipvs rules存在。從現象來看,核32應該是被用來處理host network namespace的ipvs rules,而其他的核用來處理其它容器network namespace的ipvs rules,所以纔會有這麼大的差別。

那爲什麼核32被用來處理host network namespace,而不是其他的核呢?

從實驗結果來看,該值是由加載ipvs 模塊時候的核決定的,如果通過task_set來設定加載ipvs模塊的核,那麼會由該核來執行host network namespace的estimation_timer。

至此,我們已經知道了該問題的來龍去脈,解決方法主要是:

  • 短期內 通過live patch將estimation_timer的執行功能替換來解決線上的問題,目前我們暫時不需要estimation_timer對不同的鏈路進行速率等的統計,所以暫時將功能替換爲不執行任何操作。
  • 在長期方案上 ,我們選擇去除該timer,通過kernel thread來定期執行ipvs相關的鏈路速率、數據包等的統計功能,也就是來完成之前estimation_timer的工作。該方案目前還在進行更多詳細的測試。

總結——網絡超時篇

網絡超時問題,關鍵還是在於找尋網絡超時的時間點。如果可以找出這個時間點,就能快速縮小問題範圍,也爲最終解決問題提供一個正確有力的指導方向。這事情如同穿衣服,第一個鈕釦對上了,後面的其他鈕釦也就自然而然對上了。

從上面兩個案例的定位方式對比來看, 拋棄手忙腳亂進行各種嘗試,而是利用工具,來尋找第一個鈕釦 ,是在複雜的Linux環境下,快速定位此類問題的關鍵方法。利用eBPF可以對內核進行更深入的剖析,讓我們初步感受到了它的威力。因此我們也將用它來定位其他類型的問題,詳情請關注後續的丟包篇和重傳篇。

本文轉載自公衆號eBay技術薈(ID:eBayTechRecruiting)。

原文鏈接

https://mp.weixin.qq.com/s/ZUS94PMCKsqgZFHX9b99-g

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章