天天看點

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

📄

文|唐博(花名:博易 螞蟻集團技術專家)

譚崇康(花名:見雲 螞蟻集團進階技術家)

本文 10316 字 閱讀 18 分鐘

螞蟻集團運作着全球最大的 Kubernetes(内部稱為 Sigma) 叢集之一。Kubernetes 社群官方以 5K node 作為 Kubernetes 規模化的事實标準,而螞蟻集團在 2019 年的時候,就已經維護着單叢集規模超過 1W node 的 Kubernetes 叢集。

這不僅僅是單叢集節點量級上的差異,更是業務規模的差異,業務多樣化和複雜度上的差異。

一個形象化的比喻就是,如果官方以及跟着官方的 Kubernetes 使用者能想象到的 Kubernetes 的叢集規模是泰山,那麼螞蟻集團在官方的解決方案之上已經實作了一個珠穆朗瑪峰。

螞蟻集團的 Kubernetes 的演進,從 2018 年至今已經走過了 3 年多的歲月,雖然在 2019 年的時候就建構了萬台叢集的規模,但時至今日,無論是業務形态還是叢集的伺服器都發生了巨大的變化。

  • 首先,當時的叢集萬台節點,主要還是偏小規格的伺服器,而如今都是大機型,雖然機器的數量也是萬台,實際管理的 CPU 數量已經成倍增長。
  • 其次是當時叢集裡面幾乎全量是 Long running 的線上業務,Pod 的建立頻率每天隻有幾千個,如今我們的叢集上幾乎跑滿了流式計算和離線計算業務等按需配置設定的 Pod,是以在 Pod 數量上成倍增長,實際管理的 Pod 數量超過了百萬。
  • 最後,是 Serverless 的業務快速發展,Serverless Pod 的生命周期基本在分鐘級甚至是秒級,叢集每天的 Pod 建立量也超過了幾十萬,伴随着大量的 Kubernetes list watch 和 CRUD 請求,叢集的 apiserver 承受了數倍于以往的壓力。

是以在業務 Serverless 的大背景下,我們在螞蟻啟動了大規模 Sigma 叢集的性能優化方案,根據業務的增長趨勢,我們設定的目标是,建構 1.4W 個節點規模的叢集,同時通過技術優化,期望達成在請求延遲上不會因為規模的原因有所下降,能夠對齊社群标準,即 create/update/delete 請求的天級别 P99 RT 在 1s 之内。

可想而知,挑戰是非常巨大的。

PART. 1

大規模叢集的挑戰

毋庸置疑,大規模叢集帶來了很多挑戰:

  • 随着叢集規模增大,故障的爆炸半徑也将擴大。Sigma 叢集承載了螞蟻集團諸多重要應用,保障叢集的穩定和業務的穩定是最基礎也是優先級最高的要求。
  • 使用者大量的 list 操作,包括 list all,list by namespace,list by label 等,均會随着叢集的規模增大而開銷變大。這些合理或者不合理的 list 請求,将讓 apiserver 的記憶體在短時間内快速增長,出現 OOM 異常,無法對外響應請求。此外,業務方的 list 請求也會因為 apiserver 無法處理請求而不斷重試,造成 apiserver 重新開機後因過載不可恢複服務能力,影響整個叢集的可用性。
  • 大量 List 請求透過 apiserver 直接通路 etcd 服務,也會讓 etcd 執行個體的記憶體劇增而出現 OOM 異常。
  • 随着業務量的增長,特别是離線任務的增多,create/update/delete 等請求的數量也迅速增加,導緻用戶端請求 apiserver 的 RT 極速上升,進而使得排程器和一些控制器因為選主請求逾時而丢主。
  • 業務量增長将加劇 etcd 由于 compact 等操作自身存在的性能問題,而使 etcd 的 P99 RT 暴漲,進而導緻 apiserver 無法響應請求。
  • 叢集中的控制器服務,包括 Kubernetes 社群自帶的控制器例如 service controller,cronjob controller 以及業務的 operator 等,自身存在的性能問題都将在大規模叢集面前被進一步放大。這些問題将進一步傳導到線上業務,導緻業務受損。

如計算機學科的古老格言所說:

「 All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection... and the performance problems. 」

大規模叢集既是照妖鏡,也是試金石。

PART. 2

大規模叢集的收益

