作業(yè)幫 | Kubernetes 原生調(diào)度器優(yōu)化實踐
調(diào)度系統(tǒng)的本質(zhì)是為計算服務(wù)或任務(wù)匹配合適的資源,使其能夠穩(wěn)定高效地運行,以及在此基礎(chǔ)上進(jìn)一步提高資源使用密度,而影響應(yīng)用運行的因素非常多,比如 CPU、內(nèi)存、IO、差異化的資源設(shè)備等一系列因素都會影響應(yīng)用運行的表現(xiàn)。同時,單獨和整體的資源請求、硬件 / 軟件 / 策略限制、 親和性要求、數(shù)據(jù)區(qū)域、負(fù)載間的干擾等因素以及周期性流量場景、計算密集場景、在離線混合等不同應(yīng)用場景的交織也帶來了決策上的很多變化。
調(diào)度器的目標(biāo)則是快速準(zhǔn)確地實現(xiàn)這一能力,但快速和準(zhǔn)確這兩個目標(biāo)在資源有限的場景下往往會產(chǎn)生矛盾,這需要在二者間權(quán)衡,本文主要分享了作業(yè)幫在實際應(yīng)用 K8s 過程中遇到的問題以及最終探討出的解決方案,希望對廣大開發(fā)者有所幫助。
K8s 默認(rèn)調(diào)度器的整體工作框架可以簡單用下圖概括:

