上車了!一文盡覽Scheduling Framework 應用實踐

 

 

Kubernetes 是目前最受歡迎的⾃動化容器管理平臺,它提供了靈活的聲明式容器編排、自動部署、資源調度等功能。Kube-Scheduler 做爲 Kubernetes 的核心組件之一,主要負責整個集羣資源的調度功能,根據特定的調度算法和策略,將 Pod 調度到最優的工做節點上面去,從而更加合理、充分地利用集羣資源。node

可是隨着 Kubernetes 部署的任務類型愈來愈多,原生 Kube-Scheduler 已經不能應對多樣的調度需求:好比機器學習、深度學習訓練任務對於協同調度功能的需求;高性能計算做業,基因計算工做流對於一些動態資源 GPU、網絡、存儲卷的動態資源綁定需求等。所以自定義 Kubernetes 調度器的需求愈發迫切,本文討論了擴展 Kubernetes 調度程序的各類方法,而後使用目前最佳的擴展方式 Scheduling Framework,演示如何擴展 Scheduler。nginx

01 自定義調度器的方式

02 Scheduling Framework 解析

以下圖所示,調度框架提供了豐富的擴展點,在這幅圖中,Filter 至關於以前 Predicate 預選模塊, Score 至關於 Priority 優選模塊,每個擴展點模塊都提供了接口,咱們能夠實現擴展點定義的接口來實現本身的調度邏輯,並將實現的插件註冊到擴展點。算法

Scheduling Framework 在執行調度流程時,當運行到擴展點時,會調用咱們註冊的插件,經過執行自定義插件的策略,知足調度需求。此外,一個插件能夠在多個擴展點註冊,用以執行更復雜或有狀態的任務。api

Scheduling Framework 每次調度一個 Pod ,都分爲調度週期和綁定週期兩部分來執行,調度週期爲 Pod 選擇一個節點,綁定週期則調用 Apiserver,用選好的 Node,更新 Pod 的 spec.nodeName 字段。調度週期和綁定週期統稱爲 「Scheduling Context」 (調度上下文)。調度週期是串行運行的,同一時間只能有一個 Pod 被調度,是線程安全的;而綁定週期由於須要訪問 Apiserver 的接口,耗時較長,爲了提升調度的效率,須要異步執行,即同一時間,可執行多個 bind 操做,是非線程安全的。安全

若是 Pod 被肯定爲不可調度或存在內部錯誤,那麼調度週期或綁定週期將被停止。Pod 將返回隊列並等待下一次重試。若是一個綁定週期被終止,它將觸發 Reserve 插件中的 UnReserve 方法。服務器

Scheduling Cycle 的擴展點

QueueSort

用於給調度隊列排序,默認狀況下,全部的 Pod 都會被放到一個隊列中,此擴展用於對 Pod 的待調度隊列進行排序,以決定先調度哪一個 Pod,QueueSort 擴展本質上只須要實現一個方法  Less(Pod1, Pod2)  用於比較兩個 Pod 誰更優先得到調度,同一時間點只能有一個 QueueSort 插件生效。微信

PreFilter

用於對 Pod 的信息進行預處理,或者檢查一些集羣或 Pod 必須知足的前提條件,好比 Pod 是否包含指定的 annotations 或 labels,若是 PreFilter 返回了 error ,則調度過程終止。網絡

Filter

用於排除那些不能運行該 Pod 的節點,對於每個節點,調度器將按順序執行 Filter 擴展,若是任何一個 Filter 將節點標記爲不可選,則餘下的 Filter 擴展將不會被執行。若是對默認調度器提供的預選規則不滿意,能夠在配置中禁用默認調度器的預選算法,在這個擴展點只執行本身自定義的過濾邏輯。Node 節點執行 Filter 策略是併發執行的,因此在同一調度週期中屢次調用過濾器。架構

PostFilter

實現此擴展的插件是在 Filter 階段以後被調用,僅當沒有爲 Pod 找到可行的節點時才調用。若是有任何 PostFilter 插件將節點標記爲可調度節點,則後面的 PostFilter 插件就不會被調用了。一個典型的 PostFilter 實現是搶佔,它試圖經過搶佔其餘 Pod 來使 Pod 可調度。併發

