Categories
程式開發

攜程容器偶發性超時問題案例分析


前言

隨著攜程的應用大規模在生產上用容器部署,各種上規模的問題都慢慢浮現,其中比較難定位和解決的就是偶發性超時問題,下面將分析目前為止我們遇到的幾種偶發性超時問題以及排查定位過程和解決方法,希望能給遇到同樣問題的小伙伴們以啟發。

問題描述

某一天接到用戶報障說,Redis集群有超時現象發生,比較頻繁,而訪問的QPS也比較低。緊接著,陸續有其他用戶也報障Redis訪問超時。在這些報障容器所在的宿主機裡面,我們猛然發現有之前因為時鐘漂移問題升級過內核到4.14.26的宿主機ServerA,心裡突然有一絲不詳的預感。

初步分析

因為現代軟件部署結構的複雜性以及網絡的不可靠性,我們很難快速定位“connect timout”或“connectreset by peer”之類問題的根因。

歷史經驗告訴我們,一般比較大範圍的超時問題要么和交換機路由器之類的網絡設備有關,要么就是底層系統不穩定導致的故障。從報障的情況來看,4.10和4.14的內核都有,而宿主機的機型也涉及到多個批次不同廠家,看上去沒頭緒,既然沒什麼好辦法,那就抓個包看看吧。

攜程容器偶發性超時問題案例分析 1

圖1

攜程容器偶發性超時問題案例分析 2

圖2

圖1是App端容器和宿主機的抓包,圖2是Redis端容器和宿主機的抓包。因為APP和Redis都部署在容器裡面(圖3),所以一個完整請求的包是B->A->C->D,而Redis的回包是D->C->A->B。

攜程容器偶發性超時問題案例分析 3

圖3

上面抓包的某一條請求如下:

1)B(圖1第二個)的請求時間是21:25:04.846750 #99

2)到達A(圖1第一個)的時間是21:25:04.846764 #96

3)到達C(圖2第一個)的時間是21:25:07.432436 #103

4)到達D(圖2第二個)的時間是21:25:07.432446 #115

該請求從D回復如下:

1)D的回复時間是21:25:07.433248 #116

2)到達C的時間是21:25:07.433257 #104

3)到達A點時間是21:25:05:901108 #121

4)到達B的時間是21:25:05:901114 #124

從這一條請求的訪問鏈路我們可以發現,B在200ms超時後等不到回包。在21:25:05.055341重傳了該包#100,並且可以從C收到重傳包的時間#105看出,幾乎與#103的請求包同時到達,也就是說該第一次的請求包在網絡上被延遲傳輸了。大致的示意如下圖4所示:

攜程容器偶發性超時問題案例分析 4

圖4

從抓包分析來看,宿主機上好像並沒有什麼問題,故障在網絡上。而我們同時在兩邊宿主機,容器里以及連接宿主機的交換機抓包,就變成了下面圖5所示,假設連接A的交換機為E,也就是說A->E這段的網絡有問題。

攜程容器偶發性超時問題案例分析 5

圖5

陷入僵局

儘管發現A->E這段有問題,排查卻也就此陷入了僵局,因為影響的宿主機遍布多個IDC,這也讓我們排除了網絡設備的問題。我們懷疑是否跟宿主機的某些TCP參數有關,比如TSO/GSO,一番測試後發現開啟關閉TSO/GSO和修改內核參數對解決問題無效,但同時我們也觀察到,從相同IDC裡任選一台宿主機Ping有問題的宿主機,百分之幾的概率看到很高的響應值,如下圖6所示:

攜程容器偶發性超時問題案例分析 6

圖6

同一個IDC內如此高的Ping響應延遲,很不正常。而這時DBA告訴我們,他們的某台物理機ServerB也有類似的的問題,Ping延遲很大,SSH上去後明顯感覺到有卡頓,這無疑給我們解決問題帶來了希望,但又更加迷惑:

1)延遲好像跟內核版本沒有關係,3.10,4.10,4.14的三個版本內核看上去都有這種問題。

