本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
1000 個節點及以上:Kubernetes 1.2 的效能和可擴充套件性更新
編者按:這是關於Kubernetes 1.2新功能系列深度文章的第一篇。
我們很自豪地宣佈,隨著1.2版本的釋出,Kubernetes現在支援1000個節點叢集,並且大多數API操作的第99百分位尾部延遲降低了80%。這意味著在短短六個月內,我們的總體規模擴大了10倍,同時保持了出色的使用者體驗——第99百分位Pod啟動時間不到3秒,大多數API操作的第99百分位延遲為幾十毫秒(LIST操作除外,在非常大的叢集中需要幾百毫秒)。
言語固然重要,但沒有什麼比演示更具說服力。請看!
在上面的影片中,您看到了叢集擴充套件到1000個節點,每秒1000萬次查詢(QPS),包括滾動更新,零停機時間,並且對尾部延遲沒有影響。這個規模足以成為網際網路上排名前100的網站之一!
在這篇博文中,我們將介紹我們為實現這一成果所做的工作,並討論我們未來擴充套件到更高規模的一些計劃。
方法論
我們根據以下服務級別目標(SLO)對Kubernetes的可伸縮性進行基準測試
- API響應速度 1 99%的所有API呼叫在1秒內返回。
- Pod啟動時間:99%的Pod及其容器(預拉取映象)在5秒內啟動。我們只有在滿足這兩個SLO的情況下才認為Kubernetes可以擴充套件到一定數量的節點。我們作為專案測試框架的一部分,持續收集和報告上述測量結果。這套測試分為兩部分:API響應速度和Pod啟動時間。
使用者級抽象的API響應速度2
Kubernetes為使用者提供高階抽象來表示他們的應用程式。例如,ReplicationController是表示Pod集合的抽象。列出所有ReplicationController或列出給定ReplicationController中的所有Pod是一個非常常見的用例。另一方面,幾乎沒有人會想要列出系統中的所有Pod——例如,30,000個Pod(1000個節點,每個節點30個Pod)代表大約150MB的資料(約5kB/Pod * 30k Pod)。因此,此測試使用ReplicationController。
對於此測試(假設N為叢集中的節點數),我們
建立大約3xN個不同大小的ReplicationController(5、30和250個副本),它們總共有30xN個副本。我們隨著時間推移分散它們的建立(即我們不會同時啟動所有它們),並等待所有它們都執行起來。
對每個ReplicationController執行一些操作(擴充套件它,列出它的所有例項等),隨著時間推移分散這些操作,並測量每個操作的延遲。這類似於真實使用者在正常叢集操作過程中可能做的事情。
停止並刪除系統中的所有ReplicationController。有關此測試的結果,請參閱下面的“Kubernetes 1.2的度量”部分。
對於v1.3版本,我們計劃透過建立Service、Deployment、DaemonSet和其他API物件來擴充套件此測試。
Pod啟動端到端延遲3
使用者也非常關心Kubernetes排程和啟動Pod所需的時間。這不僅在初始建立時如此,而且當ReplicationController需要建立替代Pod以接替節點發生故障的Pod時也如此。
我們(假設N為叢集中的節點數)
建立一個包含30xN個副本的單個ReplicationController,並等待所有副本執行。我們還在執行高密度測試,包含100xN個副本,但叢集中的節點較少。
啟動一系列單Pod ReplicationController——每200毫秒一個。對於每個,我們測量“總端到端啟動時間”(定義見下文)。
停止並刪除系統中的所有Pod和ReplicationController。我們將“總端到端啟動時間”定義為從客戶端向API伺服器傳送建立ReplicationController請求的時刻到透過watch向客戶端返回“執行中且就緒”Pod狀態的時刻。這意味著“Pod啟動時間”包括ReplicationController的建立以及隨後建立Pod、排程器排程該Pod、Kubernetes設定Pod內部網路、啟動容器、等待Pod成功響應健康檢查,然後最終等待Pod將其狀態報告回API伺服器,然後API伺服器透過watch將其報告給客戶端。
雖然我們可以透過例如排除等待透過watch報告,或直接建立Pod而不是透過ReplicationController來大大減少“Pod啟動時間”,但我們相信,與最實際用例相對應的廣泛定義最能幫助真實使用者瞭解他們可以從系統獲得的效能。
Kubernetes 1.2的度量
那麼結果如何?我們在Google Compute Engine上執行測試,根據Kubernetes叢集的大小設定主VM的大小。特別是對於1000節點叢集,我們使用n1-standard-32 VM作為主節點(32核,120GB RAM)。
API響應速度
以下兩張圖顯示了Kubernetes 1.2版本和1.0版本在100節點叢集上的第99百分位API呼叫延遲比較。(條形越短越好)
我們單獨展示LIST操作的結果,因為這些延遲明顯更高。請注意,我們在此期間稍微修改了測試,因此針對v1.0運行當前測試將導致比以往更高的延遲。
我們還針對1000節點叢集運行了這些測試。注意:我們不支援GKE上超過100個節點的叢集,因此我們沒有可比較這些結果的度量。但是,自Kubernetes 1.0以來,客戶已經報告在1000多個節點叢集上執行。
由於LIST操作明顯更大,我們再次單獨展示它們:在兩種叢集大小下,所有延遲都遠在我們1秒的SLO範圍內。
Pod啟動端到端延遲
“Pod啟動延遲”(定義在“Pod啟動端到端延遲”部分)的結果顯示在下圖中。作為參考,我們還在圖的第一部分展示了v1.0在100節點叢集上的結果。
正如您所看到的,我們大大降低了100節點叢集的尾部延遲,現在在最大規模的叢集中也能提供低Pod啟動延遲。值得注意的是,1000節點叢集的API延遲和Pod啟動延遲的度量通常都優於六個月前100節點叢集報告的度量!
我們是如何實現這些改進的?
為了在過去六個月中在規模和效能上取得這些顯著進展,我們對整個系統進行了一系列改進。以下列出了一些最重要的改進。
- _ 在API伺服器級別建立“讀取快取” _
(https://github.com/kubernetes/kubernetes/issues/15945)
由於大多數Kubernetes控制邏輯在由etcd watch(透過API伺服器)保持最新狀態的有序、一致快照上執行,因此資料到達的輕微延遲對叢集的正確操作沒有影響。這些獨立控制器迴圈,透過設計分散以實現系統的可擴充套件性,樂於犧牲一點延遲來提高整體吞吐量。
在Kubernetes 1.2中,我們利用這一事實,透過新增API伺服器讀取快取來提高效能和可伸縮性。透過這一更改,API伺服器的客戶端可以從API伺服器的記憶體快取中讀取資料,而不是從etcd中讀取。快取通過後臺的etcd watch直接更新。那些可以容忍檢索資料延遲的客戶端(通常快取的滯後時間在幾十毫秒的量級)可以完全從快取中提供服務,從而減少etcd的負載並提高伺服器的吞吐量。這是v1.1中開始的一項最佳化工作的延續,我們在v1.1中添加了直接從API伺服器而不是etcd提供watch的支援:https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/apiserver-watch.md。
感謝Google的Wojciech Tyczynski以及Red Hat的Clayton Coleman和Timothy St. Clair的貢獻,我們能夠將精心的系統設計與etcd的獨特優勢相結合,以提高Kubernetes的可伸縮性和效能。
- 在Kubelet中引入“Pod生命週期事件生成器”(PLEG) (https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/pod-lifecycle-event-generator.md)
Kubernetes 1.2還在Pod/節點密度方面進行了改進——對於v1.2,我們測試並宣傳單個節點上最多支援100個Pod(而1.1版本為30個Pod)。這一改進得益於Kubernetes社群透過實現Pod生命週期事件生成器(PLEG)所做的努力。
Kubelet(Kubernetes節點代理)為每個Pod分配了一個工作執行緒,負責管理Pod的生命週期。在早期版本中,每個工作執行緒會定期輪詢底層容器執行時(Docker)以檢測狀態變化,並執行任何必要的操作以確保節點狀態與所需狀態匹配(例如,透過啟動和停止容器)。隨著Pod密度的增加,每個工作執行緒的併發輪詢會使Docker執行時不堪重負,導致嚴重的可靠性和效能問題(包括額外的CPU利用率,這是擴充套件的限制因素之一)。
為了解決這個問題,我們引入了一個新的Kubelet子元件——PLEG——來集中狀態變化檢測併為工作執行緒生成生命週期事件。透過消除併發輪詢,我們能夠將Kubelet和容器執行時的穩態CPU使用率降低4倍。這還使我們能夠採用更短的輪詢週期,以便更快地檢測和響應變化。
改進排程器吞吐量 CoreOS的Kubernetes社群成員(Hongchao Deng和Xiang Li)深入研究了Kubernetes排程器,並在不犧牲準確性或靈活性的情況下,顯著提高了吞吐量。他們將排程30,000個Pod的總時間縮短了近1400%!您可以在此處閱讀一篇關於他們如何解決該問題的精彩博文:https://coreos.com/blog/improving-kubernetes-scheduler-performance.html
更高效的JSON解析器 Go的標準庫包含一個靈活易用的JSON解析器,可以使用反射API編碼和解碼任何Go結構體。但這種靈活性是有代價的——反射會分配大量小物件,這些物件必須由執行時跟蹤和垃圾回收。我們的分析證實了這一點,顯示客戶端和伺服器的大部分時間都花在序列化上。鑑於我們的型別不會頻繁更改,我們懷疑可以透過程式碼生成繞過大量的反射。
在調查了Go JSON生態系統並進行了一些初步測試後,我們發現ugorji編解碼器庫提供了最顯著的加速——使用生成的序列化器時,JSON編碼和解碼的效能提高了200%,同時顯著減少了物件分配。在為上游庫貢獻了修復程式以處理我們的一些複雜結構之後,我們將Kubernetes和go-etcd客戶端庫切換到了該庫。除了JSON上下層的其他一些重要最佳化之外,我們能夠大幅降低幾乎所有API操作的CPU時間成本,尤其是讀取操作。
其他顯著的更改也帶來了顯著的收益,包括:
- 減少了損壞的TCP連線數量,這些連線導致了不必要的新的TLS會話:https://github.com/kubernetes/kubernetes/issues/15664
- 提高了大型叢集中ReplicationController的效能:https://github.com/kubernetes/kubernetes/issues/21672
在這兩種情況下,問題都由Kubernetes社群成員除錯和/或修復,包括Red Hat的Andy Goldstein和Jordan Liggitt,以及網易的Liang Mingqiang。
Kubernetes 1.3及未來
當然,我們的工作尚未完成。我們將繼續投入精力改進Kubernetes的效能,因為我們希望它能擴充套件到數千個節點,就像Google的Borg一樣。得益於我們在測試基礎設施方面的投入以及對團隊如何在生產環境中使用容器的關注,我們已經確定了改進規模的下一步措施。
Kubernetes 1.3的計劃包括:
我們的主要瓶頸仍然是API伺服器,它大部分時間都花在JSON物件的編組和解組上。我們計劃為API新增協議緩衝區支援,作為元件間通訊和在etcd中儲存物件的可選路徑。使用者仍然可以使用JSON與API伺服器通訊,但由於大多數Kubernetes通訊是叢集內部的(API伺服器到節點,排程器到API伺服器等),我們預計主節點上的CPU和記憶體使用量將顯著減少。
Kubernetes使用標籤來標識物件集;例如,識別哪些Pod屬於給定的ReplicationController需要遍歷名稱空間中的所有Pod並選擇與控制器標籤選擇器匹配的Pod。新增一個高效的標籤索引器,可以利用現有的API物件快取,將使快速查詢匹配標籤選擇器的物件成為可能,從而使這種常見操作更快。
排程決策基於許多不同的因素,包括根據請求資源分散Pod,分散具有相同選擇器的Pod(例如,來自同一服務、ReplicationController、Job等),節點上是否存在所需的容器映象等。這些計算,特別是選擇器分散,有許多改進的機會——有關其中一個建議的更改,請參閱https://github.com/kubernetes/kubernetes/issues/22262。
我們還對即將釋出的etcd v3.0版本感到興奮,該版本在設計時考慮了Kubernetes的用例——它將提高效能並引入新功能。CoreOS的貢獻者已經開始為將Kubernetes遷移到etcd v3.0奠定基礎(請參閱https://github.com/kubernetes/kubernetes/pull/22604)。儘管此列表並未涵蓋所有與效能相關的努力,但我們樂觀地認為,我們將實現與Kubernetes 1.0到1.2版本一樣大的效能提升。
結論
在過去的六個月中,我們顯著提高了Kubernetes的可伸縮性,使v1.2能夠在1000節點叢集上執行,並具有與以前僅在小得多叢集上實現的相同出色響應速度(透過我們的SLO衡量)。但這還不夠——我們希望將Kubernetes推向更遠、更快。Kubernetes v1.3將進一步提高系統的可伸縮性和響應速度,同時繼續新增功能,使其更易於構建和執行最苛刻的基於容器的應用程式。
請加入我們的社群,幫助我們構建Kubernetes的未來!有很多方式可以參與。如果您特別對可伸縮性感興趣,您會對以下內容感興趣:
- 我們的可伸縮性Slack頻道
- 可伸縮性“特別興趣小組”,每週四太平洋時間上午9點在SIG-Scale環聊舉行會議。當然,有關該專案的更多資訊,請訪問www.kubernetes.io
1我們排除了對“事件”的操作,因為這些更像是系統日誌,並且不是系統正常執行所必需的。
2這是Kubernetes GitHub儲存庫中的test/e2e/load.go。
3這是Kubernetes GitHub儲存庫中的test/e2e/density.go測試。
4我們正在考慮在下一版本中最佳化此問題,但目前使用較小的主節點可能會導致顯著(數量級)的效能下降。我們鼓勵任何對Kubernetes進行基準測試或嘗試重現這些發現的人使用類似大小的主節點,否則效能會受到影響。