調度器選擇策略:
預選策略(Predicate)
1. 根據運行Pod的資源限制來排除不符合要求的Node
2. 根據運行Pod時,是否要求共享宿主機的網絡名稱空間來判斷,如: 某Pod啟動要共享宿主機的網絡名稱空間,啟動80端口,而某些Node的80已經被占用,那它就不符合,就也要不排除。
優選策略(Priority):
此階段會經過一系列函數,會把預選出來的節點的相關屬性都輸入到這些函數中,通過計算來獲取每個節點的優先分值,然后在倒序排序,取得分最高者,作為運行Pod的節點,若計算后,發現有多個Node得分相同,此時將隨機選取一個Node作為該Pod運行的Node。
選定(Select):
當優選選出一個Node后,該Pod將會被綁定到該Node上運行。
我們在定義一個Pod要運行在那個Node,是有一定偏向性的,比如有些Pod需要運行的有SSD硬盤的Node上,而有些Pod則需要運行在有GPU的節點上等,這時就需要我們給Node上打上不同的標簽,來標識整個集群中不同Node的特性,這些特性包括,特有硬件標識,地理位置標識,不同機房標識等。
我們使用kubectl explain pod.spec 可以看到其中有nodeName 和 nodeSelector 兩個屬性,第一個是明確指定Node節點名,我就要運行在這個Node上,第二個就是通過匹配Node標簽,來選擇運行的Node。
調度方式有以下幾類:
1. 節點傾向性(或叫 節點親和性調度)
這種調度通常會采用 nodeSelector 來完成調度。
2. Pod傾向性 和 Pod非傾向性(這也稱為Pod親和性和非親和性)
有時候我們需要讓某些Pod能運行在同一處時,就需要使用Pod親和性來定義,如:我們要定義一個NMT(Nginx+Tomcat+MySQL),若這三個Pod分別被調度三個不同地域的機房中,比如北京,上海,深圳等地,那肯定不是我們所希望的,這時我們就需要使用Pod親和性來定義這三個Pod能運行在相同的Node 或 相鄰的Node上。
而在另外一些情況下,我們又希望某些Pod一定不能運行在相同Node上,這時就需要使用Pod的非親和性了。
3. 污點 和 污點容忍調度
Taints(污點):是定義在Node上的
Tolerations(容忍): 是定義在Pod上的
污點:指有時我們希望不將某些Pod調度到某些Node上,這時我們可對某些Node上打上污點,然后,當我們創建Pod時,我們可定義此Pod能容忍的污點范圍。
假如:NodeA上有5個污點: "吃喝嫖賭抽",而此次要調度的Pod比較大度,能容忍5個污點,而NodeA的污點正好是Pod能容忍污點的范圍內,於是Pod就能"嫁入"該NodeA。
假如,Pod運行到NodeA上后,后期我們又給該NodeA打上了新污點如:"坑蒙拐騙偷",此時Pod發現NodeA的惡性原來有這么多,怎么辦?通常有兩種選擇策略:
1. Pod繼續運行在NodeA上"艱苦度日"
2. Pod被驅逐,或者叫Pod逃離。
默認調度器的調度規則是:根據Node最大資源空閑率來進行均衡調度。
調度器:
預選策略
【注意:下面這些預選策略僅是其中一部分,並且k8s在進行預選時,它會是綜合評估的,即所有啟用的策略規則都要做判斷,所得結果越符合就越會被挑選出來,進入優選策略中】:
CheckNodeCondition: 檢查節點是否符合調度條件
GeneralPredicates:
HostName: 這種是判斷Pod是否定義了pod.spec.hostname屬性,若定義了,就在預選時,看看這些Node上是否存在相同主機名的Pod,若有,就排除該Node; 這可能是因為,某些Pod是需要有固定主機名,才會使用。
PodFitsHostPorts: 此預選策略是判斷 pods.spec.containers.ports.hostPort 屬性是否定義了,若定義了就表示該Pod要綁定到Node上指定的Port上,這時在進行預選時,就要判斷這個端口是否被占用了,若占用就會排除該Node。
MatchNodeSeletctor:此預選策略會判斷 pods.spec.nodeSelector 屬性是否定義了,若定義了就根據Pod所定義的NodeSelector來選出匹配指定標簽的Node。
PodFitsResources: 此預選策略會判斷 Node上是否符合運行Pod所需的最小空閑資源。
NoDiskConfict: 用於判斷若Pod定義了存儲卷,則要檢查該存儲卷在該Node上是否可用,若Node能滿足Pod存儲卷的使用需求,則表示此Node可用。
PodToleratesNodeTaints:檢查Pod上的spec.tolerations可容忍的污點是否完全包含Node上定義的污點,若完全包含,則表示Pod能容忍該Node節點的污點,否則該Node不會通過預選。
PodToleratesNodeNoExecuteTaints:
#首先,污點並非單一屬性,它有三種屬性,NoExcute是污點的一種屬性。
#此檢查是,若Pod被調度到某Node上時,最初Node上沒有Pod不能容忍的污點,但后來Node的污點改變了,多了Pod不能容忍的污點,這時要怎么處理那?默認是繼承既定事實,繼續在該Node上運行。
# 第二種是不接受,也就是檢查此屬性,此時Node會驅逐該Pod,讓該Pod重新調度到新Node上。
CheckNodeLabelPresence:這是定義在選擇Node時,檢查Node上是否存在指定的標簽,若存在則可調度。此規則默認不啟用。
CheckServiceAffinity: 當我們需要創建一些Pod時,這些Pod都使用相同的Serivce,這時,若啟用該預選策略,則將這些Pod優先調度到 已存在較多該Service下的Pod的Node上。
#下面三個默認啟用,因為這三個是最大的三個雲運營商
MaxEBSVolumeCount:此預選策略是指若Pod需要使用亞馬遜的EBS(彈性塊存儲服務),則檢查該EBS是否已經達到最大運行掛載數39個,若沒有,則表示還有存儲空間可用。
MaxGCEPDVolumeCount: GCEPD:是Google的雲環境中的持久存儲,默認是16個
MaxAzureDiskVolumeCount: 最大是16個
CheckVolumeBinding:檢查Node上是否存在以綁定 和 未綁定的PVC,若Node上有PVC,並且沒有被綁定則能滿足Pod運行的需求。
NoVolumeZoneConflict:它是在給定了區域限制的情況下,Zone在IDC機房是有限制的,他們通常會按房間,機櫃,機架來划分范圍,假如機架上有20台服務器,每2台一個區域,這個就是一個邏輯區域,此配置項的含義就是檢查區域中Node上是否存在邏輯卷沖突。
CheckNodeMemoryPressure:檢查Node上是否存在內存壓力,即若某Node上已經出現內存緊張的情況了,那就不要在往它上面調度了。
CheckNodePIDPressure:檢查Node上是否存在PID壓力過大的情況,即若某Node上運行的進程過多,導致PID可能緊張,這時在選擇調度時,也不會選擇它。
CheckNodeDiskPressure:檢查Node上是否存在磁盤使用壓力過大的情況
MatchInterPodAffinity:檢查Node上是否滿足Pod的親和性,假如Pod是Nginx,它是要為Tomcat Pod做代理的,那么在調度tomcat Pod時,就會檢查Node上是否有Nginx Pod,若有這個非常親和的Pod則優先調度到該Node上。
優選函數:
默認啟用了7個紅色的優選函數,它們的得分相加值越高,該節點越優選被選中。
LeastRequested: 【與它相反對一個優選函數:MostRequested,它是Node資源占用越高得分越高,有點像是,先將某Node的資源先全部占滿的意味,然后空出部分Node.】
計算得分的算法公式=(cpu((capacity - sum(requested))*10 / capacity) + memory((capacity - sum(requested))*10 / capacity))/2
此公式的含義:
假如當前有16個核心,已經用了8個核心,那么總的使用率就是 (16-8)/16 = 0.5 ,而乘以10,是為了計算得分方便,將其轉化為0~10之間的數,
而內存的計算含義一樣,假如內存32G,已經用了8G,則使用率為 (32-8)/32 = 0.75 ,乘以10
將CPU + Memory/2 就可以得到一個平均值。該平均值越高,表示該節點的資源空閑率越高。
BalanceResourceAllocation:
它表示CPU和內存資源占用率的相近程度,作為評估標准,越接近也優選選擇,比如: Node1的CPU和內存使用率一個48%一個50%,而Node2的CPU和內存使用率都是50%,則Node2勝出。
#它通常要和LeastRequested 結合來評估Node的資源利用率的。
#兩個組合主要是為了平衡各Node上資源的利用率。
NodePreferAvoidPods:
根據Node節點上是否定義了注解信息"scheduler.alpha.kubernetes.io/preferAvoidPods",若沒有則該Node得分為10,而且權重為1萬,有此注解則說明該Node是適合運行Pod的。而對於那些有Replicas Controller的控制器則,該Node的得分為0,此時則表示Pod不能運行在該Node上,但這不能決定Pod一定不能運行在該Node上,因為最終是要看總體得分的。
注解信息:
# kubectl describe node node1.zcf.com
Name: node1.zcf.com
Roles: node
..........
Annotations: node.alpha.kubernetes.io/ttl: 0 #這就是所謂的node上的注解信息。
volumes.kubernetes.io/controller-managed-attach-detach: true
TaintToleration:
基於Pod對調度Node上的污點容忍度來評估是否可調度到該Node上。
簡單說:就是取出Pod對象中定義的spec.tolerations列表,查看其中能容忍的污點,然后,一個一個去對比Node上存在的污點,若匹配的越多,則該Node的得分越低,就越不會調度到該Node上。
SelectorSpreading:
此優化函數是希望將相同標簽選擇器選取的Pod盡可能的散開到多個Node上。因此假如:ReplicaSet控制器,已經在B上創建了一個Pod,那么它要再創建一個Pod時,此優選函數在計算A,B,C三個Node的得分時,會根據那個Node上擁有此ReplicaSet控制器的標簽選擇器所匹配的Pod數量,來評分,數量越少,分數越高,反之越低。
而使用標簽選擇器的資源對象有:Service,Replication Controller,ReplicaSet,StatefulSet。
InterPodAffinity:
遍歷對象的親和性條目,並將能夠匹配到給定節點的條目的條目數相加結果值越大得分越高。
簡單說:NodeA上運行了3個Pod,NodeB上運行了6個Pod,NodeC上運行了5個Pod,現在要調度一個Pod到其中一個Node上,而該Pod的比較親和某類Pod,此優選函數就會將這些Node上,所有匹配該Pod親和條目的數量相加,值越大,則越得分越高。其中條目的理解,我還不是很懂。
NodeAffinity:
它是根據pod中定義的nodeSeletor來對Node做親和性檢查, 能成功匹配的數量越多,則得分越高。
NodeLabel:
根據Node是否有某些標簽來做評估,然后計算得分的,此優選函數只關注標簽,不關注值,只有Node上有這個標簽就可以得分。
ImageLocality:
它評分的標准是,判斷Node上是否有運行該Pod的鏡像,若有則得分,否則不得分。
但它評估鏡像是根據鏡像總的體積大小來計算得分的,例如:NodeA上有1個鏡像1G,NodeB上有2鏡像,NodeC上有3個鏡像,而運行此Pod需要4個鏡像,其中一個鏡像1G,其它都是比較小,這時NodeA的得分會最高。
#實驗:
1. 定義一個Pod,並設置其nodeSelector,若指定的nodeSeletor標簽沒有匹配到任何Node,則Pod將處於Pinding狀態,只有你給某個Node打上指定的標簽和值后,該Pod才會被調度上去。
#實驗2:
kubectl explain pods.spec.affinity
nodeAffinity: 定義Node的親和性
podAffinity: 定義Pod的親和性
podAntiAffinity:定義Pod非親和性
nodeAffinity:
# 軟親和性,即若能滿足則一定運行在滿足條件的Node上,否則運行在其它Node上也不是不可以。
preferredDuringSchedulingIgnoredDuringExecution
#硬親和性,若不能滿足運行條件,則不運行Pod。
requiredDuringSchedulingIgnoredDuringExecution
nodeSelectorTerms:
matchExpressions : 這是更強大的一種方式,它是配表達式的。
matchFields:
key: 要對那個label key做匹配操作
operator:指定你是做什么比較操作,支持: In/NotIn(包含/不包含), Exists/DoesNotExist(存在/不存在) , Gt/Lt(大於/小於)
values:若操作符為Exists/DoesNotExists則,values必須為空。
vim pod-node-required-affinity.yaml apiVersion: v1 kind: Pod metadata: name: pod-node-affinity-1 labels: app: myapp tier: frontend spec: containers: - name: myapp image: ikubernetes/myapp:v1 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: zone operator: In values: - foo - bar #默認node上沒有打上zone=foo 或 zone=bar的標簽,此時是硬親和,若不能滿足條件,則無法調動. vim pod-node-affinity.yaml apiVersion: v1 kind: Pod metadata: name: pod-node-affinity-1 labels: app: myapp tier: frontend spec: containers: - name: myapp image: ikubernetes/myapp:v1 affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - preference: matchExpressions: - key: zone operator: In values: - foo - bar weight: 60 # 此為軟親和,若node上有zone=foo 或 zone=bar,則優先調度上去,若全部都沒有,勉強選一個調度上去也不是不可以
為啥有了節點親和性,還要有Pod親和性?
假如我們有一個分布式跨地域的大集群,現在想構建一個NMT的架構,為了實現這個目的,我完全可以給Node上打標簽,然后,讓NMT這些Pod都匹配這些標簽,然后被調度到這些Node上去運行,這不是也可以實現我們的目的嗎?
看上去沒有問題,但問題是,假如你跨地域的分布式K8s集群,分別分布在北京,上海,深圳,杭州等地,你就不得不精心規划不同地域,不同IDC,不同機房,不同機櫃的Node標簽規划,否則你很可能在將來實現這個目的是變得困難不堪,你可以想象一下,你希望讓一組NMT運行在北京某IDC的某機房中,這樣一個NMT架構在工作時,才能更加高效的通信,你想實現這樣的控制,你不規划Node的標簽,你讓預選策略如何按照你的想法去調度那?這還僅是一方面,你為了NMT架構能分離開,北京一套,上海一套.....你也要配置Pod的在選擇Node標簽時,設定它親和哪些標簽,所以工作量你可自己評估。
Pod親和性的邏輯時,我要部署NMT環境,我的MySQL Pod第一個被調用,我不管調度器會把MySQL Pod調度到哪里,反正只要調度完成,並且運行了,我后續的Nginx,Tomcat的Pod是親和MySQL Pod的,MySQL Pod在哪個Node上,那在調度NT時,就更加優先調度到那個Node上。
這樣說看上去很簡單,但仔細想想,MySQL被調度到某Node上,要是將NT也調度到那個Node上合適嗎?若那個Node沒有這么多資源運行它們怎么辦?能不能將NT調度到M所在Node旁邊的Node上,或同機房的Node中?那這算親和嗎? 其實也算,但怎么知道運行MySQL Pod的Node ,它旁邊的Node是否那個主機?這其實還是需要借助於Node標簽來實現,因此為集群標簽的規划是在所難免的,因為你必須給預選策略一種判斷標准,哪些是相同機櫃,哪些是不同機房對吧,否則鬼知道集群中那么多Node那個和那個是鄰居對吧,當然若你靠主機名來判斷也不是不可以,你就要定義根據主機名的判斷標准了。所以,比較通用的方法是,給每個Node打上不同的標簽,如:北京機房1 機櫃20 機架號等等來定義,這樣根據這些標簽值,就可以判斷Node和Node之間的臨近關系了。
打個比方: MySQL Pod被調度到 rack=bjYZ1 ,那后續調度NT時,就會優選rack=bjYZ1的Node,只要有這個標簽值得Node會被優選調度,這樣NMT它們就都可以運行在北京亦庄的機房中了。
kubectl pods.spec.affinity.podAffinity.
requiredDuringSchedulingIgnoredDuringExecution : 硬限制
labelSelector: 指定親和Pod的標簽
namespaces: 你要指定親和Pod,你就需要指定你親和的Pod屬於哪個名稱空間,否則默認是Pod創建在那個名稱空間,就匹配那個名稱空間的Pod
Pod親和性示例: vim pod-required-affinity.yaml apiVersion: v1 kind: Pod metadata: name: web1-first labels: app: web1 tier: frontend spec: containers: - name: myapp image: harbor.zcf.com/k8s/myapp:v1 --- apiVersion: v1 kind: Pod metadata: name: db1-second labels: app: db1 tier: db1 spec: containers: - name: busybox image: busybox: latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "sleep 3600"] affinity: podAffinity: #若需要測試Pod的反親和性,只需要修改podAffinity為 podAntiAffinity 即可測試 requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app, operator: In, values: ["web1","web2"]} topologyKey: kubernetes.io/hostname #通過默認Node上主機名標簽來臨時代表地區標簽,即:只有主機名相同,就認為是在相同位置的。 #Pod反親和測試: Pod親和性示例: vim pod-required-affinity.yaml apiVersion: v1 kind: Pod metadata: name: web1-first labels: app: web1 tier: frontend spec: containers: - name: myapp image: harbor.zcf.com/k8s/myapp:v1 --- apiVersion: v1 kind: Pod metadata: name: db1-second labels: app: db1 tier: db1 spec: containers: - name: busybox image: busybox: latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "sleep 3600"] affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app, operator: In, values: ["web1","web2"]} topologyKey: kubernetes.io/hostname #第一次測試: 使用存在的標簽來測試,web1會運行在一個節點上,db1一定不會運行在web1所在的Node上。 topologyKey: zone #第二次測試: 使用此key,因為兩個Node上都沒有此標簽key,因此預選策略將認為,沒有找到不同區域的Node,因此db1將處於Pinding狀態,無法啟動。
vim myapp-toleration.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-toler1 spec: replicas: 2 selector: matchLabels: app: myapp release: canary template: metadata: labels: app: myapp release: canary spec: containers: - name: myapp image: ikubernetes/myapp:v2 ports: - name: http containerPort: 80 tolerations: - key: "node-type" operator: "Equal" value: "production" effect: "NoSchedule" #說明: 在定義Pod的容忍度時,若指定為NoSchedule,則不能定義tolerationSeconds,即容忍不了時,可寬限多久被驅逐。 # 若定義為NoExecute 則可以定義tolerationSeconds。 # Equal:做等值比較,即node-type標簽 和 其值必須完全匹配。 # Exists :做存在性比較,即只要node-type標簽存在,即可匹配,不看其標簽值是否相同。 #測試NoExecute影響 #目前node2.zcf.com 上定義了node-type污點的影響為: NoExecute ............ tolerations: - key: "node-type" operator: "Exists" value: "" #設置node-type的值為空,因為Exists是做標簽判斷,只要node-type標簽存在,即可. effect: "NoSchedule" #定義污點影響度為NoSchedule,即若Node上沒有這個node-type污點標簽,就不調度到其上. #若Node上污點影響(effect)為NoExecute,它是不包含NoSchedule的,即Node是不允許不能容忍NoExecute的Pod調度到自己上面的。 #若Pod能容忍node-type標簽的污點,無論它是什么值,什么影響,都可以容忍 ............... tolerations: - key: "node-type" operator: "Exists" value: "" effect: "" #刪除節點上的污點: kubectl taint node node01.zcf.com node-type- #注意:node-type是:node01上的標簽,最后的“-”是刪除該標簽的意思。