Airbnb 的動態(tài) Kubernetes 集群擴(kuò)縮容
Airbnb 基礎(chǔ)設(shè)施的一個重要作用是保證我們的云能夠根據(jù)需求上升或下降進(jìn)行自動擴(kuò)縮容。我們每天的流量波動都非常大,需要依靠動態(tài)擴(kuò)縮容來保證服務(wù)的正常運(yùn)行。
為了支持?jǐn)U縮容,Airbnb 使用了 Kubernetes 編排系統(tǒng),并且使用了一種基于 Kubernetes 的服務(wù)配置接口[1] 。
本文我們將討論如何使用 Kubernetes Cluster Autoscaler 來動態(tài)調(diào)整集群的大小,并著重介紹了我們?yōu)?Sig-Autoscalsing 社區(qū)[2] 做出的貢獻(xiàn)。這些改進(jìn)增加了可定制性和靈活性,以滿足 Airbnb 獨(dú)特的業(yè)務(wù)需求。
Airbnb 的 Kubernetes 集群
過去幾年中,Airbnb 已經(jīng)將幾乎所有的在線服務(wù)從手動編排的 EC2 實(shí)例遷移到了 Kubernetes。如今,我們在近百個集群中運(yùn)行了上千個節(jié)點(diǎn)來適應(yīng)這些工作負(fù)載。然而,這些變化并不是一蹴而就的。在遷移過程中,隨著新技術(shù)棧上的工作負(fù)載和流量越來越多,我們底層的 Kubernetes 集群也隨之變得越來越復(fù)雜。這些演變可以劃分為如下三個階段:
階段 1:同質(zhì)集群,手動擴(kuò)容 階段 2:多集群類型,獨(dú)立擴(kuò)縮容 階段 3:異構(gòu)集群,自動擴(kuò)縮容
階段 1:同質(zhì)集群,手動擴(kuò)縮容
在使用 Kubernetes 之前,每個服務(wù)實(shí)例都運(yùn)行在其所在的機(jī)器上,通過手動分配足夠的容量來滿足流量增加的場景。每個團(tuán)隊(duì)的容量管理方式都不盡相同,且一旦負(fù)載下降,很少會取消配置。
一開始我們的 Kubernetes 集群的配置相對比較簡單。我們有幾個集群,每個集群都有單獨(dú)的底層節(jié)點(diǎn)類型和配置,它們只運(yùn)行無狀態(tài)的在線服務(wù)。隨著服務(wù)開始遷移到 Kubernetes,我們開始在多租戶環(huán)境中運(yùn)行容器化的服務(wù)。這種聚合方式減少了資源浪費(fèi),并且將這些服務(wù)的容量管理整合到 Kuberentes 控制平面上。在這個階段,我們手動擴(kuò)展我們的集群,但相比之前仍然有著顯著的提升。

階段 2:多集群類型,獨(dú)立擴(kuò)縮容
我們集群配置的第二個階段是因?yàn)楦鄻踊墓ぷ髫?fù)載而出現(xiàn)的,每個試圖在 Kubernetes 上運(yùn)行的工作負(fù)載都有著不同的需求。為了滿足這些需求,我們創(chuàng)建了一個抽象的集群類型。集群類型定義了集群的底層配置,這意味著集群類型的所有集群都是相同的,從節(jié)點(diǎn)類型到集群組件設(shè)置都是相同的。
越來越多的集群類型導(dǎo)致出現(xiàn)了越來越多的集群,我們最初通過手動方式來調(diào)節(jié)每個集群容量的方式,很快就變得崩潰了。為了解決這個問題,我們?yōu)槊總€集群添加了 Kubernetes Cluster Autoscaler[3] 組件。該組件會基于pod requests來動態(tài)調(diào)節(jié)集群的大小。如果一個集群的容量被耗盡,則 Cluster Autoscaler 會添加一個新的節(jié)點(diǎn)來滿足pending狀態(tài)的pods。同樣,如果在一段時間內(nèi)集群的某些節(jié)點(diǎn)的利用率偏低,則Cluster Autoscaler會移除這些節(jié)點(diǎn)。這種方式非常適合我們的場景,為我們節(jié)省了大約5%的總的云開銷,以及手動擴(kuò)展集群的運(yùn)維開銷。