2)延遲和容器無關,因為延遲都在宿主機上到連接宿主機的交換機上發現的。

3)ServerB跟ServerA雖然表現一樣,但細節上看有區別,我們的宿主機在重啟後基本上都能恢復一段時間後再复現延遲,但ServerB重啟也無效。

由此我們判斷ServerA和ServerB的症狀並不是同一個問題,並讓ServerB先升級固件看看。在升級固件後ServerB恢復了正常,那麼我們的宿主機是不是也可以靠升級固件修復呢?答案是並沒有。升級固件後沒過幾天,延遲的問題又出現了。

意外發現

回過頭來看之前為了排查Skylake時鐘漂移問題的ServerA,上面一直有個簡單的程序在運行,來統計時間漂移的值,將時間差記到文件中。當時這個程序是為了驗證時鐘漂移問題是否修復,如圖7:

攜程容器偶發性超時問題案例分析 7

圖7

這個程序跑在宿主機上,每個機器各有差異,但正常的時間差應該是100us以內,但1個多月後,時間差異越來越大,如圖8,最大能達到幾百毫秒以上。這告訴我們可能通過這無意中的log來找到根因,而且驗證了上面3的這個猜想,宿主機是運行一段時間後逐漸出問題,表現為第一次打點到第二次打點之間,調度會自動delay第二次打點。

攜程容器偶發性超時問題案例分析 8

圖8

TSC和Perf

Turbostat是intel開發的,用來查看CPU狀態以及睿頻的工具,同樣可以用來查看TSC的頻率。而關於TSC,之前的文章《攜程一次Redis遷移容器後Slowlog“異常”分析》中有過相關介紹,這裡就不再展開。

在有問題的宿主機上,TSC並不是恆定的,如圖9所示,這個跟相關資料有出入,當然我們分析更可能的原因是,turbostat兩次去取TSC的時候,被內核調度delay了,如果第一次取時被delay導致取的結果比實際TSC的值要小,而如果第二次取時被delay會導致取的結果比實際值要大。

攜程容器偶發性超時問題案例分析 9

圖9

Perf 是內置於Linux上的基於採樣的性能分析工具,一般隨著內核一起編譯出來,具體的用法可以搜索相關資料,這裡也不展開。用perf sched record -a sleep 60和perf sched latency -s max來查看linux的調度延遲,發現最大能錄得超過1s的延遲,如圖10和圖11所示。用戶態的進程有時因為CPU隔離和代碼問題導致比較大的延遲還好理解,但這些進程都是內核態的。儘管linux的CFS調度並非實時的調度,但在負載很低的情況下超過1s的調度延遲也是匪夷所思的。

攜程容器偶發性超時問題案例分析 10

圖10

攜程容器偶發性超時問題案例分析 11

圖11

根據之前的打點信息和turbostat以及perf的數據,我們非常有理由懷疑是內核的調度有問題,這樣我們就用基於rdtscp指令更精準地來獲取TSC值來檢測CPU是否卡頓。 rdtscp指令不僅可以獲得當前TSC的值,並且可以得到對應的CPU ID。如圖12所示:

攜程容器偶發性超時問題案例分析 12

圖12

上面的程序編譯後,放在宿主機上依次綁核執行,我們發現在問題的宿主機上可以打印出比較大的TSC的值。每間隔100ms去取TSC的值,將獲得的相減,在正常的宿主機上它的值應該跟CPU的TSC緊密相關,比如我們的宿主機上TSC是1.​​7GHZ的頻率,那麼0.1s它的累加值應該是170000000,正常獲得的值應該是比170000000多一點,圖13的第五條的值本身就說明了該宿主機的調度延遲在2s以上。

攜程容器偶發性超時問題案例分析 13

圖13

真相大白

通過上面的檢測手段,可以比較輕鬆地定位問題所在,但還是找不到根本原因。這時我們突然想起來,線上Redis大規模使用宿主機的內核4.14.67並沒有類似的延遲,因此我們懷疑這種延遲問題是在4.14.26到4.14.67之間的bugfix修復掉了。

