重磅解讀:K8s Cluster Autoscaler模塊及對應華爲雲插件Deep Dive

背景信息

基於業務團隊(Cloud BU 應用平臺)在開發Serverless引擎框架的過程中完成的K8s Cluster Autoscaler華爲雲插件。 目前該插件已經貢獻給了K8s開源社區,見下圖:

https://bbs-img.huaweicloud.com/blogs/img/1604349699228035498.png

本文將會涉及到下述內容:

    1. 對K8s Cluster Autoscaler模塊的架構和代碼的Deep Dive,尤其是核心功能點的所涉及的算法的介紹。
    2. K8s Cluster Autoscaler 華爲雲插件模塊的介紹。
    3. 作者本人蔘與K8s開源項目的一點心得。(如:何從開源社區獲取信息和求助,在貢獻開源過程中需要注意的點)

直入主題,這裏不再贅述K8s的基本概念。

什麼是K8s Cluster Autoscaler (CA)

什麼是彈性伸縮?

顧名思義是根據用戶的業務需求和策略,自動調整其彈性計算資源的管理服務,其優勢有:

  1. 從應用開發者的角度:能夠讓應用程序開發者專注實現業務功能,無需過多考慮系統層資源
  2. 從系統運維者的角度:極大的降低運維負擔, 如果系統設計合理可以實現「零運維」
  3. 是實現Serverless架構的基石,也是Serverless的主要特性之一

在具體解釋CA概念之前,咋們先從宏觀上了解一下K8s所支持的幾種彈性伸縮方式(CA只是其中的一種)。

K8s支持的幾種彈性伸縮方式:
注: 爲了描述精確性,介紹下面幾個關鍵概念時,先引用K8S官方解釋鎮一下場 :)。"簡而言之"部分爲作者本人的解讀。

VPA (Vertical Pod Autoscaler)

A set of components that automatically adjust the amount of CPU and memory requested by Pods running in the Kubernetes Cluster. Current state - beta.

簡而言之: 對於某一個POD,對其進行擴縮容(由於使用場景不多,不做過多介紹)

HPAHorizontal Pod Autoscaler) - Pod級別伸縮

A component that scales the number of pods in a replication controller, deployment, replica set or stateful set based on observed CPU utilization (or, with beta support, on some other, application-provided metrics).

簡而言之: 對於某一Node, 根據預先設置的伸縮策略(如CPU, Memory使用率某設定的閥值),增加/刪減其中的Pods。

  • HPA伸縮策略:
    HPA依賴metrics-server組件收集Pod上metrics, 然後根據預先設定的伸縮策略(如:CPU使用率大於50%),來決定擴縮容Pods。計算CPU/Memory使用率時,是取所有Pods的平均值。關於具體如何計算的,點擊此處有詳細算法介紹。
    注:metrics-server默認只支持基於cpu和memory監控指標伸縮策略

     
  • HPA架構圖:
    https://bbs-img.huaweicloud.com/blogs/img/1604349772684086541.png
    圖中下半部門Prometheus監控系統和K8s Prometheus Adapter組件的引入是爲了能夠使用自定義的metrics來設置伸縮策略,由於不是本文的重點,這裏不做過多介紹, K8s官方文檔有個Walkthrough案例一步一步在實操中掌握和理解該模塊。如果用戶只需要依據cpu/memory的監控指標來設置伸縮策略,只要deploy默認的metrics-server組件(其安裝對K8s來說就是一次deployment,非常方便, 上面的鏈接裏有安裝步驟)

CA (Cluster Autoscaler)- Node級別伸縮

A component that automatically adjusts the size of a Kubernetes Cluster so that: all pods have a place to run and there are no unneeded nodes.

簡而言之: 對於K8S集羣,增加/刪除其中的Nodes,達到集羣擴縮容的目的。

Kubernetes(K8s) Cluster Autoscaler(CA)模塊源碼解析:

前面做了這麼多鋪墊,是時候切入本文主題了。下面我將主要從架構代碼兩個維度來揭開CA模塊的神祕面紗,並配合FAQ的形式解答常見的問題。

CA整體架構及所含子模塊

https://bbs-img.huaweicloud.com/blogs/img/1604350088511044545.png

如上圖所示, CA模塊包含以下幾個子模塊, 詳見K8S CA模塊在Github的源碼

  • autoscaler: 核心模塊,包含核心Scale Up和Scale Down功能(對應Github裏 core Package)。
  1. 在擴容時候:其ScaleUp函數會調用estimator模塊來評估所需節點數
  2. 在縮容時:其ScaleDown函數會調用simulator模塊來評估縮容的節點數
  • estimator: 負責計算擴容需要多少Node (對應Github裏 estimator Package)
  • simulator: 負責模擬調度,計算縮容節點 (對應Github裏 simulator Package)
  • expander: 負責擴容時,選擇合適的Node的算法 (對應Github裏 expander Package),可以增加或定製化自己的算法
  • cloudprovider: CA模塊提供給具體雲提供商的接口 (對應Github裏cloudprovider Package)。關於這個子模塊後面也會着重介紹,也是我們華爲雲cloudprovider的擴展點。
  1. autoscaler通過該模塊與具體雲提供商對接(如上圖右下角方框所示 AWS, GCE等雲提供商),並可以調度每個雲提供商提供的Node.
  2. cloudprovider預先設定了一些列接口,供具體的雲提供商實現,來完成調度其提供的Node的目的
     