誠然,建構一個大規模的 Kubernetes 叢集也提供了諸多收益:

  • 為運作大型服務提供更為便利的基礎設施 ,便于應對業務擴容時大幅飙升的資源需求。例如在雙十一等電商大促活動期間,可以通過擴充現有叢集而不是建立其它小叢集來應對業務的增長。同時叢集管理者可以管理更少的叢集,并且以此來簡化基礎架構管理 。
  • 為大資料和機器學習的離線計算任務提供更多的資源,為分時複用/分時排程等排程手段提供更大的施展空間,讓離線的計算任務在線上業務的低峰期時可以使用更多的資源進行計算,享受極緻彈性和極速傳遞。
  • 還有非常重要的一點,在更大的叢集中可以通過更加豐富的編排排程手段來更為有效地提升叢集整體的資源使用率。

PART. 3

Sigma ApiServer 性能優化

Sigma apiserver 元件是 Kubernetes 叢集的所有外部請求通路入口,以及 Kubernetes 叢集内部所有元件的協作樞紐。apiserver 具備了以下幾方面的功能:

  • 屏蔽後端資料持久化元件 etcd 的存儲細節,并且引入了資料緩存,在此基礎上對于資料提供了更多種類的通路機制。
  • 通過提供标準 API,使外部通路用戶端可以對叢集中的資源進行 CRUD 操作。
  • 提供了 list-watch 原語,使用戶端可以實時擷取到資源中資源的狀态。

我們對于 apiserver 性能提升來說可以從兩個層面進行拆解,分别是 apiserver 的啟動階段和 apiserver 的運作階段。

apiserver 啟動階段 的性能優化有助于:

  • 減少更新變更影響時長/故障恢複時長,減少使用者可感覺的不可用時間,給 Sigma 終端使用者提供優質的服務體驗(面向業務的整體目标是 Sigma 月度可用性 SLO 達到 99.9%,單次故障不可用時間 < 10min)。
  • 減少因為釋出時用戶端重新 list 全量資源而導緻的 apiserver 壓力過大情況出現。

apiserver 運作階段 的性能優化的意義在于:

  • 穩定支援更大規模的 Kubernetes 叢集。
  • 提高 apiserver 在正常平穩運作的狀态中,機關資源的服務能力;即提高可以承受的請求并發和 qps, 降低請求 RT。
  • 減少用戶端的逾時以及逾時導緻的各種問題;在現有資源下提供更多的流量接入能力;

整體優化思路

建構一個大規模的 Kubernetes 叢集以及性能優化不是一件容易的事,如 Google Kubernetes Engine K8s 規模化文章所言:

「The scale of a Kubernetes cluster is like a multidimensional object composed of all the cluster’s resources—and scalability is an envelope that limits how much you can stretch that cube. The number of pods and containers, the frequency of scheduling events, the number of services and endpoints in each service—these and many others are good indicators of a cluster’s scale.

The control plane must also remain available and workloads must be able to execute their tasks.

What makes operating at a very large scale harder is that there are dependencies between these dimensions. 」

也就是說,叢集的規模化和性能優化需要考慮叢集中各個次元的資訊,包括 pod、node,configmap、service、endpoint 等資源的數量,pod 建立/排程的頻率,叢集内各種資源的變化率等等,同時需要考慮這些不同次元之間的互相的依賴關系,不同次元的因素彼此之間構成了一個多元的空間。

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

為了應對如此多的變量對大規模叢集帶來的複雜影響,我們采用了探索問題本質以不變應萬變的方法。為了可以全面而且系統化地對 apiserver 進行優化,我們由下到上把 apiserver 整體分為三個層面,分别為存儲層(storage)、緩存層(cache)、通路層(registry/handler)。

  • 底層的 etcd 是 Kubernetes 中繼資料的存儲服務,是 apiserver 的基石。存儲層提供 apiserver 對 etcd 通路功能,包括 apiserver 對 etcd 的 list watch,以及 CRUD 操作。
  • 中間的緩存層相當于是對 etcd 進行了一層封裝,提供了一層對來自用戶端且消耗資源較大的 list-watch 請求的資料緩存,以此提升了 apiserver 的服務承載能力。同時,緩存層也提供了按條件搜尋的能力。
  • 上面的通路層提供了處理 CRUD 請求的一些特殊邏輯,同時對用戶端提供各種資源操作服務。

針對上面提出的不同層面,一些可能的優化項如下:

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

同時,為了更好地衡量 apiserver 的性能,我們為 Kubernetes apiserver 制定了詳細的 SLO,包括 create/update/delete 等操作的 P99 RT 名額,list 在不同規模資源情況下的 P99 RT 名額等。

同時,在 SLO 的牽引下對 apiserver 進行優化,讓我們可以在一個更大規模的 Kubernetes 叢集下依然為使用者提供更好的 API 服務品質。