PreScore

在預選後被調用,一般用來在 Score 以前進行一些信息生成或者記錄日誌和監控信息

Score

實現此擴展的插件爲已經過過濾階段的全部節點進行打分,調度器將針對每個節點調用 Score 擴展。

NormalizeScore

在 NormalizeScore 階段,調度器將會把每一個 Score 擴展對具體某個節點的評分結果和該擴展的權重合並起來,做爲最終評分結果,評分結果是一個範圍內的整數。若是 Score 或 NormalizeScore 返回錯誤,則調度週期將停止。

Reserve

此擴展點爲 Pod 預留的在要運行節點上的資源,目的是避免調度器在等待 Pod 與節點綁定的過程當中調度新的 Pod 到節點上時,發生實際使用資源超出可用資源的狀況。(由於綁定 Pod 到節點上是異步發生的)。這是調度過程的最後一個步驟,Pod 進入 Reserved 狀態之後,要麼在綁定失敗時,觸發 Unreserve 擴展,要麼在綁定成功時,由 PostBind 擴展結束綁定過程。

Permit

Permit 擴展,發生在 Pod 使用 Reserve 插件預留資源以後, Bind 擴展點 bind 以前,主要有三種策略,批准、拒絕、等待。

1)approve(批准):當全部的 Permit 擴展都批准了 Pod 與節點的綁定,調度器將繼續執行綁定過程

2)deny(拒絕):若是任何一個 Permit 擴展 deny 了 Pod 與節點的綁定,Pod 將被放回到待調度隊列,此時將觸發 Unreserve 擴展

3)wait(等待):若是一個 Permit 擴展返回了 wait,則 Pod 將保持在 Permit 階段,直到被其餘擴展 approve,若是超時事件發生,wait 狀態變成 deny,Pod 將被放回到待調度隊列,此時將觸發 UnReserve 擴展

Binding Cycle 的擴展點

PreBind

擴展用於在 Pod 綁定以前執行某些邏輯。這個插件引入的緣由,是有一些資源,是在不在調度 Pod 時,當即肯定可用的節點的資源,因此調度程序須要確保,這些資源已經成功綁定到選定的節點後,才能將 Pod 調度到此節點。例如,PreBind 擴展能夠將一個基於網絡的數據卷掛載到節點上,以Pod 可使用。若是任何一個 PreBind 擴展返回錯誤,Pod 將被放回到待調度隊列,此時將觸發 Unreserve 擴展。

Bind

Bind 擴展會調用 apiserver 提供的接口,將 Pod 綁定到對應的節點上。

PostBind

PostBind 是一個信息擴展點。成功綁定 Pod 後,將調用 PostBind 插件,可用於清理關聯的資源。

UnReserve

是一個通知性質的擴展,若是爲 Pod 預留了資源,Pod 又在被綁定過程當中被拒絕綁定,則 Unreserve 擴展將被調用。Unreserve 擴展應該釋放已經爲 Pod 預留的節點上的計算資源。在一個插件中,Reserve 擴展和 UnReserve 擴展應該成對出現。

03 使用 Scheduling Framework 自定義 Scheduler

自定義插件須要兩個步驟:

1)實現插件的接口

2)註冊插件並配置插件

3.1 實現插件的接口

這裏咱們實現 QueueSort 擴展點,先看看 QueueSort 擴展點定義的接口:

// 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(*QueuedPodInfo, *QueuedPodInfo) bool}

默認的調度器會優先調度優先級較高的 Pod , 其具體實現的方式是使用 QueueSort 這個插件,默認的實現,是對 Pod 的 Priority 值進行排序,但當優先級相同時,再比較 Pod 的 timestamp ,  timestamp 是 Pod 加入 queue 的時間。咱們如今想先根據 Pod 的 Priority 值進行排序,當 Priority 值相同,再根據 Pod 的 QoS 類型進行排序,最後再根據 Pod 的 timestamp 排序。

Qos 類型以下:

1)Guaranteed : resources limits 和 requests 相等

2)Burstable : resources limits 和 requests 不相等