查看commit記錄,先二分查找大版本,再將懷疑的點單獨拎出來patch測試,終於發現了這個4.14.36-4.14.37之間的(圖14)commit:

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v4.14.37&id=b8d4055372b58aad4a51b67e176eabdcc238fde3

攜程容器偶發性超時問題案例分析 14

圖14

從該commit的內容來看,修復了無效的apic id會導致possible CPU個數不正確的情況,那麼什麼是x2apic呢?什麼又是possible CPU?怎麼就導致了調度的延遲呢?

說到x2apic,就必須先知道apic, 翻查網上的資料就發現,apic全稱Local Advanced ProgrammableInterrupt Controller,是一種負責接收和發送中斷的芯片,集成在CPU內部,每個CPU有一個屬於自己的local apic。它們通過apic id進行區分。而x2apic是apic的增強版本,將apic id擴充到32位,理論上支持2^32-1個CPU。簡單的說,操作系統通過apic id來確定CPU的個數。

而possible CPU則是內核為了支持CPU熱插拔特性,在開機時一次載入,相應的就有online,offline CPU等,通過修改/sys/devices/system/cpu/cpu9/online可以動態關閉或打開一個CPU,但所有的CPU個數就是possible CPU,後續不再修改。

該commit指出,因為沒有忽略apic id為0xffffffff的值,導致possible CPU不正確。此commit看上去跟我們的延遲問題找不到關聯,我們也曾向該issue的提交者請教調度延遲的問題,對方也不清楚,只是表示在自己環境只能複現possible CPU增加4倍,vmstat的運行時間增加16倍。

這時我們查看有問題的宿主機CPU信息,奇怪的事情發生了,如圖15所示,12核的機器上possbile CPU居然是235個,而其中12-235個是offline狀態,也就是說真正工作的只有12個,這麼說好像還是跟延遲沒有關係。

攜程容器偶發性超時問題案例分析 15

圖15

繼續深入研究possbile CPU,我們發現了一些端倪。從內核代碼來看,引用for_each_possible_cpu()這個函數的有600多處,遍布各個內核子模塊,其中關鍵的核心模塊比如vmstat,shed,以及loadavg等都有對它的大量調用。而這個函數的邏輯也很簡單,就是遍歷所有的possible CPU,那麼對於12核的機器,它的執行時間是正常宿主機執行時間的將近20倍!該commit的作者也指出太多的CPU會浪費向量空間並導致BUG(https://lkml.org/lkml/2018/5/2/115),而BUG就是調度系統的緩慢延遲。

以下圖16,圖17是對相同機型相同廠商的兩台空負載宿主機的kubelet的perf數據(perf stat -p $pid sleep 60),圖16是uptime 2天的,而圖17是uptime 89天的。

攜程容器偶發性超時問題案例分析 16

圖16

攜程容器偶發性超時問題案例分析 17

圖17

我們一眼就看出圖16的宿主機不正常,因為無論是CPU的消耗,上下文的切換速度,指令周期,都遠劣於圖17的宿主機,並且還在持續惡化,這就是宿主機延遲的根本原因。而圖17宿主機恰恰只是圖16的宿主機打上圖14的patch後的內核,可以看到,possible CPU恢復正常(圖18),至此超時問題的排查告一段落。

攜程容器偶發性超時問題案例分析 18

圖18

總結

我們排查發現,不是所有的宿主機,所有的內核都在此BUG的影響範圍內,具體來說4.10(之前的有可能會有影響,但我們沒有類似的內核,無法測試)-4.14.37(因為是stable分支,所以master分支可能更靠後)的內核,CPU為skylake及以後型號的某些廠商的機型會觸發這個BUG。

確認是否受影響也比較簡單,查看possible CPU是否是真實CPU即可。

作者介紹

李劍,攜程系統研發部技術專家,負責Redis和Mongodb的容器化和服務化工作,喜歡深入分析系統疑難雜症。

本文轉載自公眾號攜程技術中心(ID:ctriptech)。

原文鏈接

https://mp.weixin.qq.com/s/bSNWPnFZ3g_gciOv_qNhIQ