緩存層優化

「List 走 watchCache」

由于 apiserver 在從 etcd list 資料時會擷取大量資料,并且進行反序列化和過濾操作,是以會消耗大量記憶體。一些使用者的用戶端包含了不規範的通路 apiserver 的行為,例如某些用戶端可能每隔幾秒就會 list 一次,并且不帶有 resourceversion。這些用戶端對于 apiserver 造成了很大的記憶體壓力,也曾經險些造成叢集故障。為了應對這些不規範的使用者通路,以及減少 apiserver 的 CPU/memory 消耗,我們對 list 操作進行了修改,讓使用者的不規範 list 操作全部走 watchCache。也就是說,使用者在進行 list 操作時,請求将不會透傳到後端的 etcd 服務。

在我們的一個大規模叢集中,apiserver 記憶體會飙升到 400G 導緻幾十分鐘便會出現 OOM,期間 apiserver 對于 etcd 的通路的 RT 也會高達 100s 以上,幾乎不可用。在讓使用者全部 list 操作走 apiserver 的 watchCache 之後,apiserver 的記憶體基本穩定在 100G 左右,有 4 倍的提升,RT 也可以穩定在 50ms 量級。List 走 watchCache 也是出于 list-watch 原語的最終一緻性考慮的,watch 會持續監聽相關資源的資訊,是以不會有資料一緻性的影響。

後續我們也在考慮是否可以把 get 操作也從 watchCache 進行操作,例如等待 watchCache 一定毫秒的時間進行資料同步,以此進一步減小 apiserver 對 etcd 的壓力,同時也可以繼續保持資料一緻性。

「watchCache size 自适應」

在資源變化率(churn rate)比較大的叢集中,apiserver 的 watchCache 大小對 apiserver 的整體穩定性和用戶端通路量多少起着很大的作用。

太小的 watchCache 會使得用戶端的 watch 操作因為在 watchCache 裡面查找不到相對應的 resource vesion 的内容而觸發 too old resource version 錯誤,進而觸發用戶端進行重新 list 操作。而這些重新 list 操作又會進一步對于 apiserver 的性能産生負面的回報,對整體叢集造成影響。極端情況下會觸發 list -> watch -> too old resource version -> list 的惡性循環。相應地,太大的 watchCache 又會對于 apiserver 的記憶體使用造成壓力。

是以,動态地調整 apiserver watchCache 的大小,并且選擇一個合适的 watchCache size 的上限對于大規模大規模叢集來說非常重要。

我們對于 watchCache size 進行了動态的調整,根據同一種資源(pod/node/configmap) 的變化率(create/delete/update 操作的頻次) 來動态調整 watchCache 的大小;并且根據叢集資源的變化頻率以及 list 操作的耗時計算了 watchCache size 大小的上限。

在這些優化和改動之後,用戶端的 watch error(too old resource version)幾乎消失了。

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

「增加 watchCache index」

在分析螞蟻集團的業務之後發現,新計算(大資料實時/離線任務,機器學習離線任務)的業務對于各種資源的 list 有特定的通路模式,spark 和 blink 等業務方有大量的 list by label 操作,也就是通過标簽來查找 pod 的通路量很多。

通過對 apiserver 日志進行分析,我們提取出了各個業務方 list by label 比較多的操作,并且在 watchCache 增加了相應地增加了相關 label 的索引。在對同等規模的資源進行 list by label 操作時,用戶端 RT 可以有 4-5 倍的提升。

下圖為上述 watchCache 優化内容簡介:

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

存儲層優化

在資源更新頻率比較快的情況下,GuaranteedUpdate 會進行大量的重試,同時造成不必要的 etcd 的壓力。Sigma 給 GuaranteedUpdate 增加了指數退避的重試政策,減少了 update 操作的沖突次數,也減少了 apiserver 對于 etcd 的更新壓力。

在大規模高流量叢集中,我們發現 apiserver 的一些不合理的日志輸出會造成 apiserver 嚴重的性能抖動。例如,我們調整了 GuaranteedUpdate/delete 等操作在更新或者删除沖突時的日志輸出級别。這減少了磁盤 io 操作,降低了用戶端通路 apiserver 的請求響應時間。此外,在叢集資源變化率很高的情況下," fast watch slow processing" 的日志也會非常多。這主要是表明 apiserver 從 etcd watch 事件之後,在緩存裡面建構 watchCache 的速率低于從 etcd watch 到事件的速率,在不修改 watchCache 資料結構的情況下暫時是無法改進的。是以我們也對 slow processing 日志級别進行了調整,減少了日志輸出。

