一. 簡介
在 Kubernetes 項目中,默認調度器(default scheduler)的主要職責,就是為一個新創建出來的 Pod,尋找一個最合適的節點(Node)。
而這里“最合適”的含義,包括三層:
- 從集群所有的節點中,根據調度算法挑選出所有可以運行該 Pod 的節點。
- 從第一步的結果中,再根據調度算法挑選一個最符合條件的節點作為最終結果。
- 當一個高優先級的 Pod 調度失敗后,該 Pod 并不會被“擱置”,而是會“擠走”某個 Node 上的一些低優先級的 Pod 。
所以在具體的調度流程中,默認調度器會首先調用一組叫作 Predicate
的調度算法,來檢查每個 Node。然后,再調用一組叫作 Priority
的調度算法,來給上一步得到的結果里的每個 Node 打分。最終的調度結果,就是得分最高的那個 Node。如果出現資源競爭,那就需要通過 Preemption
的搶占機制來進行調度。
二. Predicates
2.1 定義
Predicates
在調度過程中的作用,可以理解為 Filter
,即:它按照調度策略,從當前集群的所有節點中,“過濾”出一系列符合條件的節點。這些節點,都是可以運行待調度 Pod 的宿主機。
2.2 過濾規則
2.2.1 GeneralPredicates
GeneralPredicates
負責的是最基礎的調度策略。比如,PodFitsResources
計算的就是宿主機的 CPU 和內存資源等是否夠用。
PodFitsResources
檢查的只是 Pod 的 requests 字段。Kubernetes 的調度器并沒有為 GPU 等硬件資源定義具體的資源類型,而是統一用一種名叫 Extended Resource
的、Key-Value
格式的擴展字段來描述的。
像上面這樣一組GeneralPredicates
,正是 Kubernetes 考察一個 Pod 能不能運行在一個 Node 上最基本的過濾條件。所以,GeneralPredicates
也會被其他組件(比如 kubelet
)直接調用。
2.2.2 Volume 相關的過濾規則
這一組過濾規則負責的是跟容器持久化 Volume 相關的調度策略。其中,NoDiskConflict
檢查的條件,是多個 Pod 聲明掛載的持久化 Volume 是否有沖突。比如,AWS EBS 類型的 Volume,是不允許被兩個 Pod 同時使用的。所以,當一個名叫 A 的 EBS Volume 已經被掛載在了某個節點上時,另一個同樣聲明使用這個 A Volume 的 Pod,就不能被調度到這個節點上了。
2.2.3 宿主機相關的過濾規則
這一組規則主要考察待調度 Pod 是否滿足 Node 本身的某些條件。比如,PodToleratesNodeTaints,負責檢查的就是我們前面經常用到的 Node 的“污點”機制。只有當 Pod 的 Toleration 字段與 Node 的 Taint 字段能夠匹配的時候,這個 Pod 才能被調度到該節點上。
2.2.4 Pod 相關的過濾規則
這一組規則跟 GeneralPredicates
大多數是重合的。比較特殊的,是 PodAffinityPredicate
。這個規則的作用,是檢查待調度 Pod 與 Node 上的已有 Pod 之間的親密(affinity)和反親密(anti-affinity)關系。
2.3 小結
在具體執行的時候, 當開始調度一個 Pod 時,Kubernetes 調度器會同時啟動 16 個 Goroutine
,來并發地為集群里的所有 Node 計算 Predicates,最后返回可以運行這個 Pod 的宿主機列表。
同時,為每個 Node 執行 Predicates
時,調度器會按照固定的順序來進行檢查。這個順序,是按照 Predicates 本身的含義來確定的。
三. Priorities
3.1 定義
在 Predicates 階段完成了節點的“過濾”之后,Priorities
階段的工作就是為這些節點打分。這里打分的范圍是 0-10 分,得分最高的節點就是最后被 Pod 綁定的最佳節點。
3.2 打分規則
3.2.1 LeastRequestedPriority
LeastRequestedPriority
是 Priorities 里最常用到的一個打分規則。
公式如下:
score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2
可以看到,這個算法實際上就是在選擇空閑資源(CPU 和 Memory)最多的宿主機。
3.2.2 BalancedResourceAllocation
公式如下:
score = 10 - variance(cpuFraction,memoryFraction,volumeFraction)*10
其中,每種資源的 Fraction
的定義是 :Pod 請求的資源 / 節點上的可用資源
。而 variance 算法的作用,則是計算每兩種資源 Fraction 之間的“距離”。而最后選擇的,則是資源 Fraction 差距最小的節點。
可以看出,BalancedResourceAllocation
選擇的,其實是調度完成后,所有節點里各種資源分配最均衡的那個節點,從而避免一個節點上 CPU 被大量分配、而 Memory 大量剩余的情況。
3.2.3 ImageLocalityPriority
ImageLocalityPriority
是在 Kubernetes v1.12 里新開啟的調度規則,即:如果待調度 Pod 需要使用的鏡像很大,并且已經存在于某些 Node 上,那么這些 Node 的得分就會比較高。
為了避免這個算法引發調度堆疊,調度器在計算得分的時候還會根據鏡像的分布進行優化,即:如果大鏡像分布的節點數目很少,那么這些節點的權重就會被調低,從而“對沖”掉引起調度堆疊的風險。
3.2.4 Others
NodeAffinityPriority
、 TaintTolerationPriority
和 InterPodAffinityPriority
這三種 Priority。
它們與 PodMatchNodeSelector
、PodToleratesNodeTaints
和 PodAffinityPredicate
這三個 Predicate
的含義和計算方法是類似的。但是作為 Priority,一個 Node 滿足上述規則的字段數目越多,它的得分就會越高。
四. Preemption
4.1 場景
當一個高優先級的 Pod 調度失敗后,該 Pod 并不會被“擱置”,而是會“擠走”某個 Node 上的一些低優先級的 Pod 。這樣就可以保證這個高優先級 Pod 的調度成功,這就需要用到 Preemption
搶占機制。
4.2 原理
當上述搶占過程發生時,搶占者并不會立刻被調度到被搶占的 Node 上。事實上,調度器只會將搶占者的spec.nominatedNodeName
字段,設置為被搶占的 Node 的名字,所以這就是主要方法。
4.3 案例
4.3.1 PriorityClass 設置
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 999999999
globalDefault: false
description: "This is a high-priority config."
上面這個 YAML 文件,定義的是一個名叫 high-priority
的 PriorityClass
,其中 value 的值是 999999999
。Kubernetes 規定,優先級是一個32 bit
的整數,最大值不超過1000000000(10 億,1 billion)
,并且值越大代表優先級越高。而超出 10 億的值,其實是被 Kubernetes 保留下來分配給系統 Pod 使用的。這樣做的目的,就是保證系統 Pod 不會被用戶搶占掉。
4.3.2 Pod 配置
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
這個 Pod 通過 priorityClassName
字段,聲明了要使用名叫 high-priority
的 PriorityClass
。當這個 Pod 被提交給 Kubernetes 之后,Kubernetes 的 PriorityAdmissionController
就會自動將這個 Pod 的 spec.priority
字段設置為 999999999
。
4.4 隊列
Kubernetes 調度器實現搶占算法的一個最重要的設計,就是在調度隊列的實現里,使用了兩個不同的隊列。
4.4.1 activeQ
凡是在 activeQ
里的 Pod,都是下一個調度周期需要調度的對象。所以,當在 Kubernetes 集群里新創建一個 Pod 的時候,調度器會將這個 Pod 入隊到 activeQ
里面。調度器不斷從隊列里出隊(Pop)一個 Pod 進行調度,實際上都是從 activeQ 里出隊的。
4.4.2 unschedulableQ
這是專門用來存放調度失敗的 Pod,類似 Rabbitmq 使用的死信隊列。當一個 unschedulableQ
里的 Pod 被更新之后,調度器會自動把這個 Pod 移動到 activeQ
里。
4.4 搶占流程
4.4.1 調度器為“搶占者”尋找“被搶占者”的流程
- 第一步,調度器會檢查這次失敗事件的原因,來確認搶占是不是可以幫助搶占者找到一個新節點。這是因為有很多
Predicates
的失敗是不能通過搶占來解決的。比如,PodFitsHost
算法(負責的是,檢查 Pod 的nodeSelector
與 Node 的名字是否匹配),這種情況下,除非 Node 的名字發生變化,否則即使刪除再多的 Pod,搶占者也不可能調度成功。 - 第二步,如果確定搶占可以發生,那么調度器就會把自己緩存的所有節點信息復制一份,然后使用這個副本來模擬搶占過程。
當遍歷完所有的節點之后,調度器會在上述模擬產生的所有搶占結果里做一個選擇,找出最佳結果。而這一步的判斷原則,就是盡量減少搶占對整個系統的影響。
4.4.2 調度器就真正開始搶占
- 第一步,調度器會檢“被搶占者”列表,清理這些 Pod 所攜帶的
nominatedNodeName
字段。 - 第二步,調度器會把搶占者的
nominatedNodeName
,設置為被搶占的 Node 的名字。 - 第三步,調度器會開啟一個
Goroutine
,同步地刪除“被搶占者”。
所以接下來,調度器就會通過正常的調度流程把搶占者調度成功,在這個正常的調度流程里,是一切皆有可能的。
4.5 特殊場景
在為某一對 Pod 和 Node 執行 Predicates 算法的時候,如果待檢查的 Node 是一個即將被搶占的節點,即:調度隊列里有 nominatedNodeName
字段值是該 Node 名字的 Pod 存在(“潛在的搶占者”)。那么,調度器就會對這個 Node ,將同樣的 Predicates 算法運行兩遍。
4.5.1 流程
- 第一遍, 調度器會假設上述“潛在的搶占者”已經運行在這個節點上,然后執行 Predicates 算法。
- 第二遍, 調度器會正常執行 Predicates 算法,不考慮任何“潛在的搶占者”。而只有這兩遍 Predicates 算法都能通過時,這個 Pod 和 Node 才會被認為是可以綁定(bind)的。
4.5.3 原因
第一遍 Predicates 算法的原因:是由于
InterPodAntiAffinity
規則的存在。
由于InterPodAntiAffinity
規則關心待考察節點上所有 Pod 之間的互斥關系,所以我們在執行調度算法時必須考慮,如果搶占者已經存在于待考察 Node 上時,待調度 Pod 還能不能調度成功。
我們在這一步只需要考慮那些優先級等于或者大于待調度 Pod 的搶占者。畢竟對于其他較低優先級 Pod 來說,待調度 Pod 總是可以通過搶占運行在待考察 Node 上。第二遍 Predicates 算法的原因:因為“潛在的搶占者”最后不一定會運行在待考察的 Node 上。原因是,Kubernetes 調度器并不保證搶占者一定會運行在當初選定的被搶占的 Node 上。
五. 總結
- 調度過程如下:
待調度Pod被提交到 apiServer -> 更新到 Etcd -> 調度器 Watch Etcd 感知到有需要調度的Pod(Informer) -> 取出待調度Pod的信息 -> Predicates: 挑選出可以運行該Pod的所有Node -> Priorities:給所有Node打分 -> 將Pod綁定到得分最高的Node上 -> 將Pod信息更新回 Etcd -> node的 kubelet 感知到 Etcd 中有自己node需要拉起的Pod -> 取出該Pod信息,做基本的二次檢測(端口,資源等) -> 在node 上拉起該pod
- 搶占過程如下:
Preemption:確定要發生搶占 -> 調度器將所有節點信息復制一份,開始模擬搶占 -> 檢查副本里的每一個節點,然后從該節點上逐個刪除低優先級Pod,直到滿足搶占者能運行 -> 找到一個能運行搶占者Pod的node -> 記錄下這個Node名字和被刪除Pod的列表 -> 模擬搶占結束 -> 開始真正搶占 -> 刪除被搶占者的Pod,將搶占者調度到Node上
歡迎收藏個人博客: Wyatt's Blog ,非常感謝~
Reference
https://time.geekbang.org/column/article/70519?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title
https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/
https://dominik-tornow.medium.com/the-kubernetes-scheduler-cd429abac02f
https://alibaba-cloud.medium.com/getting-started-with-kubernetes-scheduling-and-resource-management-4d819c901b8c