亞寵展、全球寵物產業風向標——亞洲寵物展覽會深度解析
1222
2022-05-29
目錄
文章目錄
目錄
調度系統
Kubernetes 調度器的設計
Kubernetes 調度器的工作流
Kubernetes 調度系統的未來
Scheduler Extender(調度器擴展)
Multiple Schedulers(多調度器)
Kubernetes Scheduling Framework(調度框架)
Scheduling Cycle
Binding Cycle
基于 scheduler-plugins 實現定制化的調度插件
Coscheduling/Gang scheduling
為什么需要 Gang Scheduling?
Coscheduling Plugin
Binpack Scheduling
為什么需要 Binpack Scheduling?
Binpack Scheduling Plugin
調度系統
通用調度的定義是指:基于某種方法將某項任務分配到特定資源以完成相關工作,其中任務可以是虛擬計算元素,如線程、進程或數據流,特定資源一般是指處理器、網絡、磁盤等,調度器則是完成這些調度行為的具體實現。使用調度器的目的是實現用戶共享系統資源的同時,降低等待時間,提高吞吐率以及資源利用率。
構建大規模集群(如數據中心規模)的成本非常之高,因此精心設計調度器就顯得尤為重要。
調度器的主要工作是將資源需求與資源提供方做全局最優的匹配。所以一方面調度器的設計需要了解不同類型的資源拓撲,另一方面還需要對工作負載有充分的認識。了解不同類型的資源拓撲,充分掌握環境拓撲信息能夠使調度工作更充分的利用資源(如經常訪問數據的任務如果距數據近可以顯著減少執行時間),并且可以基于資源拓撲信息定義更加復雜的策略。但全局資源信息的維護消耗會限制集群的整體規模和調度執行時間,這也讓調度器難以擴展,從而限制集群規模。
另一方面,由于不同類型的工作負載會有不同的甚至截然相反的特性,調度器還需要對工作負載有充分的認識,例如:服務類任務,資源需求少,運行時間長,對調度時間并不敏感;而批處理類任務,資源需求大,運行時間短,任務可能相關,對調度時間要求較高。 同時,調度器也要滿足使用方的特殊要求。如任務盡量集中或者分散,保證多個任務同時進行等。
總的來說,好的調度器需要平衡好單次調度(調度時間,質量),同時要考慮到環境變化對調度結果的影響,保持結果最優(必要時重新調度),保證集群規模,同時還要能夠支持用戶無感知的升級和擴展。調度的結果需要滿足但不限于下列條件,并最大可能滿足盡可能優先級較高的條件:
資源使用率最大化
滿足用戶指定的調度需求
滿足自定義優先級要求
調度效率高,能夠根據資源情況快速做出決策
能夠根據負載的變化調整調度策略
充分考慮各種層級的公平性
對于資源的調度,一定會涉及到鎖的應用,不同類型鎖的選擇將直接決定調度器的使用場景。類似 Mesos 的兩層調度器,一般采用悲觀鎖的設計實現方式,即:當資源全部滿足任務需要時啟動任務,否則將增量繼續申請更多的資源直到調度條件滿足;而共享狀態的調度器,會考慮使用樂觀鎖的實現方式,如 Kubernetes 默認調度器是基于樂觀鎖進行設計的。
通過一個簡單的例子,比較下悲觀鎖和樂觀鎖處理邏輯的不同,假設有如下的一個場景:
作業 A 讀取對象 O
作業 B 讀取對象 O
作業 A 在內存中更新對象 O
作業 B 在內存中更新對象 O
作業 A 寫入對象 O 實現持久化
作業 B 寫入對象 O 實現持久化
悲觀鎖的設計是對 O 實現獨占鎖,直到 A 完成對 O 的更新并寫入持久化數據之前,阻斷其他讀取請求;而樂觀鎖的設計則是對 O 實現共享鎖,假設所有的工作都能夠正常完成,直到有沖突產生,記錄沖突的發生并拒絕沖突的請求。
樂觀鎖一般會結合資源版本實現,同樣是上述中的例子,當前 O 的版本為 v1,A 首先完成對 O 的寫入持久化操作,并標記 O 的版本為 v2,B 在更新時發現對象版本已經變化,則會取消更改。
注:樂觀鎖和悲觀鎖是兩種思想,用于解決并發場景下的數據競爭問題。
樂觀鎖:在操作數據時非常樂觀,認為別人不會同時修改數據。因此樂觀鎖不會上鎖,只是在執行更新的時候判斷一下在此期間別人是否修改了數據:如果別人修改了數據則放棄操作,否則執行操作。
悲觀鎖:在操作數據時比較悲觀,認為別人會同時修改數據。因此操作數據時直接把數據鎖住,直到操作完成后才會釋放鎖;上鎖期間其他人不能修改數據。
Kubernetes 調度器的設計
Kubernetes 的調度設計采用了兩層調度架構,基于全局狀態進行調度,通過樂觀鎖控制資源歸屬,同時支持多調度器。
兩層架構幫助調度器屏蔽了很多底層實現細節,將策略和限制分別實現,同時過濾可用資源,讓調度器能夠更靈活適應資源變化,滿足用戶個性化的調度需求。相比單體架構而言,不僅更容易添加自定義規則、支持集群動態伸縮,同時對大規模集群有更好的支持(支持多調度器)。
相比于使用悲觀鎖和部分環境視圖的架構,基于全局狀態和樂觀鎖實現的好處是:調度器可以看到集群所有可以支配的資源,然后搶占低優先級任務的資源,以達到策略要求的狀態。它的資源分配更符合策略要求,避免了作業囤積資源導致集群死鎖的問題。當然這會有搶占任務的開銷以及沖突導致的重試,但總體來看資源的使用率更高了。
Kubernetes 調度器的工作流
Kubernetes Scheduler 的工作流程如下圖所示。調度器的工作本質是通過監聽 Pod 的創建、更新、刪除等事件,循環遍歷地完成每個 Pod 的調度流程。如果調度過程順利,則基于預選和優選策略,完成 Pod 和 Node 的綁定,最終通知 kubelet 完成 Pod 啟動的過程;如果遇到錯誤的調度過程,通過優先級搶占的方式,獲取優先調度的能力,進而重新進入調度循環的過程,等待成功調度。
Kubernetes 調度系統的未來
伴隨著 Kubernetes 在公有云以及企業內部 IT 系統中廣泛應用,越來越多的開發人員嘗試使用 Kubernetes 運行和管理 Web 應用和微服務以外的工作負載。典型場景包括:機器學習和深度學習訓練任務,高性能計算作業,基因計算工作流,甚至是傳統的大數據處理任務。此外,Kubernetes 集群所管理的資源類型也愈加豐富,不僅有 GPU,TPU 和 FPGA,RDMA 高性能網絡,還有針對領域任務的各種定制加速器,比如各種 AI 芯片,NPU,視頻編解碼器等。
開發人員希望在 Kubernetes 集群中能像使用 CPU 內存那樣簡單地聲明和使用各種異構設備。
總的來說,圍繞 Kubernetes 構建一個容器服務平臺,統一管理各種新算力資源,彈性運行多種類型應用,最終把服務按需交付到各種運行環境(包括公共云、數據中心、邊緣節點,甚至是終端設備),已然成為云原生技術的發展趨勢。
Scheduler Extender(調度器擴展)
社區最初提供的方案是通過 Extender 的形式來擴展 scheduler。Extender 是外部服務,支持 Filter、Preempt、Prioritize 和 Bind 的擴展。Scheduler 運行到相應階段時,通過調用 Extender 注冊的 webhook 來運行擴展的邏輯,影響調度流程中各階段的決策結果。
以 Filter 階段舉例,執行過程會經過 2 個階段:
Scheduler 先執行內置的 Filte r策略,如果執行失敗的話,會直接標識 Pod 調度失敗;
如果內置的 Filter 策略執行成功的話,Scheduler 通過 HTTP 調用 Extender 注冊的 webhook,將調度所需要的 Pod 和 Node 的信息發送到 Extender,根據返回的結果,作為最終結果。
可以發現 Extender 存在以下問題:
調用 Extender 的通信協議是 HTTP,受到網絡環境的影響,性能遠低于本地的函數調用。同時每次調用都需要將 Pod 和 Node 的信息進行 marshaling 和 unmarshalling 的操作,會進一步降低性能;
擴展點受限,位置比較固定,無法支持靈活的擴展,只能在執行完 Default Filter 策略之后調用。
可見,Scheduler Extender 適用于集群規模較小,調度效率要求不高的場景。但是在大型集群中,Extender 無法支持高吞吐量,性能較差。
Multiple Schedulers(多調度器)
Scheduler 通過監聽 Pod 和 Node 的信息,給 Pod 挑選最佳的 Node,更新 Pod 的 spec.NodeName 的信息來將調度結果同步到 Node。所以對于部分有特殊的調度需求的用戶,可以通過自研 Custom Scheduler 來完成以上的流程,然后通過和 Default Scheduler 同時部署的方式,來支持自己特殊的調度需求。
Custom Scheduler 會存在以下問題:
如果與 Default Scheduler 同時部署,因為每個調度器所看到的資源視圖都是全局的,所以在調度決策中可能會出現同一時刻、同一個節點資源上調度不同的 Pod,導致節點資源沖突的問題;
有些用戶將調度器所能調度的資源通過 Label 劃分不同的 Pool,可以避免資源沖突的現象出現。但是這樣又會導致整體集群資源利用率的下降;
有些用戶選擇通過完全自研的方式來替換 Default Scheduler,這種會帶來比較高的研發成本,以及 Kubernetes 版本升級的兼容性問題。
Kubernetes Scheduling Framework(調度框架)
從 Kubernetes 1.16 版本開始,社區構建了新一代調度框架 Kubernetes Scheduling Framework V2。
Scheduling Framework 在原有的調度流程中,增加了豐富的擴展點接口,開發者可以通過實現擴展點所定義的接口來實現插件,將插件注冊到擴展點。Scheduling Framework 在執行調度流程時,運行到相應的擴展點時,會調用用戶注冊的插件,進而影響調度決策的結果。通過這種方式來將用戶的調度邏輯集成到 Scheduling Framework 中。
Framework 的調度流程是分為兩個階段:
Scheduling Cycle:同步執行,同一個時間只有一個 scheduling cycle,是線程安全的;
Binding Cycle:異步執行,同一個時間中可能會有多個 binding cycle在運行,是線程不安全的。
Scheduling Cycle
Scheduling Cycle 是調度的核心流程,主要的工作是進行調度決策,挑選出唯一的 Node。
Queue Sort:Scheduler 中的優先級隊列是通過 heap(堆)來實現的,我們可以在 QueueSortPlugin 中定義 heap 的比較函數來決定的優先級排序。需要注意的是,heap 的比較函數同一時刻只能存在一個,即:QueueSort 插件只能 Enable 一個,如果 Enable 了 2 個則 kube-scheduler 在啟動時會報錯退出。
// 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 } // 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)) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PreFilter:PreFilter 是調度流程啟動之前的預處理,可以對 Pod 的信息進行加工。同時 PreFilter 也可以進行一些預置條件的檢查,去檢查一些集群維度的條件,判斷否滿足 Pod 的要求。在 Scheduling Cycle 開始時就被調用,只有當所有的 PreFilter 插件都返回 success 時,才能進入下一個階段。否則,Pod 直接被拒絕掉,此次調度流程失敗。
Filter:Filter 插件是 Scheduler v1 版本中的 Predicate 的邏輯,用來過濾不滿足 Pod 調度要求的 Node。為了提升效率,Filter 的執行順序可以被配置,這樣用戶就可以將可以過濾掉大量節點的 Filter 策略放到前邊執行,從而減少后續 Filter 策略執行的次數。例如:我們可以把 NodeSelector 的 Filter 放到第一個,從而過濾掉大量的節點。
PostFilter:新的 PostFilter 的接口定義在 1.19 的版本發布,用于處理當 Pod 在 Filter 階段失敗后的操作,例如:搶占,Autoscale 觸發等行為。
PreScore:PreScore 在之前版本稱為 PostFilter,現在命名為 PreScore。用于在 Score 之前進行一些信息生成。此處會獲取到通過 Filter 階段的 Node List,我們也可以在此處進行一些信息預處理或者生成一些日志或者監控信息。
Scoring:Scoring 擴展點是 Scheduler v1 版本中 Priority 的邏輯。基于 Filter 過濾后剩余的 Node List,根據 Scoring 擴展點定義的策略挑選出最優的節點。Scoring 細分為兩個階段:1)打分:對 Filter 后的 Node List 進行打分,Scheduler 會調用所配置的打分策略;2)歸一化:對打分之后的結構在 0-100 之間進行歸一化處理。
Reserve:Reserve 擴展點是 Scheduler v1 版本的 assume 的操作,此處會對調度結果進行緩存,如果在后邊的階段發生了錯誤或者失敗的情況,會直接進入 Unreserve 階段,進行數據回滾。
Permit:Permit 擴展點是 Framework v2 版本引入的新功能,當 Pod 在 Reserve 階段完成資源預留之后,Bind 操作之前,開發者可以定義自己的策略在 Permit 節點進行攔截,根據條件對經過此階段的 Pod 進行 Allow(允許)、Reject(拒絕)和 Wait(等待,可設置超時時間)的 3 種操作。
Binding Cycle
Binding Cycle 需要調用 kube-apiserver,耗時較長,為了提高調度的效率,需要異步執行,所以此階段線程不安全。
Bind:Bind 擴展點是 Scheduler v1 版本中的 Bind 操作,會調用 kube-apiserver 提供的接口,將 Pod 綁定到對應的 Node 上。
PreBind 和 PostBind:開發者可以在 PreBind 和 PostBind 分別在 Bind 操作前后執行,這兩個階段可以進行一些數據信息的獲取和更新。
UnReserve:UnReserve 擴展點的功能是用于清理到 Reserve 階段的的緩存,回滾到初始的狀態。當前版本 UnReserve 與 Reserve 是分開定義的,未來會將 UnReserve 與 Reserve 統一到一起,即要求開發者在實現 Reserve 同時需要定義 UnReserve,保證數據能夠有效的清理,避免留下臟數據。
基于 scheduler-plugins 實現定制化的調度插件
負責 Kube-scheduler 的小組 sig-scheduling 為了更好的管理調度相關的 Plugin,新建了項目 scheduler-plugins 來方便用戶管理不同的插件,用戶可以直接基于這個項目來定義自己的插件。
QoS 的插件主要基于 Pod 的 QoS Class 來實現的,目的是為了實現調度過程中如果 Pod 的優先級相同時,根據 Pod 的 QoS 來決定調度順序,調度順序:
Guaranteed (requests == limits)
Burstable (requests < limits)
BestEffort (requests and limits not set)
首先插件要定義插件的對象和構造函數:
// QoS Sort 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 }
1
2
3
4
5
6
7
然后,根據插件對應的 Extention Point(擴展點)來實現對應的接口,QoS Sort 是作用于 QueueSort 的部分,所以我們要實現 QueueSort 接口的函數。如下所示,QueueSortPlugin 接口只定義了一個函數 Less,所以我們實現這個函數即可。
Default QueueSort 在比較的時候,首先比較優先級,然后再比較 Pod 的 timestamp。我們重新定義了 Less 函數,在優先級相同的情況下,通過比較 QoS 來決定優先級。
// 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 } // 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 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
最后,需要在 kube-scheduler 的 main 函數中注冊自定義的插件和相應的構造函數:
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) } }
1
2
3
4
5
6
7
8
9
啟動 kube-scheduler 時,配置 ./manifests/qos/scheduler-config.yaml 中 kubeconfig 的路徑,啟動時傳入集群的 kubeconfig 文件以及插件的配置文件即可。
$ bin/kube-scheduler --kubeconfig=scheduler.conf --config=./manifests/qos/scheduler-config.yaml
1
Coscheduling/Gang scheduling
Coscheduling 的定義是:在并發系統中將多個相關聯的進程調度到不同處理器上同時運行的策略。
在 Coscheduling 的場景中,最主要的原則是保證所有相關聯的進程能夠同時啟動,防止部分進程的異常,導致整個關聯進程組的阻塞。這種導致阻塞的部分異常進程,稱之為 “碎片(Fragement)”。
在 Coscheduling 的具體實現過程中,根據是否允許 “碎片” 存在,可以細分為:
Explicit Coscheduling
Local Coscheduling
Implicit Coscheduling
其中 Explicit Coscheduling 就是大家常聽到的 Gang Scheduling,要求完全不允許有 “碎片” 存在, 也就是 “All or Nothing”。
將上述的概念對應到 Kubernetes 中,就是 Kubernetes 的 “批量調度任務” 功能了。一個批任務(關聯進程組)包括了 N 個 Pod(進程),Kubernetes Scheduler 負責將這 N 個 Pod 調度到 M 個 Node(處理器)上同時運行。如果這個批任務需要部分的 Pods 同時啟動才能被認為是 “可運行” 的,那么我們稱需啟動 Pods 的最小數量為 min-available。特別地,當 min-available=N 時,批任務要求滿足 Gang Scheduling。
為什么需要 Gang Scheduling?
Kubernetes Default Scheduler 是以 Pod 為調度單元進行依次調度的,并不考慮 Pod 之間的關聯關系。但是很多數據計算類的離線作業具有組合調度的特點,即:要求所有的子任務都能夠成功創建后,整個作業才能正常運行。這正是 Gang Scheduling 的場景。
例如:JobA 需要 4 個 Pod 同時啟動,才算正常運行。kube-scheduler 依次調度 3 個 Pod 并創建成功,到第 4 個 Pod 時,集群資源不足,則 JobA 的 3 個 Pod 處于空等的狀態。但是它們已經占用了部分資源,如果第 4 個 Pod 不能及時啟動的話,整個 JobA 無法成功運行,更糟糕的是導致集群資源浪費。
更壞的情況是:集群其他的資源剛好被 JobB 的 3 個 Pod 所占用,同時在等待 JobB 的第 4 個 Pod 創建,此時整個集群就出現了死鎖。
對此,社區目前有 Kube-batch 以及基于 Kube-batch 衍生的 Volcano 項目來解決這一問題。實現的方式是通過開發新的調度器將 Scheduler 中的調度單元從 Pod 修改為 PodGroup,以組的形式進行調度。使用方式是如果需要 Coscheduling 功能的 Pod 走 New Scheduler,而其他的 Pod 走 Default Scheduler 進行調度。
這些方案雖然能夠解決 Coscheduling 的問題,但是同樣引入了新的問題。對于同一集群資源的調度是需要具備中心化特性的。如果同時存在兩個調度器的話,就有可能會出現決策沖突,例如:出現分別將同一塊資源分配給兩個不同的 Pod 的情況。解決的方式往往是通過 Label 將 Node 劃分成兩個不同的 Pool,或者部署多個集群。然而,這種方式勢必會導致整體集群資源的浪費以及運維成本的增加。
再者,Volcano 運行需要啟動定制的 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook。這些 Webhooks 本身存在單點風險,一旦出現故障,將影響集群內所有 Pod 的創建。另外,多運行一套調度器,本身也會帶來維護上的復雜性,以及與上游 kube-scheduler 接口兼容上的不確定性。
Coscheduling Plugin
一個全新的方案是基于 scheduler-plugins 來實現的 Coscheduling Plugin。
定義 PodGroup:通過 Label 來定義 PodGroup 的概念,擁有同樣 Label 的 Pod 同屬于一個 PodGroup。min-available 是用來標識該 PodGroup 的作業能夠正式運行時所需要的最小副本數。
labels: pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu pod-group.scheduling.sigs.k8s.io/min-available: "2"
1
2
3
pod-group.scheduling.sigs.k8s.io/name:用于表示 PodGroup 的 Name;
pod-group.scheduling.sigs.k8s.io/min-available:用于表示當前集群資源至少滿足 min-available 個 pod 啟動時,才能整體調度該任務。
注: 要求屬于同一個 PodGroup 的 Pod 必須保持相同的優先級
Permit:Permit 插件提供了延遲綁定的功能,即 Pod 進入到 Permit 階段時,用戶可以自定義條件來 Allow(允許)、Reject(拒絕)和 Wait(等待,可設置超時時間)。Permit 的延遲綁定的功能剛好可以讓屬于同一個 PodGruop 的 Pod 調度到這個節點時,進行等待,等待積累的 Pod 數目滿足足夠的數目時,再統一運行同一個 PodGruop 的所有 Pod 進行綁定并創建。
例如:當 JobA 調度時,需要 4 個 Pod 同時啟動,才能正常運行。但此時集群僅能滿足 3 個 Pod 創建,此時與 Default Scheduler 不同的是,并不是直接將 3 個 Pod 調度并創建。而是通過 Framework 的 Permit 機制進行等待。當集群中有空閑資源被釋放后,JobA 的中 Pod 所需要的資源均可以滿足。則 JobA 的 4 個 Pod 被一起調度創建出來,正常運行任務。
QueueSort:由于 Default Scheduler 的隊列并不能感知 PodGroup 的信息,所以 PodGroup 的 Pods 在出隊時會處于無序性。當一個新的 Pod 創建后,入隊后,無法跟與其相同的 PodGroup 的 Pod 排列在一起,只能繼續以混亂的形式交錯排列。這種無序性就會導致如果 PodGroupA 在 Permit 階段處于等待狀態,此時 PodGroupB 的 Pod 調度完成后也處于等待狀態,相互占有資源使得 PodGroupA 和 PodGroupB 均無法正常調度。這種情況即是把死鎖現象出現的位置從 Node 節點移動到 Permit 階段,無法解決前文提到的問題。針對這個問題,可以通過實現 QueueSort 插件,保證在隊列中屬于同一個 PodGroup 的 Pod 能夠排列在一起。我們通過定義 QueueSort 所用的 Less 方法,作用于 Pod 在入隊后排隊的順序:
func Less(podA *PodInfo, podB *PodInfo) bool
1
首先,繼承了默認的基于優先級的比較方式,高優先級的 Pod 會排在低優先級的 Pod 之前。
然后,如果兩個 Pod 的優先級相同,我們定義了新的排隊邏輯來支持 PodGroup 的排序。
如果兩個 Pod 都是 regularPod(普通 Pod),則誰先創建,誰在隊列里邊排在前邊;
如果兩個 Pod 一個是 regularPod,另一個是 pgPod(屬于某個 PodGroup 的 Pod),我們比較的是 regularPod 的創建時間和 pgPod 所屬 PodGroup 的創建時間,則誰先創建誰在隊列里邊排在前邊;
如果兩個 Pod 都是 pgPod,我們比較兩個 PodGroup 的創建時間,則誰先創建誰在隊列里邊排在前邊。同時有可能兩個 PodGroup 的創建時間相同,我們引入了自增 ID,使得 PodGroup 的 ID 誰小誰排在前邊,目的是為了區分不同的 PodGroup。
通過如上的排隊策略,實現了屬于同一個 PodGroup 的 Pod 能夠同一個 PodGroup 的 Pod 能夠排列在一起。當一個新的 Pod 創建后,入隊后,會跟與其相同的 PodGroup 的 Pod 排列在一起。
Prefilter:為了減少無效的調度操作,提升調度的性能,在 Prefilter 階段增加了一個過濾條件,當一個 Pod 調度時,會計算該 Pod 所屬 PodGroup 的 Pod 的 Sum(包括 Running 狀態的),如果 Sum 小于 min-available 時,則肯定無法滿足 min-available 的要求,則直接在 Prefilter 階段拒絕掉,不再進入調度的主流程。
UnReserve:如果某個 Pod 在 Permit 階段等待超時了,則會進入到 UnReserve 階段,會直接拒絕掉所有跟 Pod 屬于同一個 PodGroup 的 Pod,避免剩余的 Pod 進行長時間的無效等待。
部署 Coscheduling Plugin:
$ wget http://kubeflow.oss-cn-beijing.aliyuncs.com/ack-coscheduling.tar.gz $ tar zxvf ack-coscheduling.tar.gz $ helm install ack-coscheduling -n kube-system ./ack-coscheduling NAME: ack-coscheduling LAST DEPLOYED: Mon Apr 13 16:03:57 2020 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None
1
2
3
4
5
6
7
8
9
驗證 Coscheduling Plugin:
$ helm get manifest ack-coscheduling -n kube-system | kubectl get -n kube-system -f - NAME COMPLETIONS DURATION AGE scheduler-update-clusterrole 1/1 8s 35s scheduler-update 3/1 of 3 8s 35s
1
2
3
4
卸載 Coscheduling Plugin:
$ helm uninstall ack-coscheduling -n kube-system
1
Binpack Scheduling
為什么需要 Binpack Scheduling?
Kubernetes 默認開啟的資源調度策略是 LeastRequestedPriority,消耗的資源最少的節點會優先被調度,使得整體集群的資源使用在所有節點之間分配地相對均勻。但是這種調度策略往往也會在單個節點上產生較多資源碎片。
如下這種情況情況,每個 Node 都有 1 個 GPU 卡空閑,可是又無法被利用,導致資源 GPU 這種昂貴的資源被浪費。如果使用的資源調度策略是 Binpack(裝箱),優先將節點資源填滿之后,再調度下一個節點,則上圖所出現的資源碎片問題得到解決。申請 2GPU 的作業被正常調度到節點上,提升了集群的資源使用率。
Binpack Scheduling Plugin
Binpack 實現已經抽象成 Kubernetes Scheduler Framework 的 Score 插件 RequestedToCapacityRatio,用于優選階段給 Node 打分。將 Node 根據自己定義的配置進行打分。具體的實現可以分為兩個部分:構建打分函數、打分。
構建打分函數就是用戶可以自定義不同的利用率所對應的分值大小,以便影響調度的決策過程。
如果用戶設定的對應方式如下所示,即:如果資源利用率為 0 的時候,得分為 0 分,當資源利用率為 100 時,得分為 10 分,所以得到的資源利用率越高,得分越高,則這個行為是 Binpack 的資源分配方式。
用戶也可以設置成利用率為 0 時,得分為 10 分,利用率為 100 時,得分為 0 分。這樣意味著資源利用率越低,則得分越高,這種行為是 spreading(展開)的資源分配方式。
用戶除了 2 個點之外也可以新增更多的點,對應關系可以不是線性的關系,例如:可以標識資源利用率為 50 時,得分為 8,則會將打分分割為兩個區間: 0-50 和 50-100。
打分:用戶可以自己定義在 Binpack 計算中所要參考的資源以及權重值,例如:可以只是設定 GPU 和 CPU 的值和權重。
resourcetoweightmap: "cpu": 1 "nvidia.com/gpu": 1
1
2
3
然后在打分過程中,通過計算 (pod.Request + node.Allocated)/node.Total 的結果得到對應資源的利用率,并且將利用率帶入上文中所述的打分函數中,得到相應的分數。最后將所有的資源根據 weight 值,加權得到最終的分數。
Score = line(resource1_utilization) * weight1 + line(resource2_utilization) * weight2 ....) / (weight1 + weight2 ....)
1
Kubernetes
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。