接入層優化

Golang profiling 一直是用于對 Go 語言編寫的應用的優化利器。在對 apiserver 進行線上 profiling 的時候,我們也發現了不少熱點,并對其進行了優化。

例如:

  • 在使用者 list event 時可以看到 events.GetAttrs/ToSelectableFields 會占用很多的 CPU,我們修改了 ToSelectableFields, 單體函數的 CPU util 提升 30%,這樣在 list event 時候 CPU util 會有所提升。
攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐
  • 另外,通過 profiling 可以發現,當 metrics 量很大的時候會占用很多 CPU,在削減了 apiserver metrics 的量之後,大幅度降低了 CPU util。
攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐
  • Sigma apiserver 對于鑒權模型采用的是 Node、RBAC、Webhook,對于節點鑒權,apiserver 會在記憶體當中建構一個相對來說很大的圖結構,用來對 Kubelet 對 apiserver 的通路進行鑒權。

當叢集出現大量的資源(pod/secret/configmap/pv/pvc)建立或者變更時,這個圖結構會進行更新;當 apiserver 進行重新開機之後,圖結構會進行重建。在大規模叢集中,我們發現在 apiserver 重新開機過程中,Kubelet 會因為 apiserver 的 node authorizer graph 還在建構當中而導緻部分 Kubelet 請求會因為權限問題而受阻。定位到是 node authorizer 的問題後,我們也發現了社群的修複方案,并 cherry-pick 回來進行了性能上的修複提升。

etcd 對于每個存儲的資源都會有 1.5MB 大小的限制,并在請求大小超出之後傳回 etcdserver: request is too large;為了防止 apiserver 将大于限制的資源寫入 etcd,apiserver 通過 limitedReadBody 函數對于大于資源限制的請求進行了限制。我們對 limitedReadBody 函數進行了改進,從 http header 擷取 Content-Length 字段來判斷 http request body 是否超過了 etcd 的單個資源(pod,node 等)的 1.5MB 的存儲上限。

當然也不是所有方案都會有所提升。例如,我們進行了一些其它編碼方案測試,把 encoding/json 替換成為了 jsoniter。相比之下,apiserver 的 CPU util 雖有降低但是 memory 使用有很大的增高,是以會繼續使用預設的 encoding/json。

etcd 拆分相關優化

除此之外,etcd 拆分對于用戶端通路 apiserver 的請求的 RT 也有很大提升。在大規模叢集中,我們采用了多份拆分方式,其中一份 etcd 是 Pod。在 etcd 拆分的過程中,我們發現拆分出來的 etcd 的 resource version 會小于原有 apiserver 的resource version,是以會導緻用戶端 list-watch apiserver 時長時間 hang ,無法收到新的 Pod 相關的事件。

為了解決這個 etcd 拆分時遇到的問題,我們對 apiserver 的 watch 接口進行了修改,增加了 watch 操作的 timeout 機制。用戶端的 watch 操作最多等待 3s,如果 resource version 不比對,直接傳回 error 讓 用戶端進行重新 list ,以此避免了在 etcd 拆分過程中造成的用戶端因 resource version hang 住的問題。

其它優化

除此之外為了保障 apiserver 的高可用,螞蟻 Kubernetes apiserver 進行了分層分級别的限流,采用了 sentinel-go 加 APF 的限流方案。其中 sentinel-go 來限制總量,進行了 ua 次元,verb 次元等多元度混合限流,防止服務被打垮,APF 來保障不同業務方之間的流量可以公平介入。然而,sentinel-go 中自帶了周期性記憶體采集功能,我們将其關閉之後帶來了一定的 CPU 使用率的提升。

另外,我們也在和用戶端一起優化 apiserver 的通路行為。截止目前,Sigma 和業務方一起對 blink operator(flink on K8s) / tekton pipeline / spark operator 等服務進行了 apiserver 使用方式方法上的代碼優化。

優化效果

下圖分别為我們兩個叢集分鐘級别流量的對比,其中一個叢集的業務由于業務合并有了一個跨越式的增長,叢集的節點規模範圍,超過萬台。可以看出來,随着業務的逐漸上升,叢集的壓力出現了數倍的壓力提升。各類寫請求都有明顯的上升。其中 create 和 delete 請求比較明顯,create 請求由每分鐘 200 個左右上升到了每分鐘 1000 個左右,delete 請求由每分鐘 2.7K 個 上升到了 5.9K 個。經過我們的優化,随着業務方面的遷移逐漸推進,在規模和負載持續上升的背景下,整體叢集運作平穩,基本上達成了叢集優化的預期。

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