1、第一個控制循環(huán)稱為 Informer Path,主要工作是啟動一系列 Informer,用來監(jiān)聽(Watch)集群中 Pod、Node、Service 等與調(diào)度相關(guān)的 API 對象的變化。比如,當(dāng)一個待調(diào)度 Pod 被創(chuàng)建出來之后,調(diào)度器就會通過 Pod Informer 的 Handler,將這個待調(diào)度 Pod 添加進(jìn)調(diào)度隊列;同時,調(diào)度器還要負(fù)責(zé)對調(diào)度器緩存 Scheduler Cache 進(jìn)行更新,并以這個 cache 為參考信息,來提高整個調(diào)度流程的性能。
2、第二個控制循環(huán)即為對 pod 進(jìn)行調(diào)度的主循環(huán),稱為 Scheduling Path。這一循環(huán)的工作流程是不斷地從調(diào)度隊列中取出待調(diào)度的 pod,運行兩個步驟的算法,來選出最優(yōu) node
在集群的所有節(jié)點中選出所有“可以”運行該 pod 的節(jié)點,這一步被稱為 Predicates;
在上一步選出的節(jié)點中,根據(jù)一系列優(yōu)選算法對節(jié)點打分,選出“最優(yōu)”即得分最高的節(jié)點,這一步被稱為 Priorities。
調(diào)度完成之后,調(diào)度器就會為 pod 的 spec.NodeName 賦值這個節(jié)點,這一步稱為 Bind。而為了不在主流程路徑中訪問 Api Server 影響性能,調(diào)度器只會更新 Scheduler Cache 中的相關(guān) pod 和 node 信息:這種基于樂觀假設(shè)的 API 對象更新方式,在 K8s 中稱為 Assume。之后才會創(chuàng)建一個 goroutine 來異步地向 API Server 發(fā)起更新 Bind 操作,這一步就算失敗了也沒有關(guān)系,Scheduler Cache 更新后就會一切正常。
K8s 默認(rèn)調(diào)度器策略在小規(guī)模集群下有著優(yōu)異表現(xiàn),但是隨著業(yè)務(wù)量級的增加以及業(yè)務(wù)種類的多樣性變化,默認(rèn)調(diào)度策略則逐漸顯露出局限性:調(diào)度維度較少,無并發(fā),存在性能瓶頸,以及調(diào)度器越來越復(fù)雜。
迄今為止,我們當(dāng)前單個集群規(guī)模節(jié)點量千級,pod 量級則在 10w 以上,整體資源分配率超過 60%,其中更是包含了 GPU、在離線混合部署等復(fù)雜場景。在這個過程中,我們遇到了不少調(diào)度方面的問題。
默認(rèn)調(diào)度器,參考的是 workload 的 request 值,如果我們針對 request 設(shè)置的過高,會帶來資源浪費;過低則有可能帶來高峰期 CPU 不均衡差異嚴(yán)重的情況;使用親和策略雖然可以一定程度避免這種,但是需要頻繁填充大量的策略,維護(hù)成本就會非常大。而且服務(wù)的 request 往往不能體現(xiàn)服務(wù)真實的負(fù)載,帶來差異誤差。而這種差異誤差,會在高峰時體現(xiàn)到節(jié)點負(fù)載不均上。
實時調(diào)度器,在調(diào)度的時候獲取各節(jié)點實時數(shù)據(jù)來參與節(jié)點打分,但是實際上實時調(diào)度在很多場景并不適用,尤其是對于具備明顯規(guī)律性的業(yè)務(wù)來說,比如我們大部分服務(wù)晚高峰流量是平時流量的幾十倍,高低峰資源使用差距巨大,而業(yè)務(wù)發(fā)版一般選擇低峰發(fā)版,采用實時調(diào)度器,往往發(fā)版的時候比較均衡,到晚高峰就出現(xiàn)節(jié)點間巨大差異,很多實時調(diào)度器往往在出現(xiàn)巨大差異的時候會使用再平衡策略來重新調(diào)度,高峰時段對服務(wù) POD 進(jìn)行遷移,服務(wù)高可用角度來考慮是不現(xiàn)實的。顯然,實時調(diào)度是遠(yuǎn)遠(yuǎn)無法滿足業(yè)務(wù)場景的。
針對這種情況,需要預(yù)測性調(diào)度方案,根據(jù)以往高峰時候 CPU、IO、網(wǎng)絡(luò)、日志等資源的使用量,通過對服務(wù)在節(jié)點上進(jìn)行最優(yōu)排列組合回歸測算,得到各個服務(wù)和資源的權(quán)重系數(shù),基于資源的權(quán)重打分?jǐn)U展,也就是使用過去高峰數(shù)據(jù)來預(yù)測未來高峰節(jié)點服務(wù)使用量,從而干預(yù)調(diào)度節(jié)點打分結(jié)果。
隨著業(yè)務(wù)越來越多樣,需要加入更多的調(diào)度維度,比如日志。由于采集器不可能無限速率采集日志且日志采集是基于節(jié)點維度。需要平衡日志采集速率,各個節(jié)點差異不可過大。部分服務(wù) CPU 使用量一般但是日志輸出量很大,而日志并不屬于默認(rèn)調(diào)度器決策的一環(huán),所以當(dāng)這些日志量很大的多個服務(wù) pod 在同一個節(jié)點上時,該機器上的日志上報就有可能出現(xiàn)部分延遲。
該問題顯然需要對調(diào)度決策補全,我們擴展了預(yù)測調(diào)度打分策略,添加了日志的決策因子,將日志也作為節(jié)點的一種資源,并根據(jù)歷史監(jiān)控獲取到服務(wù)對應(yīng)的日志使用量來計算分?jǐn)?shù)。
隨著業(yè)務(wù)復(fù)雜度進(jìn)一步上升,在高峰時段出現(xiàn),會有大量定時任務(wù)和集中大量彈性擴縮,大批量(上千 POD)同時調(diào)度導(dǎo)致調(diào)度時延上漲,這兩者對調(diào)度時間比較敏感,尤其對于定時任務(wù)來說,調(diào)度延時的上漲會被明顯感知到,原因是 K8s 調(diào)度 pod 本身是對集群資源的分配,反應(yīng)在調(diào)度流程上則是預(yù)選和打分階段是順序進(jìn)行的。如此一來,當(dāng)集群規(guī)模大到一定程度時,大批量更新就會出現(xiàn)可感知的 pod 調(diào)度延遲。
解決吞吐能力低下最直接的方法就是串行改并行,對于資源搶占場景,盡量細(xì)化資源域,資源域之間并行?;谝陨喜呗?,我們拆分出了獨立的 Job 調(diào)度器,同時使用 Serverless 作為 Job 運行的底層資源。K8s Serverless 為每一個 Job POD 單獨申請了獨立的 POD 運行 sanbox,也就是任務(wù)調(diào)度器,完整并行。以下為對比圖:

