歡迎光臨
每天分享高質量文章

容器化 RDS:藉助火焰圖定位Kubernetes效能問題

容器化RDS系列文章:

藉助 CSI(Container Storage Interface),加上對 Kubenetes 核心程式碼的少量修改,可以 out-tree 的方式高效且低耦合的方式擴充套件 Kubenetes 儲存管理子模組。
如《容器化 RDS:藉助 CSI 擴充套件 Kubernetes 儲存能力》介紹,以 out-tree 方式新增 PVC 擴容(Resize)特性。
從可執行程式到可用程式設計產品,還需要設計結合業務需求的效能基準測試,並對發現的效能瓶頸進行最佳化。
經驗資料表明,相同功能的程式設計產品的成本,至少是已經過測試的程式的三倍。
——人月神話
本文將分享效能基準測試的最佳化案例:
  • 發現效能瓶頸

  • 確定問題元件

  • 藉助 CPU Profile 和 Flame Graph,快速縮小範圍,定位到問題 code-path

  • 有針對的最佳化

發現效能瓶頸

測試用例:
批次建立100個讀寫樣式為RWO,容量為1GiB 的 PVC
期望測試結果:
180秒內全部建立成功並無報錯
所有的程式設計人員都是樂觀主義者,畢竟在可能出現問題的地方,一定都會遇到問題,在耗時 3600 秒後,95% 的 PVC 處於 Pending 狀態,嚴格的說,在批次建立的場景,該功能不可用。

大量 PVC 處於 Pending 狀態


定位問題元件

由於涉及元件眾多:
  • kube-apiserver

  • kube-controller-manager

  • kubelet

  • external-provisioner

  • external-attacher

  • csi-driver

  • qcfs-csi-plugin

元件之間呼叫複雜,再加上無處不在的協程(goroutine),如果直接檢視日誌或是 debug code 定位問題,猶如大海撈針,更不要說定位效能瓶頸。所以,首要工作是先定位到問題元件。
在測試過程中,我們記錄了所有元件和系統的資源使用情況,運氣不佳,從 CPU 使用情況,記憶體使用情況,網路 I/O 和磁碟 I/O 來看都沒有異常資料。
透過梳理儲存管理相關元件的架構圖:

架構圖
以及業務流程的梳理,kube-controller-manager、external-provisioner 和 csi-driver 嫌疑較大。
透過 kubelet logs 檢視日誌,可以在 external-provisioner 中發現可疑日誌:

I0728 19:19:50.504069    1 request.go:480] Throttling request took 192.714335ms, request: POST:https://10.96.0.1:443/api/v1/namespaces/default/events
I0728 19:19:50.704033    1 request.go:480] Throttling request took 190.667675ms, request: POST:https://10.96.0.1:443/api/v1/namespaces/default/events
external-provisioner 訪問 kube-apiserver 觸發限流
external-provisioner 有重大嫌疑。
定位問題 code-path

我們可以立馬進入除錯環節:
  1. 閱讀 external-provisioner 程式碼,加入除錯日誌,理解邏輯

  2. 不斷縮小 code-path

步驟 1、2持續迭代,直到最終定位到問題函式,這是非常有效的辦法。
或者採用 CPU profile:
  1. 採集堆疊樣本

  2. 找到在取樣手氣內消耗 CPU 時間比率最高的函式,把該函式作為除錯的起點

相比上一種,更高效的縮小問題的範圍,節省更多的時間。
藉助模組“net/http/pprof”,對 external-provisioner 進行 60 秒的 CPU 取樣,可以獲得如下資訊:
生成堆疊使用百分比排序:

函式的呼叫關係以及取樣週期內 CPU 耗時百分比:

針對“net/http/pprof”稍微囉嗦幾句:
  • 提供 CPU profile 和 Heap profile;

  • 在取樣時獲得堆疊(幾乎所有)資訊, 以此為依據估算整個取樣週期內堆疊的CPU佔用百分比, 並不是 100% 準確;

  • 取樣成本並不低,100赫茲既可以取樣夠用的堆疊資訊,又不會給應用程式帶來過大開銷;

  • CPU 取樣頻率預設為 100 赫茲,並硬編碼到模組中, 不建議調到 500 赫茲以上。


