得物云原生容器技術探索與落地實踐
一、前言
得物 App 作為互聯網行業的后起之秀,在快速的業務發展過程中基礎設施規模不斷增長,繼而對效率和成本的關注度也越來越高。我們在云原生技術上的推進歷程如圖所示,整體上節奏還是比較快的。
圖片
從 2021 年 8 月開始,我們以提升資源使用率和資源交付效率為目標,開始基于云原生技術建設整個服務體系的高可用性、可觀測性和高運維效率,同時要保證成本可控。在容器化過程中我們遇到了很多的挑戰,包括:如何將存量的服務在保持已有研發流程不變的情況下,做到容器化部署和管理;容器化之后如何做到高效地運維;如何針對不同的業務場景,提供不同的容器化方案等等。此外,通過技術手段實現持續的成本優化是我們的長期目標,我們先后建設落地了畫像系統、混部方案和調度優化等方案。本文把得物在推進云原生容器技術落地過程中相關方案和實踐做一些總結和梳理,歡迎閱讀和交流。
二、云原生應用管理
云原生應用管理方式
容器與 ECS 的資源形態是有差異的,所以會造成在管理流程上也會有不同之處。但是為了盡可能降低容器化帶來的使用體驗上的差異,我們參考業內容器應用 OAM 模型的設計模式,對容器的相關概念做了屏蔽和對等解釋。例如:以“應用集群”的概念代表 CloneSet 工作負載(Kruise 提供的一種 Kubernetes 擴展工作負載);將單個 Pod 約定為一個應用集群的實例;以“應用路由/域名配置”的概念代表針對 Ingress/Service 的設置。
在應用集群的構造上(即如何構造出 Kubernetes 工作負載對象),我們設計了“配置/特征分層”的方案,將一個應用集群所處歸屬的應用、環境組、環境上的配置進行疊加后,使用 Helm 工具渲染生成 Kubernetes 資源對象,提交給容器平臺。
圖片
CI 和 CD 過程均使用這種配置/特征分層的方式,一方面可以解決應用依賴的中間件信息的管理問題(由相應的提供者統一維護);另一方面,這種管理方式可以讓中間件組件/服務變更時按照不同維度進行,整體上降低了配置變更帶來的風險。
Sidecar 容器在應用集群實例中除了扮演“協作者”的角色外,我們還基于它做了權限管理,以便對應在 ECS 形態下的不同用戶的登陸權限,也算是一舉兩得。當然,在容器場景下也是可以定義不同的用戶,賦予不同的角色,但是強依賴基礎鏡像的維護。
圖片
多集群管理方案
云原生場景下的解決方案對應用集群而言本身就是高可用的,比如:容器編排引擎 Kubernetes 中支持 Pod 實例的拓撲分布設置、支持可用區設置、副本數設置、 Service 負載均衡的設計等,這些都能保證應用集群的高可用。那如果單個 Kubernetes 集群不可用了,會有什么的影響呢,該如何解決?多集群管理方案就是我們解決 Kubernetes 的可用性問題的思路。
如果 Kubernetes 控制面不可用了,會導致應用發布受損,較嚴重的情況也會影響容器服務的可用性。所以,為了保證 Kubernetes 的可用性,一方面要保證 Kubernetes 各組件的健壯性,另一方面要適當控制單個 Kubernetes 集群的規模,避免集群過大造成系統性風險升高。我們的解決思路就是“不要把雞蛋放在一個籃子里”,用聯邦的方式管理多個 Kubernetes,將業務分散到不同的 Kubernetes 集群。
聯邦的思想在 Kubernetes 誕生不久就被開始討論,逐步設計實現,從最初社區的 KubeFate V1.0 到 V2.0,再到企業開源的 Karmada、KubeAdmiral 逐漸成熟起來,并實際應用到了生產場景。那如果沒有集群聯邦,多個 Kubernetes 集群就沒法管理了嗎?當然不是的,容器管控平臺其實也能做這件事情,筆者在幾年之前還對此深以為然,但現在已經完全改變看法了。因為在實際的生產落地過程中我們發現,相比在管控中用 if...else/switch 的方式,亦或配置的方式相比,基于 CRD 的方式來管理多集群效率更高、邏輯更清晰。
圖片
圖片
得物在使用聯邦思想管理多 Kubernetes 集群的時候,參考華為開源的 Karmada 解決方案,在此基礎之上做了定制開發。容器管控平臺負責管理應用集群的原始特征和配置,管理 CICD 流程,向 Host Kubernetes 集群發起容器對象管控請求。Host Kubernetes 集群通過 PropagationPolicies 管理工作負載如何分發到 Member Kubernetes 集群,通過 OverridePolicies 管理差異化的配置。單 Kubernetes 集群下我們使用了分批發布的方式來管理應用集群的發布,在引入聯邦管理之后,我們把分批發布的邏輯從容器管控層面下移到了 Host Kubernetes 集群上。為了兼容存量的通過 Kubernetes Service 進行調用的服務,我們在 Member Kubernetes 集群通過自定義的 MCS-Controller 來管理跨集群的 Service/Endpoints 對象,在 Host Kubernetes 層通過 MCS-Validator 做雙重校驗,確保跨集群的 Service 的一致性。
三、容器調度優化與混部
應用畫像
應用服務的研發人員在部署應用集群實例時,通常會申請超過應用集群本身承載業務流量時所要消耗的資源量,這是可以理解的(要確保系統的資源利用率安全水位,防止過載造成系統夯住),但是不同的研發人員對這個“度”把握是不一樣的,因為合理地設置應用集群的資源用量是依賴研發人員經驗的,也就是說主觀性會更強。
為了解決上述問題,業內的做法通常是通過分析應用集群的過往資源利用率數據,來刻畫出應用集群在業務流量下的實際資源利用率曲線,這就是應用畫像。如下圖所示是我們建設的畫像系統的架構框圖,該畫像系統不僅負責應用的畫像分析,也負責宿主機、Kubernetes 集群的畫像分析,用來指導整個容器平臺對資源的管理。
圖片
容器的監控數據通過 Prometheus 方案進行采集和管理,自研的 KubeRM 服務將它作為數據源,周期性計算產出應用畫像、宿主機畫像和 Kubernetes 集群畫像(資源池畫像)。容器平臺部署在線服務服務時,可參考畫像值來配置應用集群的資源規格,這里的畫像值就是指 Pod 的 Request 值,計算公式如下:
Pod Request = 指標周期性利用率 / 安全水位
公式中“指標周期性利用率”是畫像系統通過統計學手段、AI 模型等方法計算分析出的資源指標(CPU/內存/GPU顯存)在實際業務流量下所表現出的周期性的規律。畫像值的生效我們通過以下 4 個策略進行實施:
- 針對 P3/P4 等級的服務,默認在服務部署時生效畫像值。
- 針對非 P3/P4 等級的服務,將畫像值推薦給用戶,由用戶決定部署時是否采用畫像。
- 分資源池設置不同的生效策略(默認生效,或者用戶決定生效)。
- GPU 顯存的畫像不做默認生效,推薦給用戶,讓用戶決定。
交由用戶決定畫像是否生效時,如何讓用戶更傾向于去生效畫像呢?我們使用差異化計費的策略:生效了畫像的應用集群實例按照其 Pod 配置的 Request 值計費,未生效畫像的應用集群實例按照其 Pod 配置的 Limit 值計費。用戶可以根據自己服務的實際情況選擇生效畫像,以降低成本;平臺也因為畫像而拿到了更多可以調度的資源,用于其它更多的場景。
此外,畫像系統也接入了 KubeAutoScale 自動伸縮器,在業務低峰期,可以指導自動伸縮器對部分場景在線服務做副本縮容操作,以便釋放出更多的資源供給其它場景使用(比如:混部任務場景),后面的章節會詳細介紹。
資源預占
當整個容器集群的資源冗余量不是很充足的時候,在以下幾種情況下是會出現 “雖然集群層面總量資源是夠的,但是業務 Pod 卻無法調度”的問題,影響業務發布效率和體驗。
- 在集群中容器實例變更比較頻繁的時候,某個大規格的業務集群在做滾動更新時,釋放的舊的實例很可能被小規格的容器實例所搶占,導致無法調度。
- 研發同學負責 2 個應用服務 A 和 B,它們的規格都是一樣的。為了保證總體成本不變會,選擇將 A 服務的實例縮掉一些,然后擴容 B 服務的實例。因為 Kubernetes 默認調度會按照 Pod 創建時間來依次調度新 Pod,當用戶縮容完 A 服務的實例再去擴容 B 服務實例的時候,A 服務釋放的資源很可能被其他容器實例搶占,導致 B 的實例無法調度。
- 在大促、全鏈路壓測等業務需要緊急擴容的情況下,容器平臺會新擴宿主機節點以滿足業務需求,不曾想新擴容的機器資源卻被那些“小而快(拉起頻繁,執行時間短)”的任務給見縫插針地搶占了,一方面會導致大規格的服務實例無法調度,另一方面還造成了較多的資源碎片。
為了解決以上問題,我們在調度器中自定義實現了資源預占的調度插件(通過 CRD 定義資源預占期望,影響調度決策),用來提升用戶體驗和提高調度效率。
圖片
圖片
平衡調度
為了更好地平衡集群中節點的水位,以避免過熱節點的出現、盡量減少碎片資源等為目標來思考和設計,我們基于 Kubernetes 提供的調度器擴展框架,自定義實現了多個調度插件:
- CoolDownHotNode 插件:給最近調度過 Pod 的節點降低優先級,避免熱點節點。
- HybridUnschedulable 插件:阻止使用彈性資源的 Pod 調度到某些節點上。
- NodeBalance 插件:用于平衡各節點上 CPU Request 值與畫像的比值,平衡各節點 CPU 使用率。
- NodeInfoRt 插件:基于畫像打分數據和實時打分數據優化 Pod 調度。
在實時混部
從今年 1 月份開始,我們著手做在離線混部的落地,一期的目標著眼于將在線服務與 Flink 任務進行混部。之所以選擇 Flink 任務做混部,是因為它與在線服務有一個相似之處,那就是它是一種常駐的離線任務,在它啟動之后如果沒有特殊情況,一般不會下線,這種特質會使得我們的容器集群調度頻次、Pod 的變更程度會低一些,進而對穩定性的挑戰也會小一點,整體混部風險也會低一些。
在沒有混部的情況下,我們的集群整體利用率較低,即便畫像功能能幫助用戶盡可能合理的為自己的服務實例設置資源規格,但對容器平臺而言這依然很被動。所以為了挖掘出可以用來混部的資源,我們為不同等級的服務設置不同的綁核策略。如下表所示定義了 4 種應用類型(LSX、LSR、LS、BE),適用于 P0~P4 范圍和離線任務,綁核策略從完全綁核到部分綁核,再到完全共享。
圖片
離線任務(Flink 任務)屬于 BE 類型,可以使用的資源是在宿主機所有 CPU 核心里面單獨劃分出來的一部分專用 CPU 核心,再加上 LS 的共享 CPU 核心,以及 LSR、LS 類型的應用上共享出來的部分 CPU 核心。
圖片
LSX、LSR 和 LS 類型的應用服務的容器實例均申請使用 Kubernetes 原生的資源 CPU/Memory 資源;BE 類型的任務需要申請使用我們自定義的資源 BE-CPU/BE-Memory 資源。
圖片
基于 Kubernetes 的 Device-Plugin 機制我們自研實現了 Kube-Agent 組件,該組件在集群中的所有節點上以 Damonset 的方式部署,一方面負責根據自定義策略將本節點上可用的 BE 資源上報給 API-Server(通過 Kubelet 組件間接上報),另一方面負責執行 CPU 綁核操作。隨著混部的深入,該組件也承擔了更多的工作內容(例如:執行 CPU 算力壓制操作、參數動態調整操作,執行 VPA 操作等)。
在離線混部
一期的混部在應用級別劃分的基礎上,應用了 CPU 核劃分策略來實現混部。站在 CPU 核心的角度來看,通過 CPU 核劃分策略之后,每個 CPU 核心已經有了自己的負載歸屬,能否充分利用取決于分配到它上面的業務特性。但站在整機的利用率上來看(或者站在整個集群的利用率角度來看),依然有很大的提升的空間。混部二期的時候,我們考慮對BE資源進行二次超賣,以另一種新的自定義資源(OT 資源)進行分配使用。使用 OT 資源的任務不獨占綁核,而是共享劃分給 BE 資源的所有 CPU 核心。
圖片
我們使用 OT 資源來混部 AI 訓練任務、其它數據處理任務,為了消除訓練任務對在線業務的影響,通過以下策略進行保證:
- 設置宿主機安全水位,通過調度插件防止過熱節點出現。
- 通過 CPU Group Identity 進行優先級競爭,保障離線任務的調度優先級絕對低于在線服務。
- 對離線任務進行獨立掛盤,避免影響在線服務的磁盤 IO。
- 夜間時段通過 KubeAutoScaler 進行對在線服務進行彈性縮容,等比例提升內存的空閑率,保障離線任務有足夠的內存資源。
彈性伸縮
- HPA:Horizontal Pod Autoscaling,水平方向的 Pod 副本擴縮容。
- VPA:Vertical Pod Autoscaling,垂直方向的 Pod 規格擴縮容。
Kubernetes 中通過資源對象 HorizontalPodautoScalers 來支持工作負載的水平擴縮容,在較早的版本中只支持 CPU 和內存這兩個資源指標,較新版本中也開始支持自定義指標了。針對 VPA 的需求,目前 Kubernetes 層面還沒有比較穩定的可用功能,因為對一個 Pod 實例做資源規格的調整,會涉及到宿主機上資源賬本的管理問題、監控問題,也會涉及到 Pod 的重建/容器重啟動作,影響面會比較大,目前社區中依然在討論。但企業在 VPA 方面,也都是躍躍欲試,會設計自己的個性化 VPA 方案,本文前述應用畫像功能,就是我們得物在 VPA 方案上探索的第一步。
此外,我們在實際的支撐業務云原生化轉型過程中發現,與通過服務的資源使用率指標來幫助業務來決策服務實例副本數的調整的方式相比,定時擴縮容反而能讓研發同學更有信心,研發同學可以根據自己負責的業務服務的流量特征,來設置定時地縮容或者擴容自己的服務實例數量。
為了滿足彈性伸縮場景的所有需求,我們設計實現了 KubeAutoScaler 組件,用來統一管理 HPA、VPA、定時伸縮等彈性伸縮策略配置。此外,如前所述,該組件與畫像系統相互協作,在混部場景下可以幫助在夜間對部分低流量的服務做縮容操作,釋放更多的資源供離線任務使用。
彈性伸縮方案在 GPU 服務場景幫我們避免了很多的資源浪費,特別是在測試環境。如圖所示,我們為 GPU 服務注入了一個名為 Queue-Proxy 的 Sidecar 容器,用來采集服務流量,當流量低于某個閾值時,會按照比例減少實例數;當流量為 0 并持續了一段時間之后,會完全縮零。當冷啟動時,請求會經過激活器 Activator,激活器再通知 KubeAutoScaler 進行服務擴容。生產環境的部分服務也開啟了這一套機制,但在流量低峰期不會完全縮容至零,會維持一個最小的副本數。
圖片
四、容器資源和成本治理優化
為了更好地提升整體的資源利用、降低基礎設施成本,與技術方案的落地周期長、復雜度高的特點比起來,通過治理方法往往能在較短時間內達到不錯的效果,特別是在應用服務容器化部署改造進行到后期的時候。我們通過以下 5 個方面的治理實踐,降本效果明顯。
機型替換
因為歷史原因,我們的模型推理服務在剛開始的時候使用的是 V100 的機型,該機型顯存較大、GPU 算力較優,更適合用在訓練場景,在推理場景的話有點大材小用了。經過機型對比分析,我們選用了一個性價比較高的 A10 機型,推理服務的成本整體降低了 20% 左右,而且因為 A10 機型配備的 CPU 架構有升級,對前后處理有較高要求的推理服務而言穩定性和性能均有提升。從 V100 切換到 A10,主要的工作在于基礎鏡像的替換,因為部分模型服務可能使用了較低版本的 CUDA,但是 A10 卡的算力需要配備較高的 CUDA。另外,因為兩種卡的 GPU 算力也是有差異的,替換之后需要對推理結果做對比驗證才可以上線。我們基于流量回放的思路,設計了 AB 實驗功能幫助業務做切換測試。
圖片
在使用 CPU 計算資源的場景上,我們對算力要求一般、對 CPU 指令集因無特殊要求、對單核/多核性能無要求的服務,均將其使用的機型從 Intel 的切換到了 AMD 的,整體成本降低 14% 左右。
資源池管理
不得不承認的是容器化初期業務方是占據主動的,業務側會基于穩定性、資源供給量上的考量要求容器平臺獨立建集群,或者資源池,容器平臺也會選擇粗放式管理而應承這些需求。隨著容器化的推進,業務側的信心也會增強,容器平臺對資源的把控程度也會更好,我們逐步采取以下幾個動作來收斂資源的管理,提高整體的資源分配率:
- 冗余量控制:業務的發布是有周期的,我們會根據發布周期,動態調整容器平臺管理的資源冗余量。在保證日常的迭代開發的同時,盡可能縮小冗余量。
- 集群合并:統一規劃 Kubernetes 集群,按照區域(上海、杭州、北京等)、網絡環境類型(測試、預發、生產)、業務形態(普通業務、中間件、基礎設施、管控集群等)等維度討論和決策,下線不必要的集群。
- 資源池合并、規整機型:合并資源需求特征比較相近的資源池(例如:計算型的、內存型的),選擇合適的機型。與業務溝通,下線或者合并利用率過低的小資源池。
- 碎片整理:單靠調度器的優化,在調度時盡可能避免碎片力量有限。加上在線服務的變更頻率一般比較低,如果不做重調度,長時間累積下來,集群中會存在大量碎片。所以,針對多副本的應用集群,在健壯的優雅停機機制基礎上,我們適當進行了一些碎片整理任務(重建 Pod 自由調度、重調度、宿主機騰挪等),有效地減少了資源碎片。
工作負載規格治理
用戶自定義工作負載的規格在云原生場景下也是一個常用的做法,這看似對用戶友好的做法卻對容器平臺造成了一些挑戰,因為如果對用戶設置規格的自由度不做一些限制,很可能出現一些非常不合理的規格設置(例如:6C120G、20C4G),會產生調度碎片、成本分攤計算標準也難統一。
為了解決規格的問題,我們對在線服務的資源規格做了限制,不允許用戶隨意指定,而是由平臺給出規格列表,由用戶選擇使用。規格列表可以分資源池設計、也可以分業務場景設置。針對任務型的工作負載,我們定義了 3 種 CPU 類型的資源規格(普通型、計算型、內存型,分別對應不同的 CPU:內存比例)。針對特殊的任務需求,我們約定了資源規格當中 CPU:內存的范圍。針對使用 GPU 的任務,因每種 GPU 卡的 CPU/內存/顯存規格配比都是不一樣的,我們定義了針對每種 GPU 卡的 CU 單位,用戶只需要選擇相應的 CU,填寫 CU 數量即可。規格約定之后,我們針對不同的規格做了差異化計費,保證了規格申請和成本分攤上的合理性。關于規格的定義和計費標準,詳見下表。
圖片
產品自建
得物的基礎設施是在云上,所以在業務發展過程中,部分服務能力我們是會直接選用云上產品的。算法側的模型訓練任務,最開始的時候就是選用云上產品,隨著容器化的推進,我們自建的 AI 平臺(KubeAI 平臺)逐步承接模型訓練任務,使得訓練任務的成本大幅下降。
自建 KubeAI 平臺,使得我們將訓練使用的資源與在線服務、其它離線任務場景使用的資源納入了統一的管理體系,便于從全局的視角去合理地調度分配資源,為 AI 模型訓練場景拿到更多的可用資源。本文前述 2.5 小節,我們就是通過混部的方式,將在線服務的資源供給了訓練任務使用,當前已經在常態混部。
圖片
多云策略
作為云上用戶,多云策略是我們的長期目標。在多云之間獲得議價主動權、符合合規性要求、獲得更充足的資源供給。尤其今年 4 月份以來,隨著 GPT/AIGC 方面的爆發、政策因素導致單個云商的 GPU 資源對我們供給不足,阻礙業務發展。我們及時采用多云策略,將 GPU 業務分散到不同的云供應商,保障業務正常開展。當然,多云的接入不是一蹴而就的,而是需要分業務場景逐步推進,周期較長,難度較大,我們需要考慮以下問題:
- 梳理業務,找到適合多云的業務場景,或者找到適合在多云之間靈活遷移的業務場景。
- 因不同云供應上的機房可能在不同的區域,所以需要考慮跨地域服務訪問、中間件依賴問題。
- 跨云供應商的數據訪問和傳輸問題,涉及到專線建設、成本問題。
五、云原生AI場景建設
我們期望云原生容器技術的落地是要覆蓋全場景的,要將云原生技術在普通服務、中間件產品和特殊的業務場景上都能發揮其巨大優勢。目前 MySQL、Redis、Miluvs、ElasticSearch 等產品都已經在推進容器化。云原生 AI 場景的建設,我們通過 KubeAI 平臺的建設在持續推進。
圖片
KubeAI 是得物 AI 平臺,是我們將云原生容器技術落地得物全站業務過程中,逐步收集和挖掘公司各業務域在AI模型研究和生產迭代過程中的需求,逐步建設而成的一個云原生 AI 平臺。KubeAI 以模型為主線提供了從模型開發,到模型訓練,再到推理(模型)服務管理,以及模型版本持續迭代的整個生命周期內的解決方案。此外,隨著 AIGC 的火熱發展,我們經過調研公司內部 AI 輔助生產相關需求,上線了 AIGC/GPT 服務,為得物豐富的業務場景提供了 GAI 能力,助力業務效果提升。關于 KubeAI 平臺相關解決方案,我們之前發布過一些文章,歡迎大家閱讀交流,這里不再贅述。
- 一文讀懂得物云原生AI平臺-KubeAI的落地實踐過程
- 得物AI平臺-KubeAI推理訓練引擎設計和實踐
- 得物大模型平臺,業務效果提升實踐
- GPU推理服務性能優化之路
六、展望
云原生容器技術在得物的落地開展還是比較快的,業務覆蓋面也比較廣泛。經過 2 年時間的實踐落地,已經全面深入資源管理系統、預算/成本管理機制、應用服務發布流程、AI 算法等管理體系和業務場景。接下來:
- 在容器化,我們會繼續推進中間件產品的容器化,進一步提升基礎設施的資源效率。
- 我們會繼續鞏固混部方案,繼續探索彈性容量、調度優化等方案,進一步提升資源效率。
- 在穩定性方面,我們會繼續關注容器平臺/Kubernetes 本身的穩定性建設,防范風險,切實保證業務平穩運行。
- 與業務場景一起探索快速接入多云,以及多云之間的快速切換能力,保障業務規模在持續增長的情況下,容器基礎設施切換靈活、堅如磐石。