进击的Kubernetes调度系统(一):SchedulingFramework
作者:王慶璨 張凱
前言
Kubernetes已經成為目前事實標準上的容器集群管理平臺。它為容器化應用提供了自動化部署、運維、資源調度等全生命周期管理功能。經過3年多的快速發展,Kubernetes在穩定性、擴展性和規模化方面都有了長足進步。 尤其是Kubernetes控制平面的核心組件日臻成熟。而作為決定容器能否在集群中運行的調度器Kube-scheduler,更是由于長久以來表現穩定,且已能滿足大部分Pod調度場景,逐漸不被開發人員特別關注。
伴隨著Kubernetes在公有云以及企業內部IT系統中廣泛應用,越來越多的開發人員嘗試使用Kubernetes運行和管理Web應用和微服務以外的工作負載。典型場景包括機器學習和深度學習訓練任務,高性能計算作業,基因計算工作流,甚至是傳統的大數據處理任務。此外,Kubernetes集群所管理的資源類型也愈加豐富,不僅有GPU,TPU和FPGA,RDMA高性能網絡,還有針對領域任務的各種定制加速器,比如各種AI芯片,NPU,視頻編解碼器等。開發人員希望在Kubernetes集群中能像使用CPU和內存那樣簡單地聲明式使用各種異構設備。
總的來說,圍繞Kubernetes構建一個容器服務平臺,統一管理各種新算力資源,彈性運行多種類型應用,最終把服務按需交付到各種運行環境(包括公共云、數據中心、邊緣節點,甚至是終端設備),已然成為云原生技術的發展趨勢。
阿里云容器服務團隊結合多年Kubernetes產品化與客戶支持經驗,對Kube-scheduler進行了大量擴展和改進,逐步使其在多種場景下依然能穩定、高效地調度復雜工作負載類型。
《進擊的Kubernetes調度系統》系列文章將把我們的經驗、技術思考和實現細節全面地展現給Kubernetes用戶和開發者,期望幫助大家更好地了解Kubernetes調度系統的強大能力和未來發展方向。
早期方案
首先,讓我們來了解一下Kubernetes社區都有過哪些提升調度器擴展能力的方案。
要統一管理和調度異構資源與更多復雜工作負載類型,首先面對挑戰的就是Kube-scheduler。在Kubernetes社區里關于提升調度器擴展能力的討論一直不斷。sig-scheduling給出的判斷是,越多功能加入,使得調度器代碼量龐大,邏輯復雜,導致維護的難度越來越大,很多bug難以發現、處理。而對于使用了自定義調度的用戶來說,跟上每一次調度器功能更新,都充滿挑戰。
在阿里云,我們的用戶遇到了同樣的挑戰。Kubernetes原生調度器循環處理單個Pod容器的固定邏輯,無法及時、簡單地支持用戶在不同場景的需求。所以針對特定的場景,我們會基于原生Kube-scheduler擴展自己的調度策略。
最初對于Kube-scheduler進行擴展的方式主要有兩種,一種是調度器擴展(Scheduler Extender), 另外一種是多調度器(Multiple schedulers)。接下來我們對這兩種方式分別進行介紹和對比。
Scheduler Extender
社區最初提供的方案是通過Extender的形式來擴展scheduler。Extender是外部服務,支持Filter、Preempt、Prioritize和Bind的擴展,scheduler運行到相應階段時,通過調用Extender注冊的webhook來運行擴展的邏輯,影響調度流程中各階段的決策結果。
以Filter階段舉例,執行過程會經過2個階段:
?
?
我們可以發現Extender存在以下問題:
基于以上介紹,Extender的方式在集群規模較小,調度效率要求不高的情況下,是一個靈活可用的擴展方案,但是在正常生產環境的大型集群中,Extender無法支持高吞吐量,性能較差。
Multiple schedulers
Scheduler在Kubernetes集群中其實類似于一個特殊的Controller,通過監聽Pod和Node的信息,給Pod挑選最佳的節點,更新Pod的spec.NodeName的信息來將調度結果同步到節點。所以對于部分有特殊的調度需求的用戶,有些開發者通過自研Custom Scheduler來完成以上的流程,然后通過和default scheduler同時部署的方式,來支持自己特殊的調度需求。
Custom Scheduler會存在一下問題:
Scheduler Extender的性能較差可是維護成本較小,Custom Scheduler的研發和維護的成本特別高但是性能較好,這種情況是開發者面臨這種兩難處境。這時候Kubernetes Scheduling Framework V2橫空出世,給我們帶來魚和熊掌可以兼得的方案。
?
新一代調度框架 Scheduling Framework之解析
社區也逐漸的發現開發者所面臨的困境,為了解決如上問題,使Kube-scheduler擴展性更好、代碼更簡潔,社區從Kubernetes 1.16版本開始, 構建了一種新的調度框架Kubernetes Scheduling Framework的機制。
Scheduling Framework在原有的調度流程中, 定義了豐富擴展點接口,開發者可以通過實現擴展點所定義的接口來實現插件,將插件注冊到擴展點。Scheduling Framework在執行調度流程時,運行到相應的擴展點時,會調用用戶注冊的插件,影響調度決策的結果。通過這種方式來將用戶的調度邏輯集成到Scheduling Framework中。
?
Framework的調度流程是分為兩個階段scheduling cycle和binding cycle. scheduling cycle是同步執行的,同一個時間只有一個scheduling cycle,是線程安全的。binding cycle是異步執行的,同一個時間中可能會有多個binding cycle在運行,是線程不安全的。
scheduling cycle
scheduling cycle是調度的核心流程,主要的工作是進行調度決策,挑選出唯一的節點。
Queue sort
// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins. // These plugins are used to sort pods in the scheduling queue. Only one queue sort // plugin may be enabled at a time. type QueueSortPlugin interface {Plugin// Less are used to sort pods in the scheduling queue.Less(*PodInfo, *PodInfo) bool }Scheduler中的優先級隊列是通過heap實現的,我們可以在QueueSortPlugin中定義heap的比較函數來決定的排序結構。但是需要注意的是heap的比較函數在同一時刻只有一個,所以QueueSort插件只能Enable一個,如果用戶Enable了2個則調度器啟動時會報錯退出。下面是默認的比較函數,可供參考。
// Less is the function used by the activeQ heap algorithm to sort pods. // It sorts pods based on their priority. When priorities are equal, it uses // PodQueueInfo.timestamp. func (pl *PrioritySort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {p1 := pod.GetPodPriority(pInfo1.Pod)p2 := pod.GetPodPriority(pInfo2.Pod)return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp)) }PreFilter
PreFilter在scheduling cycle開始時就被調用,只有當所有的PreFilter插件都返回success時,才能進入下一個階段,否則Pod將會被拒絕掉,標識此次調度流程失敗。PreFilter類似于調度流程啟動之前的預處理,可以對Pod的信息進行加工。同時PreFilter也可以進行一些預置條件的檢查,去檢查一些集群維度的條件,判斷否滿足pod的要求。
Filter
Filter插件是scheduler v1版本中的Predicate的邏輯,用來過濾掉不滿足Pod調度要求的節點。為了提升效率,Filter的執行順序可以被配置,這樣用戶就可以將可以過濾掉大量節點的Filter策略放到前邊執行,從而減少后邊Filter策略執行的次數,例如我們可以把NodeSelector的Filter放到第一個,從而過濾掉大量的節點。Node節點執行Filter策略是并發執行的,所以在同一調度周期中多次調用過濾器。
PostFilter
新的PostFilter的接口定義在1.19的版本會發布,主要是用于處理當Pod在Filter階段失敗后的操作,例如搶占,Autoscale觸發等行為。
PreScore
PreScore在之前版本稱為PostFilter,現在修改為PreScore,主要用于在Score之前進行一些信息生成。此處會獲取到通過Filter階段的節點列表,我們也可以在此處進行一些信息預處理或者生成一些日志或者監控信息。
Scoring
Scoring擴展點是scheduler v1版本中Priority的邏輯,目的是為了基于Filter過濾后的剩余節點,根據Scoring擴展點定義的策略挑選出最優的節點。
Scoring擴展點分為兩個階段:
Reserve
Reserve擴展點是scheduler v1版本的assume的操作,此處會對調度結果進行緩存,如果在后邊的階段發生了錯誤或者失敗的情況,會直接進入Unreserve階段,進行數據回滾。
Permit
Permit擴展點是framework v2版本引入的新功能,當Pod在Reserve階段完成資源預留之后,Bind操作之前,開發者可以定義自己的策略在Permit節點進行攔截,根據條件對經過此階段的Pod進行allow、reject和wait的3種操作。allow表示pod允許通過Permit階段。reject表示pod被Permit階段拒絕,則Pod調度失敗。wait表示將Pod處于等待狀態,開發者可以設置超時時間。
binding cycle
binding cycle需要調用apiserver的接口,耗時較長,為了提高調度的效率,需要異步執行,所以此階段線程不安全。
Bind
Bind擴展點是scheduler v1版本中的Bind操作,會調用apiserver提供的接口,將pod綁定到對應的節點上。
PreBind 和 PostBind
開發者可以在PreBind 和 PostBind分別在Bind操作前后執行,這兩個階段可以進行一些數據信息的獲取和更新。
UnReserve
UnReserve擴展點的功能是用于清理到Reserve階段的的緩存,回滾到初始的狀態。當前版本UnReserve與Reserve是分開定義的,未來會將UnReserve與Reserve統一到一起,即要求開發者在實現Reserve同時需要定義UnReserve,保證數據能夠有效的清理,避免留下臟數據。
實現自己的調度插件
scheduler-plugins
Kubernetes負責Kube-scheduler的小組sig-scheduling為了更好的管理調度相關的Plugin,新建了項目scheduler-plugins?來方便用戶管理不同的插件,用戶可以直接基于這個項目來定義自己的插件。接下來我們以其中的Qos的插件來為例,演示是如何開發自己的插件。
QoS的插件主要基于Pod的?QoS(Quality of Service) class?來實現的,目的是為了實現調度過程中如果Pod的優先級相同時,根據Pod的Qos來決定調度順序,調度順序是: 1. Guaranteed (requests == limits) 2. Burstable (requests < limits) 3. BestEffort (requests and limits not set)
插件構造
首先插件要定義插件的對象和構造函數
// QoSSort is a plugin that implements QoS class based sorting. type Sort struct{}// New initializes a new plugin and returns it. func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {return &Sort{}, nil }然后,根據我們插件要對應的extention point來實現對應的接口,Qos是作用于QueueSort的部分,所以我們要實現QueueSort接口的函數。如下所示,QueueSortPlugin接口只定義了一個函數Less,所以我們實現這個函數即可。
// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins. // These plugins are used to sort pods in the scheduling queue. Only one queue sort // plugin may be enabled at a time. type QueueSortPlugin interface {Plugin// Less are used to sort pods in the scheduling queue.Less(*PodInfo, *PodInfo) bool }實現的函數如下。默認的default QueueSort在比較的時候,首先比較優先級,然后再比較pod的timestamp。我們重新定義了Less函數,在優先級相同的情況下,通過比較Qos來決定優先級。
// Less is the function used by the activeQ heap algorithm to sort pods. // It sorts pods based on their priority. When priorities are equal, it uses // PodInfo.timestamp. func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool {p1 := pod.GetPodPriority(pInfo1.Pod)p2 := pod.GetPodPriority(pInfo2.Pod)return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod)) }func compQOS(p1, p2 *v1.Pod) bool {p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)if p1QOS == v1.PodQOSGuaranteed {return true} else if p1QOS == v1.PodQOSBurstable {return p2QOS != v1.PodQOSGuaranteed} else {return p2QOS == v1.PodQOSBestEffort} }插件注冊
我們在啟動的main函數中注冊自己定義的插件和相應的構造函數
// cmd/main.go func main() {rand.Seed(time.Now().UnixNano())command := app.NewSchedulerCommand(app.WithPlugin(qos.Name, qos.New),)if err := command.Execute(); err != nil {os.Exit(1)} }代碼編譯
$ makeScheduler啟動
kube-scheduler啟動時,配置./manifests/qos/scheduler-config.yaml中kubeconfig的路徑,啟動時傳入集群的kubeconfig文件以及插件的配置文件即可。
$ bin/kube-scheduler --kubeconfig=scheduler.conf --config=./manifests/qos/scheduler-config.yaml至此,相信大家已經通過我們的介紹和示例了解了Kubernetes Scheduling Framework的架構和開發方法。
后續工作
Kubernetes Scheduling Framework作為調度器的新架構方向,在可擴展性和定制化方面進步很大。基于此Kubernetes可以逐步承載更多類型的應用負載了, 一個平臺一套IT架構和技術堆棧的愿景向前演進。同時為了更好的支持數據計算類型的任務遷移到Kubernetes平臺中,我們也在努力將數據計算類型中常用Coscheduling/Gang Scheduling、Capacity Scheduling、Dominant Resource Fairness和多隊列管理等特性,通過Scheduling Framework的插件機制來融入到原生的Kube-scheduler中。
接下來,本系列文章將圍繞AI、大數據處理和高規格計算資源集群等場景,介紹我們是如何開發相應調度器插件的。敬請期待。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的进击的Kubernetes调度系统(一):SchedulingFramework的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “新基建”提速,工业互联网大数据发展迎新
- 下一篇: 万师傅使用云产品,上手简单、开箱即用、省