網上已經有大量的相關文章,這裡不贅述。
配合獲取的 CPU profile 資訊生成火焰圖(Flame Graph):

這裡針對火焰圖再囉嗦下:
  • 藉助第三方工具 go-torch 繪製

  • 每個矩形代表一個堆疊,取樣時間內,CPU 佔用百分比越高 Y 軸越長,X 軸表明瞭堆疊之間的呼叫關係

  • 從左到右按照字母表排序

  • 顏色隨機選擇,無具體含義


網上已經有大量的相關文章,這裡不贅述。
可以發現函式 addClaim 和 lockProvisionClaimOperation 的 CPU 佔用比率達到 36.23%。

來自於 external-provisioner 呼叫的第三方模組 kubenetes-incubator/external-storage
所以,只要取用例如了模組 Kubenetes-incubator/external-storage 實現捲建立功能,都可以復現 api throttling。
再針對性的加入除錯日誌到 code-path 中,理解邏輯,很快可以確定問題:
在建立捲時,external-storage 需要訪問 API 資源(譬如 configmap、pvc、pv、event、secrets、storageclass 等),為減少 kube-apiserver 工作負荷,不建議直接訪問 kube-apiserver,而應該利用本地快取(由 informer cache 構建)。但 external-storage 恰好直接訪問 kube-apiserver。透過下圖可以看到,有18.84%的取樣時間在 list event,這是導致 api throttling 的原因。
進一步分析,之所以有大量的 list event 是因為 Leader Election 的 Lock 實現粒度太細導致鎖搶佔嚴重。生產環境中,一個元件會啟動多個實體,搶佔到 Leader Lock 的實體即為 Leader 並對外提供服務,其他實體以 Slave 樣式執行,一旦 Leader 出現問題,Slave 發現 Leader Lock 在租期內沒有更新即可發起搶佔成為新的 Leader 並接管服務。這樣不僅提升了元件的可用性也避免了可能帶來的 data race 問題。所以可以理解成是一個元件實體一把鎖,並且只在 Leader 和 Slave 角色切換時才會重新選主,但 external-storage 原意為了提升併發度,執行多個實體同時為 Leader 提供服務,可以簡單理解成一個 PVC 一把鎖,100 PVC 就意味著多個實體要最少發生100次的 Lock 搶佔。
最終定位到問題原因:
Lock 的搶佔導致 api throttling,引發 Lock 搶佔 timeout,timeout 後搶佔重試,進一步惡化 api throttling。
從下圖可以進一步得到驗證,有 8.7% 的取樣時間在進行 Leader Election。

解決問題

一旦發現問題的根源,解決它反而是件不難的事情。後面針對該問題做了修複:
  • 採用 sharedinformer cache

  • 修改 Leader Lock 粒度

再次生成執行,可以發現函式 addClaim 和 lockProvisionClaimOperation 的 CPU 佔用百分比下降到 13.95%。

external-provisioner 日誌中的 throttling 關鍵字消失
100 PVC 的時間縮短到60秒以內全部建立成功,無任何報錯。


結語

對於終端使用者而言,互動的介面越來越簡單,但對於開發者而言,元件越來越多,編譯一次的時間越來越久,加上無處不在的併發,導致定位問題的難度越來越大,尤其是效能問題。所以,對體系架構的理解能幫我們快速鎖定問題元件,配合 Profile 工具和 Flame Graph 快速定位 code-path,再加上對業務邏輯的理解找到解決方案。
所有的程式設計人員都是樂觀主義者,無論是什麼樣的程式,結果是勿庸置疑的:”這次它肯定會執行。” 或者 “我剛剛找出了最後一個問題。” 
——人月神話

Kubernetes實戰培訓

Kubernetes應用實戰培訓將於2018年10月12日在深圳開課,3天時間帶你係統學習Kubernetes本次培訓包括:容器基礎、Docker基礎、Docker進階、Kubernetes架構及部署、Kubernetes常用物件、Kubernetes網路、儲存、服務發現、Kubernetes的排程和服務質量保證、監控和日誌、Helm、專案實踐等,點選下方圖片檢視詳情。

贊(0)

分享創造快樂