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的配置,基本都是採用預設配置,圖中列出了它的配置載入流程,基本都是載入它自身的預設配置。
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上,流程如下:
-
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最小。就是上面返回的numViolatingVictim b) 如果只有一個Node滿足,則返回該Node c) 比較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。
到此,排程流程分析完成。
參考:https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
作者簡介
李龍輝·沃趣科技高階開發工程師
IT從業多年,多年的C、Go語言開發經驗,主要負責基於Kubernetes的RDS平臺QFusion的研發,熟悉雲端計算及Kubernetes技術體系。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28218939/viewspace-2653678/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Kubernetes學習筆記(五):卷筆記
- Kubernetes學習筆記(更新中。。。。)筆記
- Kubernetes學習筆記(四):服務筆記
- 【一】kubernetes學習筆記-Pod概念筆記
- 【Kubernetes學習筆記】-kubeadm 手動搭建kubernetes 叢集筆記
- Kubernetes學習筆記(二):Pod、標籤、註解筆記
- Kubernetes Scheduler淺析
- Quartz job scheduler 學習quartz
- Kubernetes 學習筆記-- kafka往couchdb裡倒東西筆記Kafka
- Kubernetes學習筆記(七):訪問Pod後設資料與Kubernetes API筆記API
- kubernetes學習筆記 (二):k8s初體驗筆記K8S
- numpy的學習筆記\pandas學習筆記筆記
- kubernetes學習筆記 (三):阿里雲遊戲業務實戰筆記阿里遊戲
- Kubernetes(k8s)學習筆記(一)——系統架構K8S筆記架構
- Kubernetes學習筆記(六):使用ConfigMap和Secret配置應用程式筆記
- 【Kubernetes學習筆記】-服務訪問之 IP & Port & Endpoint 辨析筆記
- 技術分享 | Kubernetes 學習筆記之基礎知識篇筆記
- Kubernetes 漫遊:kube-scheduler
- 學習筆記筆記
- 【Kubernetes學習筆記】-使用Minikube快速部署K8S單機學習環境筆記K8S
- Kubernetes學習筆記(八):Deployment--宣告式的升級應用筆記
- K8S學習筆記之Kubernetes資料持久化方案K8S筆記持久化
- Kubernetes全棧架構師(資源排程上)--學習筆記全棧架構筆記
- Kubernetes全棧架構師(資源排程下)--學習筆記全棧架構筆記
- 【三】Kubernetes學習筆記-Pod 生命週期與 Init C 介紹筆記
- 【學習筆記】數學筆記
- 《JAVA學習指南》學習筆記Java筆記
- 機器學習學習筆記機器學習筆記
- 學習筆記-粉筆980筆記
- 學習筆記(3.29)筆記
- 學習筆記(4.1)筆記
- 學習筆記(3.25)筆記
- 學習筆記(3.26)筆記
- JavaWeb 學習筆記JavaWeb筆記
- golang 學習筆記Golang筆記
- Nginx 學習筆記Nginx筆記
- spring學習筆記Spring筆記
- gPRC學習筆記筆記