通過對K8s CA模塊的架構和源碼的織結構的介紹,我總結有以下幾點最佳實踐值得學習和借鑑, 可以適用在任何編程語言上:

  1. SOLID設計原則無處不在,具體反映在:
    1. 每個子模塊僅負責解決某一特定問題 - 單一職責
    2. 每個子模塊都預留有擴展點 - 開閉原則
    3. 每個子模塊的接口隔離做的很清晰 - 接口分離原則
  2. 清晰的子模塊包的組織結構
  3. 插件式的擴展點設計
     

關於CA模塊的用戶常見問題

  1. CA和k8s其他彈性伸縮方式的關係?
    https://bbs-img.huaweicloud.com/blogs/img/1604350117896001214.png
    1. VPA更新已經存在的Pod使用的resources
    2. HPA更新已經存在的Pod副本數
    3. 如果沒有足夠的節點在可伸縮性事件後運行POD,則CA會擴容新的Node到集羣中,之前處於Pending狀態的Pods將會被調度到被新管理的node上
  2. CA何時調整K8S集羣大小?
    1. 何時擴容: 當資源不足,Pod調度失敗,即存在一直處於Pending狀態的Pod(見下頁流程圖), 從Cloud Provider處添加NODE到集羣中
    2. 何時縮容: Node的資源利用率較低,且Node上存在Pod都能被重新調度到其它Node上去
  3. CA多久檢查一次Pods的狀態?
    CA每隔10s檢查是否有處於pending狀態的Pods
    https://bbs-img.huaweicloud.com/blogs/img/1604350143102070039.png

     
  4. 如何控制某些Node不被CA在縮容時刪除?
    1. Node上有Pod被PodDisruptionBudget控制器限制。PodDisruptionBudgetSpec
    2. Node上有命名空間是kube-system的Pods。
    3. Node上Pod被Evict之後無處安放,即沒有其他合適的Node能調度這個pod
    4. Node有annotation: 「cluster-autoscaler.kubernetes.io/scale-down-disabled」: 「true」
    5. Node上存有如下annotation的Pod:「cluster-autoscaler.kubernetes.io/safe-to-evict」: 「false」.點擊見詳情

若想更進一步瞭解和學習,請點擊這裏查看更完整的常見問題列表及解答。

CA模塊源碼解析

由於篇幅關係,只對核心子模塊深入介紹,通過結合核心子模塊與其他子模塊之間如何協調和合作的方式順帶介紹一下其他的子模塊。

CA模塊整體入口處

程序啓動入口處: kubernetes/autoscaler/cluster-autoscaler/main.go

https://bbs-img.huaweicloud.com/blogs/img/1604350218530024567.png

CAautoscaler子模塊

https://bbs-img.huaweicloud.com/blogs/img/1604350225950039773.png

如上圖所示,autoscaler.go是接口,其默認的實現是static_autoscaler.go, 該實現會分別調用scale_down.go和scale_up.go裏的ScaleDown以及ScaleUp函數來完成擴縮容。

那麼問題來了,合適ScaleUp和ScaleDown方法會被調用呢,咋們按照順序一步一步來捋一下, 回到CA整體入口,那裏有一個RunOnce(在autoscaler接口的默認實現static_autoscaler.go裏)方法,會啓動一個Loop 一直運行listen和watch系統裏面是否有那些處於pending狀態的Pods(i.e. 需要協助找到Node的Pods), 如下面代碼片段(static_autoscaler.go裏的RunOnce函數)所示, 值得注意的是,在實際調用ScaleUp之前會有幾個 if/else 判斷是否符合特定的條件:

https://bbs-img.huaweicloud.com/blogs/img/1604350252385006095.png

對於ScaleDown函數的調用,同理,也在RunOnce函數裏, ScaleDown主要邏輯是遵循如下幾步:

  1. 找出潛在的利用率低的Nodes (即代碼裏的scaleDownCandidates數組變量)
  2. 然後爲Nodes裏的Pods找到「下家」(即可以被安放的Nodes,對應代碼裏的podDestinations數組變量)
  3. 然後就是下面截圖所示,幾個if/else判斷符合ScaleDown條件,就執行TryToScaleDown函數

https://bbs-img.huaweicloud.com/blogs/img/1604350279831097266.png

通過上面的介紹結合代碼片段,我們瞭解到何時ScaleUp/ScaleDown函數會被調用。接下來,我們來看看當這兩個核心函數被調用時,裏面具體都發生了什麼。

先來看一下ScaleUp:

https://bbs-img.huaweicloud.com/blogs/img/1604350281196088226.png