階段 3:異構(gòu)集群,自動擴(kuò)縮容
當(dāng) Airbnb 的幾乎所有在線計(jì)算都轉(zhuǎn)移到 Kubernetes 時,集群類型的數(shù)量已經(jīng)增長到 30 多個了,集群的數(shù)量也增加到了 100 多個。這種擴(kuò)展使得 Kubernetes 集群管理相當(dāng)乏味。例如,在集群升級時需要單獨(dú)對每種類型的集群進(jìn)行單獨(dú)測試。
在第三個階段,我們會通過創(chuàng)建異構(gòu)集群來整合我們的集群類型,這些集群可以通過單個 Kubernetes 控制平面來容納許多不同的工作負(fù)載。首先,這種方式極大降低了集群管理的開銷,因?yàn)閾碛懈?、更通用的集群會減少需要測試的配置數(shù)量。其次,現(xiàn)在大多數(shù) Airbnb 的服務(wù)已經(jīng)運(yùn)行在了 Kubernetes 集群上,每個集群的效率可以為成本優(yōu)化提供一個很大的杠桿。整合集群類型允許我們在每個集群中運(yùn)行不同的工作負(fù)載。這種工作負(fù)載類型的聚合(有些大,有些小)可以帶來更好的封裝和效率,從而提高利用率。通過這種額外的工作負(fù)載靈活性,我們可以有更多的空間來實(shí)施復(fù)雜的擴(kuò)展策略,而不是默認(rèn)的 Cluster Autoscaler 擴(kuò)展邏輯。具體來說就是我們計(jì)劃實(shí)現(xiàn)與 Airbnb 特定業(yè)務(wù)邏輯相關(guān)的擴(kuò)縮容邏輯。

隨著對集群的擴(kuò)展和整合,我們實(shí)現(xiàn)了異構(gòu)(每個集群有多種實(shí)例類型),我們開始在擴(kuò)展期間實(shí)現(xiàn)特定的業(yè)務(wù)邏輯,并且意識到有必要對擴(kuò)縮容的行為進(jìn)行某些變更。下一節(jié)將描述我們是如何修改 Cluster Autoscaler,使其變得更加靈活。
Cluster Autoscaler 的改進(jìn)
自定義 gRPC 擴(kuò)展器
我們對 Cluster Autoscaler 所做的最重要的改進(jìn)是提供了一種新方法來確定要擴(kuò)展的節(jié)點(diǎn)組。在內(nèi)部,Cluster Autoscaler 會維護(hù)一系列映射到不同候選擴(kuò)容對象的節(jié)點(diǎn)組,它會針對當(dāng)前 Pending 狀態(tài)的 pods 執(zhí)行模擬調(diào)度,然后過濾掉不滿足調(diào)度要求的節(jié)點(diǎn)組。如果存在 Pending 的 pods,Cluster Autoscaler 會嘗試通過擴(kuò)展集群來滿足這些 pods。所有滿足 pod 要求的節(jié)點(diǎn)組都會被傳遞給一個名為 Expander 的組件。

