天天看點

Kubernetes 排程系統之 Scheduling Framework一、前言二、早期方案三、Scheduling Framework 解析四、實作自己的排程插件五、後續工作

Kubernetes 排程系統之 Scheduling Framework一、前言二、早期方案三、Scheduling Framework 解析四、實作自己的排程插件五、後續工作

鏡像下載下傳、域名解析、時間同步請點選

阿裡巴巴開源鏡像站

一、前言

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社群都有過哪些提升排程器擴充能力的方案。

要統一管理和排程異構資源與更多複雜工作負載類型,首先面對挑戰的就是Kube-scheduler。在Kubernetes社群裡關于提升排程器擴充能力的讨論一直不斷。sig-scheduling給出的判斷是,越多功能加入,使得排程器代碼量龐大,邏輯複雜,導緻維護的難度越來越大,很多bug難以發現、處理。而對于使用了自定義排程的使用者來說,跟上每一次排程器功能更新,都充滿挑戰。

在阿裡雲,我們的使用者遇到了同樣的挑戰。Kubernetes原生排程器循環處理單個Pod容器的固定邏輯,無法及時、簡單地支援使用者在不同場景的需求。是以針對特定的場景,我們會基于原生Kube-scheduler擴充自己的排程政策。

最初對于Kube-scheduler進行擴充的方式主要有兩種,一種是排程器擴充(Scheduler Extender), 另外一種是多排程器(Multiple schedulers)。接下來我們對這兩種方式分别進行介紹和對比。

1. 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結果,作為最終結果。
Kubernetes 排程系統之 Scheduling Framework一、前言二、早期方案三、Scheduling Framework 解析四、實作自己的排程插件五、後續工作

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

  1. 調用Extender的接口是HTTP請求,受到網絡環境的影響,性能遠低于本地的函數調用。同時每次調用都需要将Pod和Node的資訊進行marshaling和unmarshalling的操作,會進一步降低性能。
  2. 使用者可以擴充的點比較有限,位置比較固定,無法支援靈活的擴充,例如隻能在執行完預設的Filter政策後才能調用。

基于以上介紹,Extender的方式在叢集規模較小,排程效率要求不高的情況下,是一個靈活可用的擴充方案,但是在正常生産環境的大型叢集中,Extender無法支援高吞吐量,性能較差。

2. Multiple schedulers

Scheduler在Kubernetes叢集中其實類似于一個特殊的Controller,通過監聽Pod和Node的資訊,給Pod挑選最佳的節點,更新Pod的spec.NodeName的資訊來将排程結果同步到節點。是以對于部分有特殊的排程需求的使用者,有些開發者通過自研

Custom Scheduler

來完成以上的流程,然後通過和default scheduler同時部署的方式,來支援自己特殊的排程需求。

Kubernetes 排程系統之 Scheduling Framework一、前言二、早期方案三、Scheduling Framework 解析四、實作自己的排程插件五、後續工作

Custom Scheduler會存在一下問題:

  1. 如果與default scheduler同時部署,因為每個排程器所看到的資源視圖都是全局的,是以在排程決策中可能會在同一時刻在同一個節點資源上排程不同的Pod,導緻節點資源沖突的問題。
  2. 有些使用者将排程器所能排程的資源通過Label劃分不同的池子,可以避免資源沖突的現象出現。但是這樣又會導緻整體叢集資源使用率的下降。
  3. 有些使用者選擇通過完全自研的方式來替換default scheduler,這種會帶來比較高的研發成本,以及Kubernetes版本更新後可能存在的相容性問題。

Scheduler Extender的性能較差可是維護成本較小,Custom Scheduler的研發和維護的成本特别高但是性能較好,這種情況是開發者面臨這種兩難處境。這時候Kubernetes Scheduling Framework V2橫空出世,給我們帶來魚和熊掌可以兼得的方案。

Kubernetes 排程系統之 Scheduling Framework一、前言二、早期方案三、Scheduling Framework 解析四、實作自己的排程插件五、後續工作

三、Scheduling Framework 解析

社群也逐漸的發現開發者所面臨的困境,為了解決如上問題,使Kube-scheduler擴充性更好、代碼更簡潔,社群從Kubernetes 1.16版本開始, 建構了一種新的排程架構

Kubernetes Scheduling Framework

的機制。

Scheduling Framework在原有的排程流程中, 定義了豐富擴充點接口,開發者可以通過實作擴充點所定義的接口來實作插件,将插件注冊到擴充點。Scheduling Framework在執行排程流程時,運作到相應的擴充點時,會調用使用者注冊的插件,影響排程決策的結果。通過這種方式來将使用者的排程邏輯內建到Scheduling Framework中。

Kubernetes 排程系統之 Scheduling Framework一、前言二、早期方案三、Scheduling Framework 解析四、實作自己的排程插件五、後續工作

Framework的排程流程是分為兩個階段scheduling cycle和binding cycle. scheduling cycle是同步執行的,同一個時間隻有一個scheduling cycle,是線程安全的。binding cycle是異步執行的,同一個時間中可能會有多個binding cycle在運作,是線程不安全的。

1. 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處于等待狀态,開發者可以設定逾時時間。

2. 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,建立了項目

來友善使用者管理不同的插件,使用者可以直接基于這個項目來定義自己的插件。接下來我們以其中的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中。

提供全面,高效和穩定的鏡像下載下傳服務。釘釘搜尋 ' 21746399 ‘ 加入鏡像站官方使用者交流群。”