從上圖代碼片段,以及我裏面標註的註釋,可以看到,這裏發生了下面幾件事:

  1. 通過cloudprovider子模塊(下面專門介紹這個子模塊)從具體雲提供商處獲取可以進行擴容的的NodeGroups
  2. 把那些Unschedulable Pods按照擴容需求進行分組(對應上面代碼裏的對buildPodEquivalenceGroups函數的調用)
  3. 把第1步得到的所有可用的NodeGroups和第2步得到的待分配的Pods, 作爲輸入,送入給estimator子模塊的裝箱算法(該調用發生對上圖中computeExpansionOption函數調用內部) ,得到一些候選的Pods調度/分配方案。由於estimator子模塊的核心就是裝箱算法,下圖就是實現了裝箱算法的Estimate函數,這裏實現有個小技巧,就是算法開始之前,先調用calculatePodScore把兩維問題降爲一維問題(即Pod對CPU和Memory的需求),然後就是傳統的裝箱算法,兩個for loop來給 Pods找到合適的Node. 至於具體如何降維的,詳見binpacking.estimator.go裏的calculatePodScore函數源碼
    https://bbs-img.huaweicloud.com/blogs/img/1604350333900071974.png

     
  4. 把第3步得到的一些方案,送入給 expander子模塊,得到最優的分配方案(對應代碼片段中ExpanderStrategy.BestOption的函數調用)expander提供了下面截圖中的集中策略,用戶可以通過實現expander接口的BestOption函數,來實現自己的expander策略
    https://bbs-img.huaweicloud.com/blogs/img/1604350350331064614.png

CAcloudprovider子模塊

與具體的雲提供商(i.e. AWS, GCP, Azure, Huawei Cloud)對接來對對應雲平臺上的Node Group(有的雲平臺叫Node Pool)裏的Node進行增刪操作已達到擴縮容的目的。其代碼對應於與之同名的cloudprovider package。詳見Github代碼。 沒個雲提供商,都需要按照k8s約定的方式進行擴展,開發自家的cloudprovider插件,如下圖:

https://bbs-img.huaweicloud.com/blogs/img/1604350361818099004.png

下文會專門介紹華爲雲如何擴展該模塊的

華爲雲cloudprovider插件開發及開源貢獻心得

華爲雲cloudprovider插件如何擴展和開發的?

下圖是華爲cloudprovider插件的大致的代碼結構, 綠色框裏是SDK實際是對CCE雲容器引擎 CCE 進行必要操作所需要的 (對Node Pool/Group裏的Node 進行增加和刪除)。 按理說我們不需要自己寫這一部分,不過由於咋們雲CCE 團隊的SDK實在是不完善,所以我們開發了一些必要的對CCE進行操作的SDK。重點是紅色框中的代碼:

huaweicloud_cloud_provider.go是入口處,其負責總huaweicloud_cloud_config.go讀取配置,並實例化huaweicloud_manager.go對象。huaweicloud_manager.go對象裏通過調用藍色框部門裏的CCE SDK來獲取CCE整體的信息。 CCE整體的信息被獲取到後,可以調用huaweicloud_node_group.go 來完成對該CCE綁定的Node Group/Pool進行Node的擴縮容已達到對整體CCENode伸縮。

https://bbs-img.huaweicloud.com/blogs/img/1604350390734024311.png

如何從開源社區獲取所需資源及開源過程中需要注意的點?

我剛開始接受該項目的時候,一頭霧水,不知道該如何下手。K8s關於這一塊的文檔寫的又不是很清楚。以往的經驗以及K8s Github README中提供的信息,我加入他們的Slack組織,找到相應的興趣組channel( 對應我的情況就是sig-autoscaling channel),提出了我的問題(如下面截圖)。 基於K8s代碼倉的大小,如果沒找到合適的擴展點,幾乎無法改動和擴展的。

劃重點: 現在幾乎所有的開源組中都有Slack羣組,加入找到相應的興趣組,裏面大牛很多,提出問題,一般會有人熱心解答的。 郵件列表也可以,不過我認爲Slack高效實時一點,強烈推薦。對於我本人平常接觸到的開源項目,我一般都會加入到其 Slack中,有問題隨時提問。 當然,中國貢獻的開源項目,好多以微信羣的方式溝通 :)譬如咋們華爲開源出去的微服務框架項目 ServiceComb,我也有加微信羣。總之, 對於開源項目,一定要找到高效的和組織溝通的方式。
https://bbs-img.huaweicloud.com/blogs/img/1604350405607043265.png

https://bbs-img.huaweicloud.com/blogs/img/1604350453890084598.png

另外,對於貢獻代碼過程中,如果使用到了三方開源代碼,由於版權和二次分發的問題,儘量避免直接包含三方源代碼, 如果實在需要,可以對其進行擴展,並在新擴展的文件附上華爲的版權信息與免責聲明。 關於公司的具體要求和政策請參閱文件: 對外開源代碼出口自檢標準與指導書 以及 對外開源流程指導

點擊關注,第一時間瞭解華爲雲新鮮技術~