本文主要介紹阿里巴巴在大規模生產環境中落地 Kubernetes 的過程當中,在集羣規模上遇到的典型問題以及對應的解決方案,內容包含對 etcd、kube-apiserver、kube-controller 的若干性能及穩定性加強,這些關鍵的加強是阿里巴巴內部上萬節點的 Kubernetes 集羣可以平穩支撐 2019 年天貓 618 大促的關鍵所在。
從阿里巴巴最先期的 AI 系統(2013)開始,集羣管理系統經歷了多輪的架構演進,到 2018 年全面的應用 Kubernetes ,這期間的故事是很是精彩的,有機會能夠單獨給你們作一個分享。這裏忽略系統演進的過程,不去討論爲何 Kubernetes 可以在社區和公司內部全面的勝出,而是將焦點關注到應用 Kubernetes 中會遇到什麼樣的問題,以及咱們作了哪些關鍵的優化。node
在阿里巴巴的生產環境中,容器化的應用超過了 10k 個,全網的容器在百萬的級別,運行在十幾萬臺宿主機上。支撐阿里巴巴核心電商業務的集羣有十幾個,最大的集羣有幾萬的節點。在落地 Kubernetes 的過程當中,在規模上面臨了很大的挑戰,好比如何將 Kubernetes 應用到超大規模的生產級別。web
羅馬不是一天就建成的,爲了瞭解 Kubernetes 的性能瓶頸,咱們結合阿里的生產集羣現狀,估算了在 10k 個節點的集羣中,預計會達到的規模:算法
咱們基於 Kubemark 搭建了大規模集羣模擬的平臺,經過一個容器啓動多個(50個)Kubemark 進程的方式,使用了 200 個 4c 的容器模擬了 10k 節點的 kubelet。在模擬集羣中運行常見的負載時,咱們發現一些基本的操做好比 Pod 調度延遲很是高,達到了驚人的 10s 這一級別,而且集羣處在很是不穩定的狀態。數據庫
當 Kubernetes 集羣規模達到 10k 節點時,系統的各個組件均出現相應的性能問題,好比:後端
爲了解決這些問題,阿里雲容器平臺在各方面都作了很大的努力,改進 Kubernetes 在大規模場景下的性能。api
首先是 etcd 層面,做爲 Kubernetes 存儲對象的數據庫,其對 Kubernetes 集羣的性能影響相當重要。安全
爲了解決該問題,咱們設計了基於 segregrated hashmap 的空閒頁面管理算法,hashmap 以連續 page 大小爲 key, 連續頁面起始 page id 爲 value。經過查這個 segregrated hashmap 實現 O(1) 的空閒 page 查找,極大地提升了性能。在釋放塊時,新算法嘗試和地址相鄰的 page 合併,並更新 segregrated hashmap。更詳細的算法分析能夠見已發表在 CNCF 博客的博文:
https://www.cncf.io/blog/2019/05/09/performance-optimization-of-etcd-in-web-scale-data-scenario/性能優化
經過這個算法改進,咱們能夠將 etcd 的存儲空間從推薦的 2GB 擴展到 100GB,極大的提升了 etcd 存儲數據的規模,而且讀寫無顯著延遲增加。除此以外,咱們也和谷歌工程師協做開發了 etcd raft learner(類 zookeeper observer)/fully concurrent read 等特性,在數據的安全性和讀寫性能上進行加強。這些改進已貢獻開源,將在社區 etcd 3.4 版本中發佈。架構
在 Kubernetes 集羣中,影響其擴展到更大規模的一個核心問題是如何有效的處理節點的心跳。在一個典型的生產環境中 (non-trival),kubelet 每 10s 彙報一次心跳,每次心跳請求的內容達到 15kb(包含節點上數十計的鏡像,和若干的卷信息),這會帶來兩大問題:併發
爲了解決這個問題,Kubernetes 引入了一個新的 build-in Lease API
,將與心跳密切相關的信息從 node 對象中剝離出來,也就是上圖中的 Lease
。本來 kubelet 每 10s 更新一次 node 對象升級爲:
由於 Lease
對象很是小,所以其更新的代價遠小於更新 node 對象。kubernetes 經過這個機制,顯著的下降了 API Server 的 CPU 開銷,同時也大幅減少了 etcd 中大量的 transaction logs,成功將其規模從 1000 擴展到了幾千個節點的規模,該功能在社區 Kubernetes-1.14 中已經默認啓用。
在生產集羣中,出於性能和可用性的考慮,一般會部署多個節點組成高可用 Kubernetes 集羣。但在高可用集羣實際的運行中,可能會出現多個 API Server 之間的負載不均衡,尤爲是在集羣升級或部分節點發生故障重啓的時候。這給集羣的穩定性帶來了很大的壓力,本來計劃經過高可用的方式分攤 API Server 面臨的壓力,但在極端狀況下全部壓力又回到了一個節點,致使系統響應時間變長,甚至擊垮該節點繼而致使雪崩。
下圖爲壓測集羣中模擬的一個 case,在三個節點的集羣,API Server 升級後全部的壓力均打到了其中一個 API Server 上,其 CPU 開銷遠高於其餘兩個節點。
解決負載均衡問題,一個天然的思路就是增長 load balancer。前文的描述中提到,集羣中主要的負載是處理節點的心跳,那咱們就在 API Server 與 kubelet 中間增長 lb,有兩個典型的思路:
經過壓測環境驗證發現,增長 lb 並不能很好的解決上面提到的問題,咱們必需要深刻理解 Kubernetes 內部的通訊機制。深刻到 Kubernetes 中研究發現,爲了解決 tls 鏈接認證的開銷,Kubernetes 客戶端作了不少的努力確保「儘可能複用一樣的 tls 鏈接」,大多數狀況下客戶端 watcher 均工做在下層的同一個 tls 鏈接上,僅當這個鏈接發生異常時,纔可能會觸發重連繼而發生 API Server 的切換。其結果就是咱們看到的,當 kubelet 鏈接到其中一個 API Server 後,基本上是不會發生負載切換。爲了解決這個問題,咱們進行了三個方面的優化:
409 - too many requests
提醒客戶端退避;當自身負載超過一個更高的閾值時,經過關閉客戶端鏈接拒絕請求;409
時,嘗試重建鏈接切換 API Server;按期地重建鏈接切換 API Server 完成洗牌;如上圖左下角監控圖所示,加強後的版本能夠作到 API Server 負載基本均衡,同時在顯示重啓兩個節點(圖中抖動)時,可以快速的自動恢復到均衡狀態。
List-Watch 是 Kubernetes 中 Server 與 Client 通訊最核心一個機制,etcd 中全部對象及其更新的信息,API Server 內部經過 Reflector 去 watch etcd 的數據變化並存儲到內存中,controller/kubelets 中的客戶端也經過相似的機制去訂閱數據的變化。
在 List-Watch 機制中面臨的一個核心問題是,當 Client 與 Server 之間的通訊斷開時,如何確保重連期間的數據不丟,這在 Kubernetes 中經過了一個全局遞增的版本號 resourceVersion
來實現。以下圖所示 Reflector 中保存這當前已經同步到的數據版本,重連時 Reflector 告知 Server 本身當前的版本(5),Server 根據內存中記錄的最近變動歷史計算客戶端須要的數據起始位置(7)。
這一切看起來十分簡單可靠,可是...
在 API Server 內部,每一個類型的對象會存儲在一個叫作 storage
的對象中,好比會有:
每一個類型的 storage 會有一個有限的隊列,存儲對象最近的變動,用於支持 watcher 必定的滯後(重試等場景)。通常來講,全部類型的類型共享一個遞增版本號空間(1, 2, 3, ..., n),也就是如上圖所示,pod 對象的版本號僅保證遞增不保證連續。Client 使用 List-Watch 機制同步數據時,可能僅關注 pods 中的一部分,最典型的 kubelet 僅關注和本身節點相關的 pods,如上圖所示,某個 kubelet 僅關注綠色的 pods (2, 5)。
由於 storage 隊列是有限的(FIFO),當 pods 的更新時隊列,舊的變動就會從隊列中淘汰。如上圖所示,當隊列中的更新與某個 Client 無關時,Client 進度仍然保持在 rv=5,若是 Client 在 5 被淘汰後重連,這時候 API Server 沒法判斷 5 與當前隊列最小值(7)之間是否存在客戶端須要感知的變動,所以返回 Client too old version err
觸發 Client 從新 list 全部的數據。爲了解決這個問題,Kubernetes 引入 Watch bookmark
機制:
bookmark 的核心思想歸納起來就是在 Client 與 Server 之間保持一個「心跳」,即便隊列中無 Client 須要感知的更新,Reflector 內部的版本號也須要及時的更新。如上圖所示,Server 會在合適的適合推送當前最新的 rv=12 版本號給 Client,使得 Client 版本號跟上 Server 的進展。bookmark 能夠將 API Server 重啓時須要從新同步的事件下降爲原來的 3%(性能提升了幾十倍),該功能有阿里雲容器平臺開發,已經發布到社區 Kubernetes-1.15 版本中。
除 List-Watch 以外,另一種客戶端的訪問模式是直接查詢 API Server,以下圖所示。爲了保證客戶端在多個 API Server 節點間讀到一致的數據,API Server 會經過獲取 etcd 中的數據來支持 Client 的查詢請求。從性能角度看,這帶來了幾個問題:
Quorum read
,這個查詢開銷是集羣級別,沒法擴展的。
爲了解決這個問題,咱們設計了 API Server 與 etcd 的數據協同機制,確保 Client 可以經過 API Server 的 cache 獲取到一致的數據,其原理以下圖所示,總體工做流程以下:
這個方式並未打破 Client 的一致性模型(感興趣的能夠本身論證一下),同時經過 cache 響應用戶請求時咱們能夠靈活的加強查詢能力,好比支持 namespace nodename/labels 索引。該加強大幅提升了 API Server 的讀請求處理能力,在萬臺規模集羣中典型的 describe node 的時間從原來的 5s 下降到 0.3s(觸發了 node name 索引),其餘如 get nodes 等查詢操做的效率也得到了成倍的增加。
在 10k node 的生產集羣中,Controller 中存儲着近百萬的對象,從 API Server 獲取這些對象並反序列化的開銷是沒法忽略的,重啓 Controller 恢復時可能須要花費幾分鐘才能完成這項工做,這對於阿里巴巴規模的企業來講是不可接受的。爲了減少組件升級對系統可用性的影響,咱們須要儘可能的減少 controller 單次升級對系統的中斷時間,這裏經過以下圖所示的方案來解決這個問題:
經過這個方案,咱們將 controller 中斷時間下降到秒級別(升級時 < 2s),即便在異常宕機時,備僅需等待 leader lease 的過時(默認 15s),無須要花費幾分鐘從新同步數據。經過這個加強,顯著的下降了 controller MTTR,同時下降了 controller 恢復時對 API Server 的性能衝擊。該方案一樣適用於 scheduler。
因爲歷史緣由,阿里巴巴的調度器採用了自研的架構,因時間的關係本次分享並未展開調度器部分的加強。這裏僅分享兩個基本的思路,以下圖所示:
阿里巴巴經過一系列的加強與優化,成功將 Kubernetes 應用到生產環境並達到了單集羣 10000 節點的超大規模,具體包括:
經過這一系列功能加強,阿里巴巴成功將內部最核心的業務運行在上萬節點的 Kubernetes 集羣之上,並經歷了 2019 年天貓 618 大促的考驗。
做者簡介:曾凡鬆(花名:逐靈),阿里云云原生應用平臺高級技術專家。
有豐富的分佈式系統設計研發經驗。在集羣資源調度這一領域,曾負責的自研調度系統管理了數十萬規模的節點,在集羣資源調度、容器資源隔離、不一樣工做負載混部等方面有豐富的實踐經驗。當前主要負責 Kubernetes 在阿里內部的規模化落地,將 Kubernetes 應用於阿里內部的最核心電商業務,提升了應用發佈效率及集羣資源利用率,並穩定支撐了 2018 雙十一 及 2019 618 大促。
本文爲雲棲社區原創內容,未經容許不得轉載。