Expander 負(fù)責(zé)根據(jù)需求進(jìn)一步過濾節(jié)點(diǎn)組。Cluster Autoscaler 有大量內(nèi)置的擴(kuò)展器選項(xiàng),每個選型都有不同的處理邏輯。例如,默認(rèn)是隨機(jī)擴(kuò)展器,它會隨機(jī)選擇可用的節(jié)點(diǎn)組。另一個是 Airbnb 曾經(jīng)使用過的 優(yōu)先級擴(kuò)展器[4] ,它會根據(jù)用戶指定的分級優(yōu)先級列表來選擇需要擴(kuò)展的節(jié)點(diǎn)組。
當(dāng)我們使用異構(gòu)集群邏輯的同時,我們發(fā)現(xiàn)默認(rèn)的擴(kuò)展器無法在成本和實(shí)例類型選擇方面滿足我們復(fù)雜的業(yè)務(wù)需求。
假設(shè),我們想要實(shí)現(xiàn)一個基于權(quán)重的優(yōu)先級擴(kuò)展器。目前的優(yōu)先級擴(kuò)展器僅允許用戶為節(jié)點(diǎn)組設(shè)置不同的等級,這意味著它會始終以確定的順序來擴(kuò)展節(jié)點(diǎn)組。如果某個等級有多個節(jié)點(diǎn)組,則會隨機(jī)選擇一個節(jié)點(diǎn)組?;跈?quán)重的優(yōu)先級策略可以支持在同一個等級下設(shè)置兩個節(jié)點(diǎn)組,其中 80% 的時間會擴(kuò)展一個節(jié)點(diǎn)組,另外 20% 的時間會擴(kuò)展另一個節(jié)點(diǎn)組。但默認(rèn)并不支持基于權(quán)重的擴(kuò)展器。
除了當(dāng)前支持的擴(kuò)展器的某些限制外,還有一些操作上的問題:
Cluster Autoscaler 的發(fā)布流水線比較嚴(yán)格,在合并到上游之前,需要花大量時間來審核變更。但我們的業(yè)務(wù)邏輯和所需的擴(kuò)展策略是在不斷變化的。能夠滿足當(dāng)前需求的擴(kuò)展器并不一定能夠滿足未來的需求。 我們的業(yè)務(wù)邏輯是與 Airbnb 關(guān)聯(lián)的,其他用戶則沒有這種業(yè)務(wù)邏輯。因此我們實(shí)現(xiàn)的特定邏輯并不一定對上游用戶有用。
所以我們對 Cluster Autoscaler 中的新擴(kuò)展器類型提出了一些要求:
我們希望擴(kuò)展器是可擴(kuò)展的,能夠被其他用戶使用。其他用戶在使用默認(rèn)的 Expanders 可能會遇到類似的限制,我們希望提供一個通用的解決方案,并回饋上游。 我們的解決方案應(yīng)該能夠獨(dú)立于 Cluster Autoscaler 部署,這樣可以讓我們能夠響應(yīng)快速變更的業(yè)務(wù)需求。 我們的解決方案應(yīng)該能夠融入 Kubernetes Cluster Autoscaler 生態(tài)系統(tǒng),這樣就無需一直維護(hù)一個 Cluster Autoscale 的分支。
鑒于這些需求,我們提出了一種設(shè)計(jì),將擴(kuò)展職責(zé)從 Cluster Autoscaler 的核心邏輯中分離出來。我們設(shè)計(jì)了一種可插拔的 自定義擴(kuò)展器[5] ,它實(shí)現(xiàn)了gRPC客戶端(類似 custom cloud provider[6] ),該自定義擴(kuò)展器分為兩個組件。
第一個組件是內(nèi)置到 Cluster Autoscaler 中的 gRPC 客戶端,這個 Expander 與 Cluster Autoscaler 中的其他擴(kuò)展器遵循相同的接口,負(fù)責(zé)將 Cluster Autoscaler 中的有效節(jié)點(diǎn)組信息轉(zhuǎn)換為定義好的 protobuf 格式(見下文),并接收來自gRPC 服務(wù)端的輸出,將其轉(zhuǎn)換回 Cluster Autoscaler 要擴(kuò)展的最終的可選列表。
service Expander {
rpc BestOptions (BestOptionsRequest) returns (BestOptionsResponse)
}message BestOptionsRequest {
repeated Option options;
map<string, k8s.io.api.core.v1.Node> nodeInfoMap;
}message BestOptionsResponse {
repeated Option options;
}message Option {
// ID of node to uniquely identify the nodeGroup
string nodeGroupId;
int32 nodeCount;
string debug;
repeated k8s.io.api.core.v1.Pod pod;
}
第二個組件是 gRPC 服務(wù)端,這需要由用戶實(shí)現(xiàn),該服務(wù)端作為一個獨(dú)立的應(yīng)用或服務(wù)運(yùn)行。通過客戶端傳遞的信息以及復(fù)雜的擴(kuò)展邏輯來選擇需要擴(kuò)容的節(jié)點(diǎn)組。當(dāng)前通過 gRPC 傳遞的 protobuf 消息是 Cluster Autoscaler 中傳遞給 Expander 的內(nèi)容的(略微)轉(zhuǎn)換版本。
在前面的例子中,可以非常容易地實(shí)現(xiàn)加權(quán)隨機(jī)優(yōu)先級擴(kuò)展器,方法是讓服務(wù)器從優(yōu)先級列表中讀取,并通過 confimap 讀取權(quán)重百分比,然后進(jìn)行相應(yīng)的選擇。

