進擊的Kubernetes調度系統(一):Scheduling Framework

Kubernetes已經成爲目前事實標準上的容器集羣管理平臺。它爲容器化應用提供了自動化部署、運維、資源調度等全生命週期管理功能。經過3年多的快速發展,Kubernetes在穩定性、擴展性和規模化方面都有了長足進步。 尤其是Kubernetes控制平面的核心組件日臻成熟。而作爲決定容器能否在集羣中運行的調度器Kube-scheduler,更是由於長久以來表現穩定,且已能滿足大部分Pod調度場景,逐漸不被開發人員特別關注。

伴隨着Kubernetes在公有云以及企業內部IT系統中廣泛應用,越來越多的開發人員嘗試使用Kubernetes運行和管理Web應用和微服務以外的工作負載。典型場景包括機器學習和深度學習訓練任務,高性能計算作業,基因計算工作流,甚至是傳統的大數據處理任務。此外,Kubernetes集羣所管理的資源類型也愈加豐富,不僅有GPU,TPU和FPGA,RDMA高性能網絡,還有針對領域任務的各種定製加速器,比如各種AI芯片,NPU,視頻編解碼器等。開發人員希望在Kubernetes集羣中能像使用CPU和內存那樣簡單地聲明式使用各種異構設備。

總的來說,圍繞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個階段:

1、scheduler會先執行內置的Filter策略,如果執行失敗的話,會直接標識Pod調度失敗。
2、如何內置的Filter策略執行成功的話,scheduler通過Http調用Extender註冊的webhook, 將調度所需要的Pod和Node的信息發送到到Extender,根據返回filter結果,作爲最終結果。

我們可以發現Extender存在以下問題:

1、調用Extender的接口是HTTP請求,受到網絡環境的影響,性能遠低於本地的函數調用。同時每次調用都需要將Pod和Node的信息進行marshaling和unmarshalling的操作,會進一步降低性能。
2、用戶可以擴展的點比較有限,位置比較固定,無法支持靈活的擴展,例如只能在執行完默認的Filter策略後才能調用。

基於以上介紹,Extender的方式在集羣規模較小,調度效率要求不高的情況下,是一個靈活可用的擴展方案,但是在正常生產環境的大型集羣中,Extender無法支持高吞吐量,性能較差。

Multiple schedulers

Scheduler在Kubernetes集羣中其實類似於一個特殊的Controller,通過監聽Pod和Node的信息,給Pod挑選最佳的節點,更新Pod的spec.NodeName的信息來將調度結果同步到節點。所以對於部分有特殊的調度需求的用戶,有些開發者通過自研Custom Scheduler來完成以上的流程,然後通過和default scheduler同時部署的方式,來支持自己特殊的調度需求。

Custom Scheduler會存在一下問題:

1、如果與default scheduler同時部署,因爲每個調度器所看到的資源視圖都是全局的,所以在調度決策中可能會在同一時刻在同一個節點資源上調度不同的Pod,導致節點資源衝突的問題。
2、有些用戶將調度器所能調度的資源通過Label劃分不同的池子,可以避免資源衝突的現象出現。但是這樣又會導致整體集羣資源利用率的下降。
3、有些用戶選擇通過完全自研的方式來替換default scheduler,這種會帶來比較高的研發成本,以及Kubernetes版本升級後可能存在的兼容性問題。

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擴展點分爲兩個階段:

1、打分:打分階段會對Filter後的節點進行打分,scheduler會調用所配置的打分策略
2、歸一化: 對打分之後的結構在0-100之間進行歸一化處理

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)
    }
}

代碼編譯

$ make

Scheduler啓動

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中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章