3)BestEffort : 未設置 resources limits 和 requests

具體是,Guaranteed 優先級 高於 Burstable,Burstable 優先級高於 BestEffort。

插件的實現,其實咱們只須要實現 QueueSortPlugin 的 Less 方法:








package qosimport ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/api/v1/pod" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1")// Name is the name of the plugin used in the plugin registry and configurations.const Name = "QOSSort"// Sort is a plugin that implements QoS class based sorting.type Sort struct{}var _ framework.QueueSortPlugin = &Sort{}// Name returns name of the plugin.func (pl *Sort) Name() string { return Name}// Less is the function used by the activeQ heap algorithm to sort pods.// It sorts pods based on their priorities. When the priorities are equal, it uses// the Pod QoS classes to break the tie.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)) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))}func compQOS(p1, p2 *v1.Pod) bool { p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2) if p1QOS == v1.PodQOSGuaranteed { return true } if p1QOS == v1.PodQOSBurstable { return p2QOS != v1.PodQOSGuaranteed } return p2QOS == v1.PodQOSBestEffort}// New initializes a new plugin and returns it.func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) { return &Sort{}, nil}

注意:一個 Plugin 能夠實現多個擴展點。即在一個 Plugin 中既能夠實現 Filter,又能夠實現 Score,也能夠再實現 PreBind。

3.2 註冊和配置插件

1)註冊指向默認調度器中註冊插件。

2)配置是經過配置來決定哪些插件須要被初始化。

3.2.1 在 Sheduler 中註冊已經實現的 Qos 插件


func main() { rand.Seed(time.Now().UnixNano()) command := app.NewSchedulerCommand( app.WithPlugin(qos.Name, qos.New), ) logs.InitLogs() defer logs.FlushLogs() if err := command.Execute(); err != nil { os.Exit(1) }}

3.2.2 經過配置來使用 Qos 插件,並部署自定義調度器

1)配置

經過配置讓 Sheduler 知道那些插件須要被初始化,以下面指定了 QueueSort 的插件 Qos,其餘的擴展點的插件沒有被指定,則都會 Kube-Scheduler 的默認的實現。能夠看到,schedulerName 字段表明擴展的調度器名稱, plugins 字段中各個擴展點的插件名稱,enable 表明該擴展點關於運行你的插件。

apiVersion: v1kind: ConfigMapmetadata: name: scheduler-config3 namespace: kube-systemdata: scheduler-config.yaml: | apiVersion: kubescheduler.config.k8s.io/v1alpha1 kind: KubeSchedulerConfiguration schedulerName: qos-scheduler leaderElection: leaderElect: true lockObjectName: qos-scheduler lockObjectNamespace: kube-system plugins: queueSort: enabled: - name: "QOSSort"

2)接着爲調度器建立 RBAC

kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata: name: qos-crrules: - apiGroups: - '*' resources: - '*' verbs: - '*' - nonResourceURLs: - '*' verbs: - '*'---apiVersion: v1kind: ServiceAccountmetadata: name: qos-sa namespace: kube-system---kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata: name: qos-crb namespace: kube-systemroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: qos-crsubjects: - kind: ServiceAccount name: qos-sa namespace: kube-system

3)配置調度器的 Deployment 

apiVersion: apps/v1kind: Deploymentmetadata: name: qos-scheduler namespace: kube-system labels: component: qos-schedulerspec: replicas: 1 selector: matchLabels: component: qos-scheduler template: metadata: labels: component: qos-scheduler spec: imagePullSecrets: - name: hybrid-regsecret serviceAccount: qos-sa priorityClassName: system-cluster-critical volumes: - name: scheduler-config3 configMap: name: scheduler-config3 containers: - name: qos-scheduler image: hub.baidubce.com/kun/sxy/qos-scheduler:v1.0.0 imagePullPolicy: Always args: - /qos-sample-scheduler - --config=/scheduler/scheduler-config.yaml - --v=3 resources: requests: cpu: "50m" volumeMounts: - name: scheduler-config3 mountPath: /scheduler

4)使用 kubectl apply 部署後,能夠看到 qos-scheduler 已經啓動了