基礎資源

在各類型的流量随着業務增長有不同程度的上升的情況下,經過優化,apiserver CPU 使用率下降了約 7%。但是在記憶體上,增多了 20% 左右,這是因為 watchCache 在開啟動态調整後相比之前緩存了更多的不同類别資源(node/pod等)的對象。

緩存更多資源對象帶來的收益是,減少了用戶端的重連并且降低了 list 操作個數,同時也間接減少了用戶端各類操作的 RT,提升了整體叢集和運作的業務的穩定性。當然後續也會繼續針對減少 apiserver 的記憶體使用量進行優化。

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

RT

寫請求的 RT 對于叢集和業務的穩定性是最關鍵的名額之一。經優化過後,用戶端通路 apiserver 的各類寫請求的 P99,P90,P50 RT 均有明顯的下降,并且數值更加趨于平穩,表明 apiserver 在向着高效且穩定的方向發展。

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

Watch error 和 list 個數

不合理的 watchCache 大小會使得用戶端的 watch 操作因為在 watchCache 裡面查找不到相對應的 resource vesion 的内容而觸發 too old resource version 錯誤,也就是下面的 watch error,進而會引發用戶端對 apiserver 的重新 list。

在優化之後,pod 的每分鐘 watch error 的個數下降約 25%,node 的 watch error 下降為 0;相應的 list 操作個數每分鐘也下降了 1000 個以上。

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

PART. 4

未來之路

總體來說,提升一個分布式系統整體的能力,我們可以從以下方面入手:

1.提升系統自身架構,提高穩定性與性能

2.管理系統接入方的流量,優化系統接入方的使用方法和架構

3.對系統依賴的服務進行優化

對應到 apiserver 的性能優化來說,未來我們還将從以下幾個方面繼續深入:

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

針對 apiserver 自身,一些可能的優化點包括:優化 apiserver 啟動總時間,提升 watchCache 建構速度;threadSafeStore 資料結構優化;對 get 操作采用緩存;對 apiserver 存入 etcd 的資料進行壓縮,減小資料大小,借此提升 etcd 性能 等等。

除了優化 apiserver 本身之外,螞蟻 Sigma 團隊也在緻力于優化 apiserver 上下遊的元件。例如 etcd 多 sharding,異步化等高效方案;以及對于各種大資料實時和離線任務的 operator 的整體鍊路的優化。

當然 SLO 的牽引必不可少,同時也會在各個名額的量化上進行增強。隻有這些協調成為一個有機的整體,才能說我們有可能達到為運作在基礎設施上面的業務方提供了優質的服務。

建構大規模叢集道阻且長。

後續我們會繼續在上面列舉的各方面進一步投入,并且為更多的線上任務、離線任務、新計算任務提供更好的運作環境。

同時,我們也将進一步提升方法論,從緩存、異步化、水準拆分/可擴充性、合并操作、縮短資源建立鍊路等大方向上進行下一步的優化。随着叢集規模的繼續增長,性能優化的重要性也會日益凸顯,我們将朝着建構和維護對于使用者來說高效可靠高保障的大規模 Kubernetes 叢集這一目标繼續努力,就像 Kubernetes 這個名字的寓意一樣,為應用程式保駕護航!

-

「參考資料」

.【Kubernetes Scalability thresholds】

https://github.com/kubernetes/community/blob/master/sig-scalability/configs-and-limits/thresholds.md

.【Kubernetes scalability and performance SLIs/SLOs】

https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md

.【Watch latency SLI details】

https://github.com/kubernetes/community/blob/master/sig-scalability/slos/watch_latency.md

.【Bayer Crop Science seeds the future with 15000-node GKE clusters】

https://cloud.google.com/blog/products/containers-kubernetes/google-kubernetes-engine-clusters-can-have-up-to-15000-nodes

.【Openstack benchmark】

https://docs.openstack.org/developer/performance-docs/test_results/container_cluster_systems/kubernetes/API_testing/index.html

「求賢若渴」

螞蟻集團 Kubernetes 叢集排程系統支撐了螞蟻集團線上、實時業務的百萬級容器資源排程, 向上層各類金融業務提供标準的容器服務及動态資源排程能力, 肩負螞蟻集團資源成本優化的責任。我們有業界規模最大 Kubernetes 叢集,最深入的雲原生實踐,最優秀的排程技術。歡迎有意在 Kubernetes/雲原生/容器/核心隔離混部/排程/叢集管理深耕的同學加入,北京、上海、杭州期待大家的加入。

聯系郵箱 [email protected]

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

繼續閱讀