原生調(diào)度器在晚高峰下節(jié)點 CPU 使用率

優(yōu)化后調(diào)度器在晚高峰下節(jié)點 CPU 使用率
Work 節(jié)點資源、GPU 資源、Serverless 資源是我們集群異構(gòu)資源的三類資源域,這三種資源上運行的服務(wù)存在天然差異,我們使用 forecast-scheduler、gpu-scheduler、job-schedule 三個調(diào)度器來管理這三種資源域上的 Pod 調(diào)度情況。
預(yù)測調(diào)度器管理大部分在線業(yè)務(wù),其中擴展了資源維度,添加了預(yù)測打分策略。
GPU 調(diào)度器管理 GPU 資源機器的分配,運行在線推理和離線訓(xùn)練,兩者的比例處于長期波動中,高峰期間離線訓(xùn)練縮容、在線推理擴容;非高峰期間離線訓(xùn)練擴容、在線推理縮容;同時處理一些離線圖片任務(wù)來復(fù)用 GPU 機器上比較空閑的 CPU 等資源。
Job 調(diào)度器負(fù)責(zé)管理定時任務(wù)調(diào)度,定時任務(wù)量大且創(chuàng)建銷毀頻繁,資源使用非常碎片化,而且對時效性要求更高;所以我們將任務(wù)盡量調(diào)度到 Serverless 服務(wù)上,壓縮集群中為了能容納大量任務(wù)而冗余的機器資源,提升資源利用率。

更細(xì)粒度的資源域劃分,將資源域劃分至節(jié)點級別,節(jié)點級別加鎖來進(jìn)行。
資源搶占和重調(diào)度。正常場景下,當(dāng)一個 Pod 調(diào)度失敗,這個 Pod 會保持在 pending 的狀態(tài),等待 Pod 更新或者集群資源發(fā)生變化進(jìn)行重新調(diào)度,但是 K8s 調(diào)度器依然存在一個搶占功能,可以使得高優(yōu)先級 Pod 在調(diào)度失敗時,擠走某個節(jié)點上的部分低優(yōu)先級 Pod 以保證高優(yōu)先級 Pod 的正常運行,迄今為止我們并沒有使用調(diào)度器的搶占能力,即使我們通過以上多種策略來加強調(diào)度的準(zhǔn)確性,但依然無法避免部分場景下由于業(yè)務(wù)帶來的不均衡情況,這種非正常場景中,重調(diào)度的能力就有了用武之地,也許重調(diào)度將會成為日后針對異常場景的一種自動修復(fù)方式。
- END -
?推薦閱讀? Kubernetes 企業(yè)容器云平臺運維實戰(zhàn)? 這6個 Linux 命令,每個都很炫酷! Gitlab+Jenkins+k8s+Helm 的自動化部署實踐 一名運維小哥對運維規(guī)則的10個總結(jié),收藏起來 終于明白了 DevOps 與 SRE 的區(qū)別! Kubernetes上生產(chǎn)環(huán)境后,99%都會遇到這2個故障 K8s kubectl 常用命令總結(jié)(建議收藏) Kubernetes 的這些核心資源原理,你一定要了解 我在創(chuàng)業(yè)公司的 “云原生” 之旅 基于Nginx實現(xiàn)灰度發(fā)布與AB測試 編寫 Dockerfile 最佳實踐 12年資深運維老司機的成長感悟 搭建一套完整的企業(yè)級 K8s 集群(二進(jìn)制方式)
點亮,服務(wù)器三年不宕機