我們的實(shí)現(xiàn)還包含一個故障保護(hù)選項(xiàng)。建議使用該選項(xiàng)將 多個擴(kuò)展器[7] 作為參數(shù)傳遞給 Cluster Autoscaler。使用該選擇后,如果服務(wù)端出現(xiàn)故障,Cluster Autoscaler 仍然能夠使用一個備用的擴(kuò)展器進(jìn)行擴(kuò)展。
由于服務(wù)端作為一個獨(dú)立的應(yīng)用運(yùn)行,因此可以在 Cluster Autoscaler 外開發(fā)擴(kuò)展邏輯,且 gRPC 服務(wù)端可以根據(jù)用戶需求實(shí)現(xiàn)自定義,因此這種方案對整個社區(qū)來說也非常有用。
在內(nèi)部,從 2022 年開始,Airbnb 就一直在使用這種方案來擴(kuò)縮容所有的集群,期間一直沒有出現(xiàn)任何問題。它允許我們動態(tài)地選擇何時去擴(kuò)展特定的節(jié)點(diǎn)組來滿足 Airbnb 的業(yè)務(wù)需求,從而實(shí)現(xiàn)了我們開發(fā)一個可擴(kuò)展的自定義擴(kuò)展器。
我們的自定義擴(kuò)展器在今年早些時候被上游 Cluster Autoscaler 接受,并將在下一個版本 (v1.24.0) 版本中可以使用。
總結(jié)
在過去的四年里,Airbnb 在我們的 Kubernetes 集群配置中取得了長足的進(jìn)步。通過在 Cluster Autoscaler 中開發(fā)和引入更加成熟的擴(kuò)展器,我們已經(jīng)能夠?qū)崿F(xiàn)圍繞成本和多實(shí)例類型開發(fā)復(fù)雜的、特定業(yè)務(wù)的擴(kuò)展策略目標(biāo),同時還向社區(qū)貢獻(xiàn)了一些有用的功能。
原文地址:https://medium.com/airbnb-engineering/dynamic-kubernetes-cluster-scaling-at-airbnb-d79ae3afa132
參考資料
基于 Kubernetes 的服務(wù)配置接口: https://medium.com/airbnb-engineering/a-krispr-approach-to-kubernetes-infrastructure-a0741cff4e0c
[2]Sig-Autoscalsing 社區(qū): https://github.com/kubernetes/community/tree/master/sig-autoscaling
[3]Cluster Autoscaler: https://github.com/kubernetes/autoscaler
[4]優(yōu)先級擴(kuò)展器: https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/expander/priority
[5]自定義擴(kuò)展器: https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/expander/grpcplugin
[6]custom cloud provider: https://github.com/kubernetes/autoscaler/blob/68c984472acce69cba89d96d724d25b3c78fc4a0/cluster-autoscaler/proposals/plugable-provider-grpc.md
[7]多個擴(kuò)展器: https://github.com/kubernetes/autoscaler/pull/4233
