Kubernetes Pod排程說明
簡介
Scheduler 是 Kubernetes 的排程器,主要任務是把定義的Pod分配到叢集的節點上,聽起來非常簡單,但要考慮需要方面的問題:
- 公平:如何保證每個節點都能被分配到資源
- 資源高效利用:叢集所有資源最大化被使用
- 效率:排程效能要好,能夠儘快的對大批量的Pod完成排程工作
- 靈活:允許使用者根據自己的需求控制排程的流程
Scheduler 是作為單獨的服務執行的,啟動之後會一直監聽API Server,獲取 podSpec.NodeName為空的Pod,對每個Pod都會建立一個buiding,表明該Pod應該放在哪個節點上
排程過程
排程流程:首先過濾掉不滿足條件的節點,這個過程稱為predicate;然後對通過的節點按照優先順序的順序,這個是priority;最後從中選擇優先順序最高的節點。如果中間有任何一步報錯,則直接返回錯誤資訊。
Predicate有一系列的演算法可以使用:
- PodFitsResources:節點上剩餘的資源是否大於 Pod 請求的資源
- PodFitsHost:如果Pod指定了nodeName,檢查節點名稱是否和nodeName匹配
- PodFitsHostPort:節點上已經使用的port是否和Pod申請的port衝突
- PodSelectorMatches:過濾和Pod指定的 label 不匹配的節點
- NoDiskConflict:已經 mount 的 volume 和 Pod 指定的volume不衝突,除非他們都是隻讀
如果在predicate過程中沒有適合的節點,Pod會一直處於Pending狀態,不斷重新排程,直到有節點滿足條件,經過這個步驟,如果多個節點滿足條件,就會進入priority過程:按照優先順序大小對節點排序,優先順序由一系列鍵值對組成,鍵是該優先順序的名稱,值是它的權重,這些優先順序選項包括:
- LeastRequestedPriority:通過計算CPU和Memory的使用率來決定權重,使用率越低權重越高,換句話說,這個優先順序傾向於資源使用率低的節點
- BalanceResourceAllocation:節點上CPU和Memory使用率非常及接近,權重就越高,這個要和上邊的一起使用,不可單獨使用
- ImageLocalityPriority:傾向於已經要使用映象的節點,映象的總大小值越大,權重越高
通過演算法對所有的優先順序專案和權重進行計算,得出最終的結果
自定義排程器
除了Kubernetes自帶的排程器,也可以編寫自己的排程器,通過spec.schedulername引數指定排程器的名字,可以為Pod選擇某個排程器進行排程,比如下邊的Pod選擇my-scheduler進行排程,而不是預設的default-scheduler
apiVersion: v1
kind: Pod
metadata:
name: scheduler-test
labels:
name: example-scheduler
spec:
schedulername: my-scheduler
containers:
- name: Pod-test
image: nginx:v1
下邊開始正式介紹Pod的各種排程方法!!!
一、親和性
注意,以下所有的測試都是1Master、1Node的情況下:
[root@Centos8 scheduler]# kubectl get node NAME STATUS ROLES AGE VERSION centos8 Ready master 134d v1.15.1 testcentos7 Ready <none> 133d v1.15.1
1、節點親和性
pod.spec.affinity.nodeAffinity
- preferredDuringSchedulingIgnoredDuringExecution:軟策略
- 軟策略是偏向於,更想(不)落在某個節點上,但如果實在沒有,落在其他節點也可以
- requiredDuringSchedulingIgnoredDuringExecution:硬策略
- 硬策略是必須(不)落在指定的節點上,如果不符合條件,則一直處於Pending狀態
requiredDuringSchedulingIgnoredDuringExecution硬策略
vim node-affinity-required.yaml
apiVersion: v1 kind: Pod metadata: name: affinity-required labels: app: node-affinity-pod spec: containers: - name: with-node-required image: nginx:1.2.1 imagePullPolicy: IfNotPresent affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname #節點名稱 operator: NotIn #不是 values: - testcentos7 #node節點
[root@Centos8 ~]# kubectl get node --show-labels #檢視node節點標籤 NAME STATUS ROLES AGE VERSION LABELS centos8 Ready master 133d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=centos8,kubernetes.io/os=linux,node-role.kubernetes.io/master= testcentos7 Ready <none> 133d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=testcentos7,kubernetes.io/os=linux ## 目前只有兩個節點,一個master 一個node,策略中表示此Pod必須不在testcentos7這個節點上 ## Pod建立之後,因為除去testcentos7節點已再無其他node,Master節點又不能被排程,所以一直處於Pending狀態
[root@Centos8 scheduler]# kubectl create -f node-affinity-required.yaml pod/affinity-required created [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE affinity-required 0/1 Pending 0 4s [root@Centos8 scheduler]# kubectl describe pod affinity-required default-scheduler 0/2 nodes are available: 1 node(s) didn't match node selector, 1 node(s) had taints that the pod didn't tolerate.
將yaml檔案中,NotIn改為In
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname #節點名稱 operator: In #是,存在 values: - testcentos7 #node節點
再次建立,已經落在指定node節點中
[root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE affinity-required 1/1 Running 0 11s 10.244.3.219 testcentos7
preferredDuringSchedulingIgnoredDuringExecution軟策略
vim node-affinity-preferred.yaml
apiVersion: v1 kind: Pod metadata: name: affinity-preferred labels: app: node-affinity-pod spec: containers: - name: with-node-preferred image: nginx:1.2.1 imagePullPolicy: IfNotPresent affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 #權重為1,軟策略中權重越高匹配到的機會越大 preference: #更偏向於 matchExpressions: - key: kubernetes.io/hostname #node名稱 operator: In #等於,為 values: - testcentos7 #node真實名稱
## 更想落在node節點名稱為testcentos7的node中 [root@Centos8 scheduler]# kubectl create -f node-affinity-prefered.yaml pod/affinity-prefered created [root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE affinity-prefered 1/1 Running 0 9s 10.244.3.220 testcentos7
更改一下策略,將node節點名稱隨便更改為不存在的node名稱,例如kube-node2
affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 #權重為1,軟策略中權重越高匹配到的機會越大 preference: #更偏向於 matchExpressions: - key: kubernetes.io/hostname #node名稱 operator: In #等於,為 values: - kube-node2 #node真實名稱
[root@Centos8 scheduler]# kubectl create -f node-affinity-prefered.yaml pod/affinity-prefered created ##建立後,同樣是落在了testcentos7節點上,雖然它更想落在kube-node2節點上,但沒有,只好落在testcentos7節點中 [root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE affinity-prefered 1/1 Running 0 17s 10.244.3.221 testcentos7
軟硬策略合體
vim node-affinity-common.yaml
apiVersion: v1 kind: Pod metadata: name: affinity-node labels: app: node-affinity-pod spec: containers: - name: with-affinity-node image: nginx:v1 imagePullPulicy: IfNotPresent affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: NotIn values: - k8s-node2 preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: source operator: In values: - hello
軟硬結合達到一個更為準確的node選擇,以上檔案意思為此Pod必須不存在k8s-node2節點中,其他的節點都可以,但最好落在label中source的值為hello的節點中
鍵值運算關係
- In:label 的值在某個列表裡
- NotIn:label 的值不在某個列表中
- Gt:label 的值大於某個值
- Lt:label 的值小於某個值
- Exists:某個 label 存在
- DoesNotExist:某個 label 不存在
如果nodeSelectorTerms下面有多個選項,滿足任何一個條件就可以了;如果matchExpressions有多個選項,則必須滿足這些條件才能正常排程
2、Pod親和性
pod.spec.affinity.podAffinity/podAntiAffinity
- preferedDuringSchedulingIgnoredDuringExecution:軟策略
- 軟策略是偏向於,更想(不)落在某個節點上,但如果實在沒有,落在其他節點也可以
- requiredDuringSchedulingIgnoredDuringExecution:硬策略
- 硬策略是必須(不)落在指定的節點上,如果不符合條件,則一直處於Pending狀態
先建立一個測試Pod
vim pod.yaml
apiVersion: v1 kind: Pod metadata: name: pod-1 labels: app: nginx type: web spec: containers: - name: pod-1 image: nginx:1.2.1 imagePullPolicy: IfNotPresent ports: - name: web containerPort: 80
[root@Centos8 scheduler]# kubectl create -f pod.yaml pod/pod-1 created [root@Centos8 scheduler]# kubectl get pod --show-labels NAME READY STATUS RESTARTS AGE LABELS pod-1 1/1 Running 0 4s app=nginx,type=web
requiredDuringSchedulingIgnoredDuringExecution Pod硬策略
vim pod-affinity-required.yaml
apiVersion: v1 kind: Pod metadata: name: affinity-required labels: app: pod-3 spec: containers: - name: with-pod-required image: nginx:1.2.1 imagePullPolicy: IfNotPresent affinity: podAffinity: #在同一域下 requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app #標籤key operator: In values: - nginx #標籤value topologyKey: kubernetes.io/hostname #域的標準為node節點的名稱
以上檔案策略為:此必須要和有label種app:nginx的pod在同一node下
建立測試:
[root@Centos8 scheduler]# kubectl create -f pod-affinity-required.yaml pod/affinity-required created [root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE affinity-required 1/1 Running 0 43s 10.244.3.224 testcentos7 pod-1 1/1 Running 0 10m 10.244.3.223 testcentos7 # 和此標籤Pod在同一node節點下
將podAffinity改為podAnitAffinity,使它們不在用於node節點下
apiVersion: v1 kind: Pod metadata: name: required-pod2 labels: app: pod-3 spec: containers: - name: with-pod-required image: nginx:1.2.1 imagePullPolicy: IfNotPresent affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app #標籤key operator: In values: - nginx #標籤value topologyKey: kubernetes.io/hostname #域的標準為node節點的名稱
此策略表示,必須要和label為app:nginx的pod在不用的node節點上
建立測試:
[root@Centos8 scheduler]# kubectl create -f pod-affinity-required.yaml pod/required-pod2 created [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE affinity-required 1/1 Running 0 9m40s pod-1 1/1 Running 0 19m required-pod2 0/1 Pending 0 51s ## 由於我這裡只有一個節點,所以required-pod2只能處於Pending狀態
preferedDuringSchedulingIgnoredDuringExecution Pod軟策略
vim pod-affinity-prefered.yaml ... apiVersion: v1 kind: Pod metadata: name: affinity-prefered labels: app: pod-3 spec: containers: - name: with-pod-prefered image: nginx:v1 imagePullPolicy: IfNotPresent affinity: podAntiAffinity: #不在同一個域下 preferedDuringSchedulingIgnoredDuringExecution: - weight: 1 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - pod-2 topologyKey: kubernetes.io/hostname ...
軟策略和硬策略的方法基本類似,只是新增了權重,表示更喜歡而已,也可以接受其他,在此就不再演示
親和性/反親和性排程策略比較如下:
排程策略 | 匹配標籤 | 操作符 | 拓撲域支援 | 排程目標 |
---|---|---|---|---|
nodeAffinity | 主機 | In,NotIn,Exists,DoesNotExists,Gt,Lt | 否 | 指定主機 |
podAffinity | Pod | In,NotIn,Exists,DoesNotExists,Gt,Lt | 是 | pod與指定pod在一拓撲域 |
podAnitAffinity | Pod | In,NotIn,Exists,DoesNotExists,Gt,Lt | 是 | pod與指定pod不在一拓撲域 |
二、汙點(Taint)和容忍(Toleration)
節點親和性,是Pod的一種屬性(偏好或硬性要求),它使Pod被吸引到一類特定的節點,Taint則相反,它使節點能夠 排斥 一類特定的Pod
Taint與Toleration相互配合,可以用來避免Pod被分配到不合適的節點上,每個節點上都可以應用一個或兩個taint,這表示對那些不能容忍這些taint和pod,是不會被該節點接受的,如果將toleration應用於pod上,則表示這些pod可以(但不要求)被排程到具有匹配taint的節點上
注意,以下所有的測試都是1Master、1Node的情況下:
[root@Centos8 scheduler]# kubectl get node NAME STATUS ROLES AGE VERSION centos8 Ready master 134d v1.15.1 testcentos7 Ready <none> 133d v1.15.1
1、汙點(Taint)
(1)汙點的組成
使用kubectl taint 命令可以給某個node節點設定汙點,Node被設定上汙點之後就和Pod之間存在了一種相斥的關係,可以讓Node拒絕Pod的排程執行,甚至將已經存在得Pod驅逐出去
每個汙點的組成如下:
key=value:effect
每個汙點有一個 key 和 value 作為汙點標籤,其中 value 可以為空,effect描述汙點的作用,當前 taint effect 支援如下三個選項:
- NoSchedule:表示 k8s 不會將Pod排程到具有該汙點的Node上
- PreferNoSchedule:表示 k8s 將盡量避免將Pod排程到具有該汙點的Node上
- NoExecute:表示 k8s 將不會將Pod排程到具有該汙點的Node上,同時會將Node上已有的Pod驅逐出去
(2)汙點的設定、檢視和去除
k8s的master節點本身就帶有汙點,這也是為什麼k8s在排程Pod時,不會排程到master節點的原因,具體檢視如下:
[root@Centos8 scheduler]# kubectl describe node centos8 Taints: node-role.kubernetes.io/master:NoSchedule ## 設定汙點 kubectl taint nodes [node name] key1=value:NoSchedule ## 節點說明中,檢視Taint欄位 kubectl describe node [node name] ## 去除汙點 kubectl taint nodes [node name] key1:NoSchedule-
測試效果:
## 檢視當前節點所擁有Pod,都在testcentos7中 [root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE affinity-required 1/1 Running 0 68m 10.244.3.224 testcentos7 pod-1 1/1 Running 0 78m 10.244.3.223 testcentos7 required-pod2 0/1 Pending 0 59m <none> <none> ## 給testcentos7設定NoExecute汙點 [root@Centos8 scheduler]# kubectl taint nodes testcentos7 check=vfan:NoExecute node/testcentos7 tainted ## 檢視Pod有沒被驅逐出去 [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE required-pod2 0/1 Pending 0 62m ## 只剩一個Pending狀態的Pod,因為他還沒建立,所以還未分配Node
檢視 testcentos7 節點資訊
[root@Centos8 scheduler]# kubectl describe node testcentos7
Taints: check=vfan:NoExecute
目前所有的節點都被打上了汙點,新建Pod測試下效果:
[root@Centos8 scheduler]# kubectl create -f pod.yaml pod/pod-1 created [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE pod-1 0/1 Pending 0 4s required-pod2 0/1 Pending 0 7h18m
新建的Pod會一直處於Pending狀態,因為沒有可用的Node節點,這時候就可以使用容忍(Toleration)了
2、容忍(Toleration)
設定了汙點的Node將根據 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute和Pod之間產生互斥的關係,Pod將在一定程度上不會被排程到 Node 上。但我們可以在 Pod 上設定容忍(Toleration),意思是設定了容忍的 Pod 將可以容忍汙點的存在,可以被排程到存在汙點的Node上
Pod.spec.tolerations
tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" tolerationSeconds: 3600 - key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" - key: "key2" operator: "Exists" effect: "NoSchedule"
- 其中 key、value、effect 要與Node中的 taint 保持一致
- operator 的值為 Exists 將會忽略 value 的值
- tolerationSeconds 用於描述當Pod需要驅逐時可以在Node上繼續保留執行的時間
(1)當不指定key時,表示容忍所有汙點的key:
tolerations: - operator: "Exists"
例如:
vim pod3.yaml
apiVersion: v1 kind: Pod metadata: name: pod-3 labels: app: nginx type: web spec: containers: - name: pod-3 image: nginx:1.2.1 imagePullPolicy: IfNotPresent ports: - name: web containerPort: 80 tolerations: - operator: "Exists" effect: "NoSchedule"
[root@Centos8 scheduler]# kubectl create -f pod3.yaml pod/pod-3 created [root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-1 0/1 Pending 0 14m <none> <none> pod-2 1/1 Running 0 11m 10.244.3.229 testcentos7 pod-3 1/1 Running 0 9s 10.244.0.107 centos8
yaml策略為:可以容忍所有為NoSchedule的汙點,因為centos8為master節點,汙點預設為NoSchedule,所以Pod-3被排程到master節點
(2)當不指定 offect 值時,表示容忍所有的汙點型別
tolerations: - key: "key1" value: "value1" operator: "Exists"
(3)Pod容忍測試用例:
vim pod2.yaml
apiVersion: v1 kind: Pod metadata: name: pod-2 labels: app: nginx type: web spec: containers: - name: pod-2 image: nginx:1.2.1 imagePullPolicy: IfNotPresent ports: - name: web containerPort: 80 tolerations: - key: "check" operator: "Equal" value: "vfan" effect: "NoExecute"
[root@Centos8 scheduler]# kubectl create -f pod2.yaml pod/pod-2 created [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE pod-1 0/1 Pending 0 3m25s pod-2 1/1 Running 0 4s
設定容忍的Pod,可以正常排程到Node節點,而沒有設定容忍的,還一直處於Pending狀態
最後將Node汙點去除:
kubectl taint nodes testcentos7 check=vfan:NoExecute-
去除node汙點僅需在建立命令的最後加一個 - 即可
三、指定排程節點
注意,以下所有的測試都是1Master、1Node的情況下:
[root@Centos8 scheduler]# kubectl get node NAME STATUS ROLES AGE VERSION centos8 Ready master 134d v1.15.1 testcentos7 Ready <none> 133d v1.15.1
1、Pod.spec.nodeName 將 Pod 直接排程到指定的 Node 節點上,會跳過 Schedule 的排程策略,該匹配規則是強制匹配
vim nodeName1.yaml
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nodename-1 labels: app: web spec: replicas: 3 template: metadata: labels: app: web spec: nodeName: testcentos7 containers: - name: nodename-1 image: nginx:1.2.1 imagePullPolicy: IfNotPresent ports: - containerPort: 80
[root@Centos8 scheduler]# kubectl apply -f nodeName1.yaml deployment.extensions/nodename-1 created [root@Centos8 scheduler]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE nodename-1-7f4c7db4d4-hdcjv 1/1 Running 0 92s 10.244.3.240 testcentos7 nodename-1-7f4c7db4d4-xxrj8 1/1 Running 0 93s 10.244.3.238 testcentos7 nodename-1-7f4c7db4d4-zkt2c 1/1 Running 0 92s 10.244.3.239 testcentos7
以上策略表示,所有Deployment所有的副本,均要排程到testcentos7節點上
為了對比效果,修改yaml檔案中Node節點為centos8
nodeName: centos8
再次建立測試
[root@Centos8 scheduler]# kubectl delete -f nodeName1.yaml [root@Centos8 scheduler]# kubectl apply -f nodeName1.yaml deployment.extensions/nodename-1 created NAME READY STATUS RESTARTS AGE IP NODE nodename-1-7d49bd7849-ct9w5 1/1 Running 0 2m2s 10.244.0.112 centos8 nodename-1-7d49bd7849-qk9mm 1/1 Running 0 2m2s 10.244.0.113 centos8 nodename-1-7d49bd7849-zdphd 1/1 Running 0 2m2s 10.244.0.111 centos8
全部落在了centos8節點中
2、Pod.spec.nodeSelector:通過 kubernetes 的 label-selector 機制選擇節點,由排程策略匹配 label,而後排程 Pod 到目標節點,該匹配規則屬於強制約束
vim nodeSelect1.yaml
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: node-1 labels: app: web spec: replicas: 3 template: metadata: labels: app: myweb spec: nodeSelector: type: ssd # Node包含的標籤 containers: - name: myweb image: nginx:1.2.1 ports: - containerPort: 80
[root@Centos8 scheduler]# kubectl apply -f nodeSelect1.yaml deployment.extensions/node-1 created [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE node-1-684b6cc685-9lzbn 0/1 Pending 0 3s node-1-684b6cc685-lwzrm 0/1 Pending 0 3s node-1-684b6cc685-qlgjq 0/1 Pending 0 3s [root@Centos8 scheduler]# kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS centos8 Ready master 135d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=centos8,kubernetes.io/os=linux,node-role.kubernetes.io/master= testcentos7 Ready <none> 134d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=testcentos7,kubernetes.io/os=linux
以上策略表示:將Deployment的三個副本全部排程到標籤為type:ssd的Node節點上,因為沒有Node節點有此標籤,所以一直處於Pending狀態
下面我們將testcentos7節點打上標籤type:ssd
[root@Centos8 scheduler]# kubectl label node testcentos7 type=ssd node/testcentos7 labeled [root@Centos8 scheduler]# kubectl get pod NAME READY STATUS RESTARTS AGE node-1-684b6cc685-9lzbn 1/1 Running 0 4m30s node-1-684b6cc685-lwzrm 1/1 Running 0 4m30s node-1-684b6cc685-qlgjq 1/1 Running 0 4m30s
打上標籤後,Pod恢復Running狀態
文中有不足之處歡迎指出,不勝感激!