共計 11362 個字符,預計需要花費 29 分鐘才能閱讀完成。
Kubernetes scheduler 學習筆記是怎樣的,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
簡介
Kubernetes 是一個強大的編排工具,可以用來很方便的管理許多臺機器,為了使機器的資源利用率提高,同時也盡可能的把壓力分攤到各個機器上,這個職責就是由 scheduler 來完成的。
Kubernetes scheduler 是一個策略豐富、拓撲感知、工作負載特定的功能,顯著影響可用性、性能和容量。
為了能更好的使用它,所以從源碼的角度,對它進行一個全方位的分析與學習。
scheduler 的功能不多,但邏輯比較復雜,里面有很多考慮的因素,總結下來大致有如下幾點:
Leader 選主,確保集群中只有一個 scheduler 在工作,其它只是高可用備份實例。通過 endpoint:kube-scheduler 作為仲裁資源。
Node 篩選,根據設置的條件、資源要求等,匹配出所有滿足分配的 Node 結點。
最優 Node 選擇。在所有滿足條件的 Node 中,根據定義好的規則來打分,取分數最高的。如果有相同分數的,則采用輪詢方式。
為了響應高優先級的資源分配,增加了搶占功能。scheduler 有權刪除一些低優先級的 Pod,以釋放資源給高優先級的 Pod 來使用。
功能說明
代碼看下來比較困難,下面將分幾個場景來描述 scheduler 工作的過程:
1、環境說明(假設 3 臺機器,分別是 8C16G)
場景一:資源分配——最基本的功能
2、先分配一個請求 2C4G 的 Pod:A
場景二:機器負載均衡——評分機制
3、再分配一個請求 2C4G 的 Pod:B(盡管 node1 上還有空閑資源可分配 B,但 node2 和 node3 空閑資源更多,打分更高,所以分配到了 node2 選擇 node2 還是 node3,是由 schedule 輪詢選擇的。)
4、同理,如果再分配一個 C,scheduler 會優先分配到 node3 上
場景三:資源搶占——特權機制
5、現在 3 個 Node 上都分配了 2C4G,就是都剩余 6C12G,如果我這個時候分配一個 8C12G 的 Pod:D,在同優先級的情況下,D 將不會分配,處于 Pending 狀態,因為三臺機器都資源不足。
6、如果這個時候,我給 D 設置一個高的優先級,schedule 會刪除一臺機器上的 Pod,比如 A,然后資源足夠了,將 D 分配到 node1 上,再將 A 分配到 node2 或 node3 上。(這里分配是一個類似,因為三臺都是一樣的)
7、下面實戰一把,詳細試驗下 scheduler 的搶占過程:
我有一個 Deployment,有 3 個復本,分別分配到兩臺機器上。(為什么用這個例子,是為了說明,搶占一定會發生在 10-10-40-89 上,因為要刪除的 Pod 最少)
這個時候,我創建一個高優先級的 Deployment:
快速查詢,能看到下面的階段:
第一步,將要分配的 testpc-745cc7867-fqbp2 設置為“提名 Pod”,這個名字后面會再出現,同時刪除原 10-10-40-89 上的 testpod,由于截的比較慢,下圖中新的 testpod 已經在 10-10-88-99 上創建了。
第二步,提名 Pod 將會分配到對應的結點上(等待 Terminating 狀態的 Pod 釋放完資源后)。
第三步,資源足夠,Pod 正常 Running。
最后展示下 watch 情況下的事件:
測試我共有兩個 yaml 文件,如下:
testpod.yaml:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: 1
labels:
k8s-app: testpod
name: testpod
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: testpod
template:
metadata:
labels:
k8s-app: testpod
spec:
containers:
- image: nginx:1.17
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
name: nginx
protocol: TCP
resources:
requests:
cpu: 1
memory: 2Gi
testpc.yaml:
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000000
globalDefault: false
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: 1
labels:
k8s-app: testpc
name: testpc
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: testpc
template:
metadata:
labels:
k8s-app: testpc
spec:
containers:
- image: nginx:1.17
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
name: nginx
protocol: TCP
resources:
requests:
cpu: 6
memory: 2Gi
priorityClassName: high-priority
場景四:關系戶——親和與反親和
scheduler 在分配 Pod 時,考慮的要素很多,親和性和反親和,是一個比較常用的,在這里做一個典型來講講。
比如在上圖中,我新的 Pod:D,要求不能和 A 在一臺機器上,和 B 的互斥打分是 100,和 C 的互斥打分是 10。表示說,D 一定不能和 A 在一臺機器,盡可能不和 B、C 在同一臺機器,實在沒辦法時(資源不足),D 更傾向于和 C 在一起。
樣例:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: kubernetes.io/hostname
通過對這四個應用場景的分析,對它的功能有了一個初步的了解。要想更全面、深入的了解它的功能,需要從它的源碼來著手。下面將從源碼層面來做深入分析。
代碼分析 scheduler 總體結構
scheduler 的配置,基本都是采用默認配置,圖中列出了它的配置加載流程,基本都是加載它自身的默認配置。
server.Run 為它的主體邏輯,之后會詳細講解。
重要配置講解
圖中,單獨列出了兩個 config 配置:
1、disablePreemption:
scheduler 有個搶占功能。當 Pod 調度發現無可用資源時,它會將比該 Pod 優先級低的 Pod 刪除,以釋放資源給它來調度。disablePreemption 默認為 false,表示開啟搶占,如果需要關閉,則設置為 true。
2、既然說到優先級,所以我還列出來了優先級的設置方法。
Kubernetes 中有個單獨的優先級的資源,叫:PriorityClass,通過下面這個 yaml,能創建一個 PriorityClass。
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: This priority class should be used for XYZ service pods only.
然后可將這個 PriorityClass 關聯到 Pod 上:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
這樣就完成的 Pod 優先級的設置。如果不設置,Pod 默認是同一優先級(為 0)。
特別注意:
static Pod 比較特殊,需要直接設置 priority,因為 kubelet 是根據 priority 來判斷。
scheduler 啟動流程
通過深入分析 server.Run,可看到如下流程:
server.Run 還是有一部分的配置處理流程。
schedulerConfig 中,根據默認的參數,加載了兩大塊內容:predicate、priority 函數。
predicate 函數用于做 Pod 是否可分配到 Node 上的檢查函數。
priority 函數,則用于選優。當可分配的 Node 有多個時,這個時候就會根據 priority 函數來給 node 打分,最終調度到分數最高的 Node 上。
Kubernetes 提供了這些默認的判斷函數:
predicate:
1、CheckNodeConditionPredicate
we really don’t want to check predicates against unschedulable nodes.
檢查 Node 狀態:是否處于可調度狀態等。
—– 遍歷 nodeInfo 中 Node 的所有狀況:
如果 Node 類型為 ready,并且狀態不是 True,則認為結點為 notReady
如果 Node 類型為 OutOfDisk,并且狀態不是 False,則認為結點 OutOfDisk
如果 Node 類型為 NetworkUnavailable,并且狀態不是 False,則認為結點狀態為:NetworkUnavailable
檢查 Node 的 spec,如果是 UnSchedulable,則認為結點為 UnSchedulable。
以上檢查都通過,則返回匹配成功。
2、PodFitsHost
we check the pod.spec.nodeName.
檢查 pod.spec.nodeName 是否匹配。
—- 如果 Pod 未指定 NodeName,則返回匹配成功。
檢查 Node 的名字,如果與 Pod 指定的同名,則匹配成功,否則返回:nodeName 不匹配。
3、PodFitsHostPorts
we check ports asked on the spec.
檢查服務端口是否被占用。
—– 如果元數據 metadata 中有定義需要的 podPorts,則直接從元數據中取,否則從 Pod 的所有容器中獲取需要的 port。
如果需要的 port 為空,則返回匹配成功。
從 nodeInfo 中獲取當前已經使用的 port,如果有沖突,則返回:端口不匹配,否則返回匹配成功。
4、PodMatchNodeSelector
check node label after narrowing search.
檢查 label 是否匹配。
—— 如果 Pod 中定義了 NodeSelector,則根據選擇來匹配 Node 的 labels,如果不匹配,則返回 NodeSelectorNotMatch。
如果 Pod 的 Affinity 中定義了 NodeAffinity,則檢查結點親和關系:
如果未定義 requiredDuringSchedulingIgnoredDuringExecution,則直接返回匹配。
如果定義了 requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms,則里面有一個匹配,則匹配。否則認為不匹配。
特別的:如果 nodeSelectorTerms 為 nil,則全不匹配;如果 nodeSelectorTerms 不為 nil,但是空的切片,則全不匹配;同樣,nodeSelectorTerms 中的 MatchExpressions,如果為 nil 或者是空切片,也不匹配。
5、PodFitsResources
this one comes here since it’s not restrictive enough as we do not try to match values but ranges.
—– 檢查 Node 的 allowedPodNumber 是否超過,如果超過,增加超限錯誤(此處未直接返回,會把所有錯誤檢查完一次性返回)。
檢查元數據中是否有定義 podRequest、ignoredExtendedResources,如果定義了,則從元數據中取。否則從 Pod 中每個容器中取:先檢查所有 container 中所有需要的資源總合,再檢查 initContainer 中,如果有資源比總合還大,則取較大的為所需要的資源。
如果需要的資源都為 0,則返回檢查結果。
獲取 Node 的可用資源,檢查需要新申請的資源 + 已申請的資源是否超過可用資源,如果超過,則記錄資源不足。
檢查所有 Pod 擴展資源,并判斷擴展資源是否需要檢查(ignoredExtendedResources),如果需要檢查,則判斷資源是否足夠,不足夠則記錄失敗。
返回檢查結果(如果無失敗,是檢查成功)。
6、NoDiskConflict
Following the resource predicate, we check disk.
—- 遍歷 Pod 所有存儲、Node 下的所有 Pod,檢查是否有存儲沖突:
如果 Pod 無存儲(無 GCE、AWS、RBD、ISCSI),則檢查通過。
7、PodToleratesNodeTaints
check toleration here, as node might have toleration.
—– 檢查結點是否容忍 taint 環境:
參數:Pod 中定義的容忍規則:tolerations,Node 中的環境狀態:taints,篩選規則:取 effect 為 NoSchedule、NoExecute 的。
如果 Node 無 taints,返回匹配成功。
遍歷所有 taints,如果 taint 不滿足篩選規則,則跳過檢查。
遍歷所有的容忍規則,檢查是否有規則是允許結點的 taint 狀態。檢查步驟:
如果 effect 為空,則檢查通過,否則要相同。
如果 key 為空,則檢查通過,否則要相同。
如果 operator 為 Exists,則檢查通過,如果為空或者是 Equal,則要相同,否則不通過。
8、PodToleratesNodeNoExecuteTaints
check toleration here, as node might have toleration.
—– 檢查規則同上相似,只是篩選規則變了:取 effect 為 NoExecute 的。
9、CheckNodeLabelPresence
labels are easy to check, so this one goes before.
—— 檢查 label 是否存在,不關心值。可設置 label 存在與不存在。
只有在 scheduler.CreateFromConfig(policy) 才會初始化該檢查,在 RegisterCustomFitPredicate 中注冊,默認無該檢查。
10、checkServiceAffinity
—– 檢查服務類同關系。
如果一個 Pod 的服務調度到有 label: region=foo 的 Node,之后有相同服務的 Pod 都會調度到該 Node。
11、MaxPDVolumeCountPredicate
—– 檢查掛載的卷個數是不是超標,只支持:ESB:39,GCE:16,AzureDisk:16。
12、VolumeNodePredicate
—– 無
13、VolumeZonePredicate
—– 檢查存儲區域劃分:
檢查 Node 中是否有 label:failure-domain.beta.kubernetes.io/zone 或者 failure-domain.beta.kubernetes.io/region,如果有,則檢查 Pod 存儲情況。
遍歷 Pod 需要的存儲信息:
根據 PVC 名字獲取 PVC 信息,取出 PVC 對應的 PV 名字,如果沒有名字(表示還未綁定 PV),獲取 PVC 的 StorageClassName,如果處理正在綁定中,則跳過不檢查,否則返回匹配失敗(因為 PVC 綁定失敗)。
綁定成功的,根據 pvName 獲取對應的 PV 信息,檢查 PV 的標簽,如果 PV 有上面兩個標簽(zone、region),檢查 PV 的值中(值可能有多個,用__分隔),是否包含 Node 對應標簽的值,如果沒有包含,則返回匹配失敗。
14、CheckNodeMemoryPressurePredicate
doesn’t happen often.
—– 檢查 Node 內存壓力。
15、CheckNodeDiskPressurePredicate
doesn’t happen often.
16、InterPodAffinityMatches
Most expensive predicate to compute.
默認有這些打分函數(priority):
SelectorSpreadPriority:根據相同的 RC 和服務拆分,使每個 Node 具有相同服務或 RC 的 Pod 盡量少,spreads pods by minimizing the number of pods (belonging to the same service or replication controller) on the same node.
InterPodAffinityPriority:根據 Pod 共性來分配,pods should be placed in the same topological domain (e.g. same node, same rack, same zone, same power domain, etc.).
LeastRequestedPriority:選擇比較閑的 node,Prioritize nodes by least requested utilization.
BalancedResourceAllocation:從資源分配平衡性來考慮分配,Prioritizes nodes to help achieve balanced resource usage.
NodePreferAvoidPodsPriority:用于用戶自定義分配,權重 10000 起,方便用戶來指定。0 的時候不起作用。用戶通過這個來指定:scheduler.alpha.kubernetes.io/preferAvoidPods Set this weight large enough to override all other priority functions.
NodeAffinityPriority:根據結點關系來分配,Prioritizes nodes that have labels matching NodeAffinity.
TaintTolerationPriority:根據 pod 設置的容忍項來分配,Prioritizes nodes that marked with taint which pod can tolerate.
最終,死循環進入:scheduleOne,真正開始 schedule 的調度流程。
Schedule 調度流程
先講主流程:
主流程分為以下 8 步:
從 Pod 隊列中取出一個需要調度的 Pod。
嘗試調度該 Pod。
調度失敗,則嘗試搶占 Pod。
調度成功后,嘗試做 volumes 綁定。
由于 reserve 插件暫時未啟用,暫未分析。
嘗試將 Pod 分配到 Node 上。
真正實現綁定。第 4 步和第 6 步中,都只是對 schedule 的 cache 的操作,先確保對 cache 的操作能完成,最終在第 7 步,異常實現將 cache 中的修改應用到 apiserver 中。如果應用失敗,會將 pod 的分配信息從 cache 中清除,重新進行 scheduler。
最復雜也最核心的,就是第 2 步和第 3 步,下面分別進行分析。
調度 Pod 流程
調度 Pod,就是嘗試將 Pod 分配到 Node 上,流程如下:
共有 7 點,將逐步分析:
Pod 基本檢查,檢查 Pod 是否有了對應的 PVC,這里只是檢查 PVC 是否存在,不關心綁定過程。
取出所有 Node 列表。
將 nodeInfo 應用到緩存中。全局 nodeInfo 中保存了當前 Node 的真實數據信息,而 cache 中會有調度過程的假設分析的信息。
檢查 Pod 是否可調度到 Node 上,返回可調度的 Node 列表。
a) 這里的檢查,是針對前面初始化時,注冊的 predicate 函數,如果有不符合,則認為不可調度。
b) 這里會嘗試兩次,之所以兩次,是因為有“提名 Pod”的存在。暫時先不管“提名 Pod”哪來的,后面會講到。提名 Pod,就是說,這個 Pod 已經分配到 Node 上,但它還未應用到 Kubernetes 環境,目前只是占著這個坑位,要調度的 Pod,在調度的過程中,也需要考慮它所占的資源。
c) 第一次時,會先把優先級大于當前 Pod 的提名 Pod 分配到 Node 中(添加到一個臨時的 nodeInfo 中),然后檢查所有的 predicat 函數是否通過。
d) 第二次時,不添加提名 Pod,再檢查所有的 predicate 函數。之所以有第二次,是因為提名 Pod 實際還并不存在,有些 Pod 親和性可能會判斷有誤。
e) 當然,如果沒有提名 Pod,則不需要第二次判斷。
如果找不到,則返回失敗。如果只找到一個,則返回該 Node。
當找到多個 Node 時,會去給 Node 打分,打分規則如下:
a) 如果沒有定義打分規則,則返回所有分數都為 1。schedule 默認是有打分函數的,前面初始化中有講。
b) 運行早期老版本的打分函數。早期就是單純的一個 function,運行后得到打分結果。
c) 新版本,將打分函數拆分成兩步,map 和 reduce,先按 16 個并發運行 map,之后運行 reduce 統計執行結果。
d) 這里還預留了擴展支持。
e) 最終返回打分結果。
根據打分結果,選擇 Node。
a) 先取出得分最高的 Node 列表。
b) 然后按 round-robin 的方式選擇 Node。
由于相同最高分的 Node 可能有多個,genericScheduler 采用 round-robin 的方式:它自己記錄一個全局的 lastNodeIndex,如何 num 為當前有相同最高分的節點數,則用 lastNodeIndex % num 來選取本次節點的下標,之后 lastNodeIndex 加 1,實現輪詢調度。
到此,Pod 的調度流程分析完成。當中有個特別的東西:提名 Pod(NominatedPod),它的出現和下面講的搶占流程有關。
Pod 搶占流程
搶占的流程,比調度復雜一些,主要分兩大步:搶占分析和搶占。第一步是檢查是不是能完成搶占,第二步是執行搶占(刪除 Pod)。
搶占檢查
檢查 Pod 是否可以發起搶占:如果 Pod 是提名 Pod(已經預分配到 Node),并且該 Node 上有處于 terminating 的 Pod p,并且 p 的優先級小于當前 Pod,則不允許發起搶占。
獲取所有 Node 清單。
獲取可能的 Node。檢查調度失敗原因,如果是 nodeNotReady 這種原因,則 Node 不參與搶占。這些都是不參與搶占的:predicates.ErrNodeSelectorNotMatch,predicates.ErrPodAffinityRulesNotMatch,predicates.ErrPodNotMatchHostName,predicates.ErrTaintsTolerationsNotMatch,predicates.ErrNodeLabelPresenceViolated,predicates.ErrNodeNotReady,predicates.ErrNodeNetworkUnavailable,predicates.ErrNodeUnderDiskPressure,predicates.ErrNodeUnderPIDPressure,predicates.ErrNodeUnderMemoryPressure,predicates.ErrNodeUnschedulable,predicates.ErrNodeUnknownCondition,predicates.ErrVolumeZoneConflict,predicates.ErrVolumeNodeConflict,predicates.ErrVolumeBindConflict
如果可搶占的 Node 沒有,則結束。
獲取 pdb 列表:pdb is PodDisruptionBudget. 這個是預算的定義,比如 statefulset 定義了 3 個復本,而我們定義了,允許其中 1 個 Pod 可以掛掉。
獲取通過搶占(刪除一些 Pod),能完成調度的 Node 列表。
a) 將比當前 Pod 優先級低的 Pod 全部從 nodeInfoCopy 中刪除,然后嘗試去調度。b) 如果調度失敗,則表示無法搶占。(因為不能刪除比它優先級高的)c) 將要刪除的 Pod,根據 pdb 進行拆分:nonViolatingVictim 和 violatingVictim。說明見圖中。d) 然后嘗試將 violatingVictim 中的 Pod 一個個加進去,嘗試能不能調度。numViolatingVictim 中記錄不通過數。e) 然后嘗試將 nonViolatingVictim 中的 Pod 一個個加進去,嘗試能不能調度。victims 記錄不通過的 Pod 信息。f) 返回 victims 和 numViolatingVictim。
extenders 擴展保留。
從可搶占的 Node 列表中,選擇最合適的一個 Node。按如下規則進行選擇:a) node pdb violations 最小。就是上面返回的 numViolatingVictimb) 如果只有一個 Node 滿足,則返回該 Nodec) 比較 Node 中 victims 中優先級的最高值,取最小的那個。最高:取的是單個 Node 中,優先級的最高值。最小:取的是所有 Node 中的最小值 d) 如果只有一個,則返回該 Node。e) 取 Node 中 victims 優先級總和最小的。f) 如果只有一個,則返回該 Node。g) 取 Node 中 victims 的 Pod 數最小的。h) 返回第一個。
如果無合適的,則結束。
獲取比當前優先級小的提名 Pod。
返回 Node 信息,需要刪除的 Pod 列表,優先級小的提名 Pod。
到此,搶占檢查結束。得到期望調度的 Node、要調度到這個 Node 上,需要刪除的 Pod 列表、以及比當前 Pod 優先級小的提名 Pod。
搶占執行流程(找到了期望的 Node 才會進入)
將當前 Pod,變更為提名 Pod,對應的 Node 為期望的 Node。這里就是提名 Pod 出現的原因。將提名 Pod 信息更新到 apiServer。遍歷 victims(搶占流程返回的需要刪除的 Pod 列表),刪除 Pod,并記錄 event。遍歷 nominatedPodsToClear(搶占返回的比當前 Pod 優先級小的提名 Pod),清空提名 Pod 配置,并更新 apiServer。
到此,調度流程分析完成。
關于 Kubernetes scheduler 學習筆記是怎樣的問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注丸趣 TV 行業資訊頻道了解更多相關知識。