$ kubectl get pods -n kube-systemNAME READY STATUS RESTARTS AGEqos-scheduler-79c767954f-225mr 1/1 Running 0 44m

5) 使用自定義調度器調度 Pod

在 Pod 中的 spec.schedulerName 上指定 qos-scheduler,自定義調度器將會爲 Pod 執行調度邏輯。

apiVersion: v1kind: Podmetadata: name: test labels: app: testspec: schedulerName: qos-scheduler containers: - image: nginx name: nginx ports: - containerPort: 80

建立後,能夠看到 Pod 已經被正常調度,並啓動成功。

$ kubectl get podsNAME READY STATUS RESTARTS AGEtest 1/1 Running 0 15s

04 使用調度框架的其餘示例

4.1 Coscheduling(協同調度)

有時候,咱們須要使用協同調度,相似於 Kube-batch 的功能(也稱爲「 Gang 調度」)。Gang 調度容許同時安排必定數量的 Pod 。若是 Gang 的全部成員不能同時調度,他們都不該該調度。Scheduling Framework 中的 Gang 調度可使用 「Permit」 插件完成。

1)主調度線程逐個處理 Pod 併爲它們預留節點,每一個 Pod 都會調用准入階段的 Gang 調度插件。

2) 當它發現 Pod 屬於一個 Gang 時,它會檢查 Gang 的屬性。若是沒有足夠的成員按期或處於「等待」狀態,該插件返回「等待」。

3) 當數字達到指望值時,處於等待狀態的全部 Pod 均被批准併發送進行綁定。

4.2 Dynamic Resource Binding(動態資源綁定)

在調度 Pod 時,有一些動態的資源好比 volumn ,還不屬於備選節點的資源,調度程序須要確保此類集羣級資源綁定到選定的節點,而後才能將pod調度到有此類資源的節點的節點。可使用 Scheduling Framework 的 PreBind 擴展點實現插件,完成這種動態資源的綁定功能。

05總結

本文首先分析了幾種擴展調度器方式的優缺點,以後介紹了 Scheduling Framework 的實現原理,以及如何基於 Scheduling Framework 去實現一個自定義插件。Scheduling Framework 做爲目前擴展 Kubernetes 調度器的最佳方式,採用插件和插件擴展點機制,能夠輕鬆對調度器進行擴展、定製。將來,會用更多的調度需求使用 Scheduling Framework 進行擴展。

06落地

Kubernetes 技術已然成爲了容器編排領域的事實標準。百度從 2010 年開始探索容器和集羣管理技術,2016年自主研發的 Matrix 集羣管理系統已經管理了數十萬臺機器和服務。

隨着 Kubernetes 技術的成熟,咱們看到了開源技術強大的生命力,從 2018 年開始嘗試向 Kubernetes 架構演化。試點成功以後,啓動了大規模 Kubernetes 架構融合項目。一方面保留百度在 Matrix 上積累的核心技術能力,另外一方面讓存量業務能夠更低成本的遷移到 Kubernetes 之上。

百度於 2020 年得到 InfoQ 十大雲原生創新技術方案,對百度雲原生來講僅僅是個開始。目前大規模 Kubernetes 融合架構的業務正在百度雲原生各產品技術架構中穩定運行並持續增加,百度雲原生團隊也將會在繼續服務好客戶的同時,利用Kubernetes技術實踐經驗不斷優化產品,更好地助力各行各業的客戶實現基於雲原生架構的數字化轉型。

百度智能云云原平生臺,爲客戶建設容器化和無服務器化的基礎設施,提供企業級的微服務治理能力,同時集成源自百度自身多年實踐的DevOps工具鏈。保障開發者享受到高效、靈活、彈性的開發與運維體驗,助力企業更高效率低風險地構建雲原生應用,普遍應用於金融、互聯網、製造等各行各業的雲原生轉型階段。

重磅!雲原生計算交流羣成立

掃碼添加小助手便可申請加入,必定要備註:名字-公司/學校-地區,根據格式備註,才能經過且邀請進羣。

瞭解更多微服務、雲原生技術的相關信息,請關注咱們的微信公衆號【雲原生計算】

相關文章
相關標籤/搜索