Pod調度器
API Server
接收客戶端提交Pod對象創建請求后的操作過程中,有一個重要的步驟是由調度程序(kube-scheduler
)從當前集群中選擇一個可用的最佳節點來接收並運行它,通常是默認的調度器(default-scheduler
)負責執行此類任務。對於每個待創建的Pod
對象來說,調度過程通常分為三個階段——預選、優選和選定三個步驟,以篩選執行任務的最佳節點。
Kubernetes 調度器概述
Kubernetes
系統的核心任務在於創建客戶端請求創建的Pod
對象並確保其以期望的狀態運行。創建Pod
對象時,調度器scheduler
負責為每一個未經調度的Pod
資源、基於一系列的規則集從集群中挑選一個合適的節點來運行它,因此也稱為Pod
調度器。調度過程中,調度器不會修改Pod
資源,而是從中讀取數據,並根據配置的策略挑選出最適合的節點,而后通過API
調用將Pod
綁定至挑選出的節點之上以完成調度過程。
Pod
調度器其核心目標是基於資源可用性將各Pod
資源公平地分布於集群節點之上。通過三個步驟完成調度操作:節點預選(Predicate)、節點優先級排序(Priority)及節點擇優(Select);如下圖所示
📌 節點預選
基於一系列預選規則(如PodFitsResources和MatchNode-Selector等)對每個節點進行檢查,將那些不符合條件的節點過濾掉從而完成節點預選。
📌 節點優選
對預選出的節點進行優先級排序,以便選出最適合運行Pod對象的一個節點。
📌 節點擇優
從優選級排序結果中挑選出優先級最高的節點運行Pod對象,當此類節點多於一個時,則從中隨機選擇一個。
常用的預選策略
簡單來說,預選策略就是節點過濾器,列如節點標簽必須能夠匹配到Pod資源的標簽選擇器(由
MatchNodeSelector
實現的規則),以及Pod
容器的資源請求量不能大於節點上剩余的可分配資源(由PodFitsResources
實現的規則)等。執行預選操作時,調度器將每個節點基於配置使用的預選策略以特定次序逐一篩選,並根據一票否決制進行節點淘汰。若預選后不存在任何一個滿足條件的節點,則Pod
處於Pending
狀態,直到至少有一個節點可用為止。
常用的預選策略如下:
✏️ CheckNodeCondition:檢查是否可以在節點報告磁盤、網絡不可用或未准備好的情況下將Pod
對象調度於其上。
✏️ HostName:若Pod
對象擁有spec.hostname
屬性,則檢查節點名稱字符串與此屬性值是否匹配。
✏️ PodFitsHostPorts:若Pod
容器定義了ports.hostPort
屬性,則檢查其值指定的端口是否已被節點上的其他容器或服務占用。
✏️ MatchNodeSelector:若Pod
對象定義了sepc.nodeSelector
屬性,則檢查節點標簽是否能匹配此屬性值。
✏️ NoDiskConflict:檢查Pod
對象請求的存儲卷在此節點是否可用,若不存在沖突則通過檢查。
✏️ PodFitsResources:檢查節點是否有足夠的資源(如CPU
、內存和GPU
等)滿足Pod
對象的運行需求。
✏️ PodToleratesNodeTaints:若Pod
對象定義了spec.tolerations
屬性,則檢查其值是否能夠接納節點定義的污點(taints),不過,它僅關注具有NoSchedule
和NoExecute
兩個效用標識的污點。
✏️ PodToleratesNodeNoExecuteTaints:若Pod
對象定義了spec.tolerations
屬性,則檢查其值是否能夠接納節點定義的NoExecute
類型的污點。
✏️ CheckNodeLabelPresence:僅檢查節點上指定的所有標簽的存在性,要檢查的標簽以及其可否存在取決於用戶的定義。
✏️ CheckServiceAffinity:根據當前Pod
對象所屬的Service
已有的其他Pod
對象所運行的節點進行調度,其目的在於將相同的Service
的Pod
對象放置在同一個或同一類節點上以提高效率。
✏️ MaxEBSVolumeCount:檢查節點上已掛載的EBS
存儲卷數量是否超過了設置的最大值,默認值為39。
✏️ MaxGCEPDVolumeCount:檢查節點上已掛載的GCE PD
存儲卷數量是否超過了設置的最大值,默認值為16。
✏️ MaxAzureDiskVolumeCount:檢查節點上已掛載的Azure Disk
存儲卷數量是否超過了設置的最大值,默認值為16。
✏️ CheckVolumeBinding:檢查節點上已綁定和未綁定的PVC
是否能夠滿足Pod
對象的存儲卷需求,對於已綁定的PVC
,此預選策略將檢查給定的節點是否能夠兼容相應的PV
,而對於未綁定的PVC
,預選策略將搜索那些可滿足PVC
申請的可用的PV
,並確保它可與給定的節點兼容。
✏️ NoVolumeZoneConflict:在給定了區域(zone
)限制的前提下,檢查在此節點上部署Pod
對象是否存在存儲卷沖突。某些存儲卷存在區域調度約束,於是,此類存儲卷的區域標簽(zone-labels
)必須與節點上的區域標簽完全匹配方可滿足綁定條件。
✏️ CheckNodeMemoryPressure:若給定的節點已經報告了存在內存資源壓力過大的狀態,則檢查當前Pod
對象是否可調度至此類節點之上。
✏️ CheckNodePIDPressure:若給定的節點已經報告了存在PID
資源壓力過大的狀態,則檢查當前Pod
對象是否可調度至此節點之上。
✏️ CheckNodeDiskPressure:若給定的節點已經報告了存在磁盤資源壓力過大的狀態,則檢查當前Pod
對象是否可調度至此節點之上。
✏️ MatchInterPodAffinity:檢查給定節點是否能夠滿足Pod
對象的親和性或反親和性條件,以用於實現Pod
親和性調度或反親和性調度。
常用的優選函數
預選策略篩選並生成一個節點列表后即進入第二階段的優選過程。在這個過程中,調度器向每個通過預選的節點傳遞一系列的優選函數來計算其優先級分值,優先級分值介於0到10之間,其中0表示不適合,10表示最適合托管該
Pod
對象。
常見的優選函數相關說明如下:
✏️ LeastRequestedPriority:由節點空閑資源與節點總容量的比值計算而來,即由CPU
或內存資源的總容量減去節點上已有Pod
對象需求的容量總和,再減去當前要創建的Pod
對象的需求容量得到的結果除以總容量。CPU
和內存具有相同的權重,資源空閑比例越高的節點得分就越高,其計算公式為:(cpu((capacity - sum(requested)) * 10/capacity) + memory((capacity - sum(requested)) * 10/capacity))/2 。
✏️ BalancedResourceAllocation:以CPU
和內存資源占用率的相近程序作為評估標准,二者越接近的節點權重越高。該優選函數不能單獨使用,需要和LeastRequestedPriority
組合使用來平衡優化節點資源的使用情況,以選擇那些在部署當前Pod
資源后系統資源更為均衡的節點。
✏️ NodePreferAvoidPodsPriority:此優選級函數權限默認為10000,根據節點是否設置了注解信息"scheduler.alpha.kubernetes.io/preferAvoidPods"來計算其優選級。計算方式是:給定的節點無此注解信息時,其得分為10乘以權重10000;存在此注解信息時,對於那些由ReplicationController
或ReplicaSet
控制器管控的Pod
對象的得分為0,其他Pod
對象會被忽略(得最高分)。
✏️ NodeAffinityPriority:基於節點親和性調度偏好進行優先級評估,它將根據Pod
資源中的nodeSelector
對給定節點進行匹配度檢查,成功匹配到的條目越多則節點的分越高。
✏️ TaintTolerationPriority:基於Pod
資源對節點的污點容忍調度偏好進行其優先級評估,它將Pod
對象的tolerations
列表與節點的污點進行匹配度檢查,成功匹配的條目越多,則節點得分越低。
✏️ SelectorSpreadPriority:首先查找與當前Pod
對相匹配的Service、ReplicationController、ReplicaSet(RS)和StatefulSet
,而后查找與這些選擇器匹配的現存Pod
對象及其所在的節點,則運行此類Pod
對象越少的節點得分將越高。簡單來說,會盡量將同一標簽選擇器匹配到的Pod
資源分散到不同的節點之上來運行。
✏️ InterPodAffinityPriority:遍歷Pod
對象的親和性條目,並將那些能夠匹配給定節點的條目的權重相加,結果值越大的節點得分越高。
✏️ MostRequestedPriority:與優選函數LeastRequestedPriority
的評估節點得分的方法相似,不同的是,資源站用比例越大的節點,其得分越高。
✏️ NodeLabelPriority:根據節點是否擁有特定的標簽來評估其得分,而無論其值為何。需要其存在時,擁有相應標簽的節點將獲得優先級,否則,不具有相應標簽的節點將獲得優先級。
✏️ ImageLocalItyPriority:基於給定節點上擁有的運行當前Pod
對象中的容器所依賴到的鏡像文件來計算節點得分,不具有Pod
依賴到的任何鏡像文件的節點其得分為0,而擁有相應鏡像文件的各節點中,所擁有的被依賴到的鏡像文件其體積之和越大則節點得分越高。
節點親和調度
節點親和性是調度程序用來確定
Pod
對象調度位置的一組規則,這些規則基於節點上的自定義標簽和Pod
對象上指定的標簽選擇器進行定義。節點親和性允許Pod
對象定義針對一組可以調度於其上的節點的親和性或反親和性,不過,它無法具體到某個特定的節點。如將Pod
調度至有着特殊CPU
的節點或一個可用區域內的節點之上。
節點親和性規則有兩種類型:硬親和性(required)和軟親和性(preferred)。硬親和性實現的是強制性規則,
Pod
調度時必須要滿足的規則,而在不存在滿足規則的節點時,Pod
對象會被置為Pending
狀態。而軟親和性規則實現的是一種柔和性調度限制,傾向於將Pod
對象運行於某類特定的節點之上,而調度器也將盡量滿足此需求,但在無法滿足調度需求時它將退而求其次地選擇一個不匹配規則的節點。
定義節點親和性規則的關鍵點有兩個,一是為節點配置合乎需求的標簽,另一個是為
Pod
對象定義合理的標簽選擇器,從而能夠基於標簽選擇出符合期望的目標節點。不過preferredDuringSchedulingIgnoredDuringExecution
和requiredDuringSchedulingIgnoredDuringExecution
名字中的后半段字符串IgnoredDuringExecution
隱含的意義所指,在Pod
資源基於節點親和性規則調度至某節點之后,節點標簽發生了改變而不再符合此節點親和性規則時,調度器不會將Pod
對象從此節點上移除,因為它僅對新建的Pod
對象生效。節點親和性模型如下:
節點硬親和性
📝 示例一
下面的配置清單示例中定義的Pod
對象,其使用節點硬親和性規則定義可將當前Pod
對象調度至擁有zone
標簽其值為foo
的節點之上:
# vim required-nodeaffinity-demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
接下來創建pod
,並查看其狀態
# kubectl apply -f required-nodeaffinity-demo-pod.yaml
pod/with-required-nodeaffinity created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity 0/1 Pending 0 4s
# kubectl describe pods with-required-nodeaffinity
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
通過kubectl get pods
和kubectl describe pods
查看pod
,可以發現pod
一直處於Pending
狀態,調度失敗,這是由於強制型的節點親和性和限制場景中不存在能夠滿足匹配條件的節點所致。
接下來給node
節點設置節點標簽,這也是設置節點親和性的前提之一。
# kubectl label node k8s-node1 zone=foo
node/k8s-node1 labeled
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity 1/1 Running 0 1m
# kubectl describe pods with-required-nodeaffinity
.....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Normal Scheduled <unknown> default-scheduler Successfully assigned default/with-required-nodeaffinity to k8s-node1
Normal Pulled 9s kubelet, k8s-node1 Container image "blwy/myapp:v1" already present on machine
Normal Created 9s kubelet, k8s-node1 Created container myapp
Normal Started 9s kubelet, k8s-node1 Started container myapp
通過給其中一個node
節點打上標簽后,可以看出pod
狀態一下就變成了Running
狀態,這是因為節點硬親和性匹配到了規則。
在定義節點親和性時,
requiredDuringSchedulingIgnoredDuringExecution
字段的值是一個對象列表,用於定義節點硬親和性,可以定義多個nodeSelectorTerm
對象,彼此之間為“邏輯或“的關系,進行匹配度檢查時,在多個nodeSelectorTerm
之間只要滿足其中之一即可。nodeSelectorTerm
用於定義節點選擇器條目,其值為對象列表,由一個或多個matchExpressions
對象定義的匹配規則組成,多個規則彼此之間為”邏輯與“的關系,意味着某節點的標簽需要完全匹配同一個nodeSelectorTerm
下所有的matchExpression
對象定義的規則才算成功通過節點選擇器條目的檢查。而matchExpressions
又可由一到多個標簽選擇器組成,多個標簽選擇器彼此間為”邏輯與“的關系。
📝 示例二
下面的資源配置清單示例中定義了調度擁有兩個標簽選擇器的節點選擇頭目。兩個標簽選擇器彼此間為“邏輯與”的關系。
為了測試,這里先給節點多打幾個標簽
# kubectl label node k8s-node1 ssd=true
node/k8s-node1 labeled
# kubectl label node k8s-node2 ssd=true
node/k8s-node2 labeled
# kubectl label node k8s-node2 zone=bar
node/k8s-node2 labeled
查看節點標簽
# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master1 Ready master 47d v1.16.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master1,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-node1 Ready <none> 47d v1.16.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node1,kubernetes.io/os=linux,ssd=true,zone=foo
k8s-node2 Ready <none> 47d v1.16.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node2,kubernetes.io/os=linux,ssd=true,zone=bar
編寫yaml
文件
# vim required-nodeaffinity-demo-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-2
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- {key: ssd, operator: Exists, values: []}
接下來創建pode2
,可以明確知道滿足條件的只有node2
節點,因為同時滿足了zone=foo
和ssd=true
# kubectl apply -f required-nodeaffinity-demo-pod2.yaml
pod/with-required-nodeaffinity-2 created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity-2 1/1 Running 0 4s
# kubectl describe pods with-required-nodeaffinity-2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned default/with-required-nodeaffinity-2 to k8s-node1
Normal Pulled 15s kubelet, k8s-node1 Container image "blwy/myapp:v1" already present on machine
Normal Created 15s kubelet, k8s-node1 Created container myapp
Normal Started 15s kubelet, k8s-node1 Started container myapp
❗ 注意:調度器在調度Pod
資源時,節點親和性僅是其節點預選策略中遵循的預選機制之一,其他配置使用的預選策略依然正常參與節點預選過程。例如下面的測試,將上面的資源配置清單修改如下
# vim required-nodeaffinity-demo-pod3.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-3
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
resources:
requests:
cpu: 6
memory: 20Gi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- {key: ssd, operator: Exists, values: []}
# kubectl apply -f required-nodeaffinity-demo-pod3.yaml
pod/with-required-nodeaffinity-3 created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity-3 0/1 Pending 0 4s
# kubectl describe pods with-required-nodeaffinity-3
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 2 node(s) didn't match node selector, 3 Insufficient cpu, 3 Insufficient memory.
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 2 node(s) didn't match node selector, 3 Insufficient cpu, 3 Insufficient memory.
上面的with-required-nodeaffinity-3
pod資源一直處於Pending
狀態,這是因為這里所有節點都無法滿足需求,這里所有節點配置在2核心CPU
和2GB
內存,無法滿足容器myapp
的需求,所以調度失敗。
節點軟親和性
節點軟親和性為節點選擇機制提供了一種柔性控制邏輯,被調度的
Pod
對象不再是"必須"而是"應該"放置於某些特定的節點之上,當條件不滿足時,也能夠接受編排於其他不符合條件的節點之上。還提供了weight
屬性以便用戶自定義其優先級,取值范圍是1~100
,數字越大優先級越高。
📝 示例
# vim deploy-with-preferred-nodeaffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-with-node-affinity
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
上面資源配置清單中定義了節點軟親和性以選擇運行在擁有zone=foo
和ssd
標簽(無論其值為何)的節點之上,其中zone=foo
是更為重要的傾向性規則,權重為60,相比ssd
標簽就沒有那么關鍵,權重為30。這樣一來,如果集群中擁有足夠多的節點,那么它將被此規則分為四類:同時滿足擁有zone=foo
和ssd
標簽、僅具有zone=foo
標簽、僅具有ssd
標簽,以及不具備此兩個標簽,如下圖所示:
# kubectl apply -f deploy-with-preferred-nodeaffinity-demo.yaml
deployment.apps/myapp-deploy-with-node-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-with-node-affinity-6d7884b4c8-kdg5n 1/1 Running 0 116s 10.244.1.11 k8s-node1 <none> <none>
myapp-deploy-with-node-affinity-6d7884b4c8-rjn99 1/1 Running 0 116s 10.244.1.10 k8s-node1 <none> <none>
myapp-deploy-with-node-affinity-6d7884b4c8-rnd4r 1/1 Running 0 116s 10.244.2.10 k8s-node2 <none> <none>
這里只有兩個工作節點,可以從上面的結果看出,node1
節點運行了兩個pod
,這是因為上面定義的規則中,node1
節點同時具有zone=foo
標簽和ssd
標簽,且權重為60。
Pod 資源親和調度
出於高效通信的需求,偶爾需要把一些
Pod
對象組織在相近的位置(同一節點、機架、區域或地區等),如一個業務的前端Pod
和后端Pod
等,此時可以將這些Pod
對象間的關系稱為親和性。偶爾,出於安全或分布式等原因也有可能需要將一些Pod
對象在其運行的位置上隔離開來,如在每個區域運行一個應用代理Pod
對象等,此時可以把這些Pod
對象間的關系稱為反親和性(anti-affinity)
Kubernetes
調度器通過內建的MatchInterPodAffinity
預選策略為這種調度方式完成節點預選,並給予InterPodAffinityPriority
優選函數進行各節點的優選級評估。
位置拓撲
Pod
親和性調度需要各相關的Pod
對象運行於"同一位置",而反親和性調度則需要它們不能運行於"同一位置"。何謂同一位置?事實上,取決於節點的位置拓撲,拓撲的方式不同,如下圖"Pod資源與位置拓撲"中的Pod-A
和Pod-B
是否在同一位置的判定結果也可能有所不同
如果以基於各節點的kubernetes.io/hostname
標簽作為評判標准,那么"同一位置"意味着同一節點,不同節點即不同的位置,如"基於節點的位置拓撲"圖所示
如果是基於"故障轉移域"來進行評判,那么server1
和server4
屬於同一位置,而server2
和server3
屬於另一個意義上的同一位置。
故此,在定義Pod
對象的親和性與反親和性時,需要借助於標簽選擇器來選擇被依賴的Pod
對象 ,並根據選出的Pod
對象所在的節點的標簽來判定"同一位置"的具體意義。
Pod 硬親和調度
Pod硬親和性調度也是用
requiredDuringSchedulingIgnoredDuringExecution
屬性進行定義。Pod
親和性用於描述一個Pod
對象與具有某特征的現存Pod
對象運行位置的依賴關系,因此,測試使用Pod
親和性約束,需要事先存在被依賴的Pod
對象,具有特別的識別標簽。
📝 示例1
下面創建一個有着標簽app=tomcat
的deployment資源部署一個Pod
對象:
# kubectl run tomcat -l app=tomcat --image tomcat:alpine
下面資源配置清單中定義了一個Pod
對象,通過labelsSelector
定義的標簽選擇器挑選感興趣的現存Pod
對象,然后根據挑選出的Pod
對象所在節點的標簽kubenetes.io/hostname
來判斷同一位置的具體含義,並將當前Pod
對象調度至這一位置的某節點之上:
# vim required-podAffinity-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-pod-paffinity-1
namespace: default
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["tomcat"]}
topologyKey: kubernetes.io/hostname
事實上,kubernetes.io/hostname
標簽是kubernetes
集群節點的內建標簽,它的值為當前節點的節點主機名稱標識,各個節點均不相同。因此,新建的Pod
對象將部署至被依賴的Pod
對象的同一節點之上。requiredDuringSchedulingIgnoredDuringExecution
表示這種親和性為強制約束。
# kubectl apply -f required-podAffinity-pod1.yaml
pod/with-pod-paffinity-1 created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
tomcat-6c999f4c7d-x5x95 1/1 Running 0 12m 10.244.1.41 k8s-node1 <none> <none>
with-pod-paffinity-1 1/1 Running 0 25s 10.244.2.42 k8s-node2 <none> <none>
基於單一節點的Pod
親和性只在極個別的情況下才可能會用到,較為常用的是基於同一地區、區域或幾家的拓撲位置約束。例如部署應用程序myapp
與數據庫db
服務相關的Pod
時,db pod
可能會部署於如下圖"pod硬親和性"所示的foo
或bar
這兩個區域中的某個節點之上,依賴與數據服務的myapp pod
對象可部署於 db pod
所在區域的節點上。如果db pod
在兩個區域foo
和bar
中各有副本運行,那么myapp
將可以運行於這兩個區域的任何節點之上。
📝 示例2
創建具有兩個擁有標簽為"app=db"的副本Pod
作為被依賴的資源,這里隨機分配給了兩個節點。
# kubectl run db -l app=db --image=redis:alpine --replicas=2
# kubectl get pods -l app=db -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-86f4768855-cdtq7 1/1 Running 0 32s 10.244.1.43 k8s-node1 <none> <none>
db-86f4768855-pftfv 1/1 Running 0 32s 10.244.2.32 k8s-node2 <none> <none>
於是,依賴於親和於這兩個Pod
的其他pod
對象可運行於zone
標簽值為foo
和bar
的區域內的所有節點之上。下面進行定義一個Deployment
控制器管理的Pod
資源示例
# cat deploy-with-required-podAffinity-pod2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
在調度器示例中的Deployment控制器創建的Pod
資源時,調度器首先會基於標簽選擇器查詢擁有標簽"app=db"的所有Pod
資源,接着獲取它們分別所屬的節點的zone
標簽值,接下來再查詢擁有匹配這些標簽值的所有節點,從而完成節點預選。根據優選函數計算這些節點的優先級,從而挑選出運行新建Pod
對象的節點。
# kubectl apply -f deploy-with-required-podAffinity-pod2.yaml
deployment.apps/myapp-with-pod-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-86f4768855-cdtq7 1/1 Running 0 132m 10.244.1.43 k8s-node1 <none> <none>
db-86f4768855-pftfv 1/1 Running 0 132m 10.244.2.32 k8s-node2 <none> <none>
myapp-with-pod-affinity-c775766d5-86c48 1/1 Running 0 5s 10.244.1.44 k8s-node1 <none> <none>
myapp-with-pod-affinity-c775766d5-gdfxp 1/1 Running 0 5s 10.244.2.39 k8s-node2 <none> <none>
myapp-with-pod-affinity-c775766d5-ptcfd 1/1 Running 0 5s 10.244.2.40 k8s-node2 <none> <none>
❗ 注意:如果節點上的標簽在運行時發生了變更,以致不再滿足Pod
上的親和性規則,但該Pod
還將繼續在該節點上運行,因此它僅會影響新建的Pod
資源;另外、labelSelector
屬性僅匹配與被調度器的Pod
在同一名稱空間中的Pod
資源,不過也可以通過為其天劍namespace
字段以指定其他名稱空間。
Pod 軟親和調度
類似於節點親和性機制,
Pod
也支持使用preferredDuringSchedulingIgnoredDuringExecution
屬性定義柔性親和性機制,調度器會盡力確保滿足親和性約束的調度邏輯,然而在約束條件不能得到滿足時,它也允許將Pod
對象調度至其他節點運行。
📝 示例
# vim deploy-with-perferred-podAffinity-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-preferred-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["cache"]}
topologyKey: zone
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
上面資源配置清單中定義了兩組親和性判定機制,一個是選出cache Pod
所在節點的zone
標簽,並賦予了較高的權重80,另一個是選擇db Pod
所在節點的zone
標簽,有着略低的權重20。於是,調度器會將目標節點分為四類:cache Pod
和db Pod
同時所屬的zone
、cache Pod
單獨所屬的zone
、db Pod
單獨所屬的zone
,以及其他所有的zone
。
# kubectl apply -f deploy-with-perferred-podAffinity-pod.yaml
deployment.apps/myapp-with-preferred-pod-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-86f4768855-cdtq7 1/1 Running 0 160m 10.244.1.43 k8s-node1 <none> <none>
db-86f4768855-pftfv 1/1 Running 0 160m 10.244.2.32 k8s-node2 <none> <none>
myapp-with-preferred-pod-affinity-79b96db78b-24mnl 1/1 Running 0 7s 10.244.1.45 k8s-node1 <none> <none>
myapp-with-preferred-pod-affinity-79b96db78b-l9rt9 1/1 Running 0 7s 10.244.2.41 k8s-node2 <none> <none>
myapp-with-preferred-pod-affinity-79b96db78b-vwfbw 1/1 Running 0 7s 10.244.2.42 k8s-node2 <none>
Pod 反親和調度
podAffinity
用於定義Pod
對象的親和約束,對應地,將其替換為podAntiAffinity
即可用於定義Pod
對象的反親和約束。反親和性調度一般用於分散同一類應用的Pod
對象等,也包括將不同安全級別的Pod
對象調度至不同的區域、機架或節點等。
📝 示例
這里將定義由同一Deployment
創建但彼此基於節點位置互斥的Pod
對象:
# vim deploy-with-required-podAntiAffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-anti-affinity
namespace: default
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["myapp"]}
topologyKey: kubernetes.io/hostname
由於定義的強制性反親和約束,因此,創建的4個Pod
副本必須運行於不同的節點中,不過,由於這里實驗只有兩台工作節點,因此,必然會有兩個Pod
對象處於Pending
狀態。
# kubectl apply -f deploy-with-required-podAntiAffinity-demo.yaml
deployment.apps/myapp-with-pod-anti-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-with-pod-anti-affinity-5d7ddf7766-8rql7 0/1 Pending 0 5s <none> <none> <none> <none>
myapp-with-pod-anti-affinity-5d7ddf7766-b85c6 1/1 Running 0 5s 10.244.2.25 k8s-node2 <none> <none>
myapp-with-pod-anti-affinity-5d7ddf7766-t25xt 0/1 Pending 0 5s <none> <none> <none> <none>
myapp-with-pod-anti-affinity-5d7ddf7766-tbb94 1/1 Running 0 5s 10.244.1.15 k8s-node1 <none> <none>
Pod
反親和性調度也支持使用柔性約束機制,在調度時,將盡量滿足不把位置相斥的Pod
對象調度於同一位置,但是,當約束條件無法得到滿足時,也可以違反約束而調度。
污點和容忍度
污點(taints)是定義節點之上的鍵值型屬性數據,用於讓節點拒絕將
Pod
調度運行於其上,除非該Pod
對象具有接納節點污點的容忍度。而容忍度(tolerations)是定義在Pod
對象上的鍵值型屬性數據,用於配置其可容忍的節點污點,而且調度器僅能將Pod
對象調度至其能夠容忍該節點污點的節點之上,如下圖所示:
上面的節點選擇器(nodeSelector)和節點親和性(nodeAffinity)兩種調度方式都是通過在
Pod
對象上添加標簽選擇器來完成對特定類型節點標簽的匹配,它們實現的是由Pod
選擇節點的機制。而污點和容忍度則是通過向節點添加污點信息來控制Pod
對象的調度結果,從而賦予了節點控制何種Pod
對象能夠調度於其上的主控權。簡單來說,節點親和性使得Pod
對象被吸引到一類特定的節點,而污點則相反,提供了讓節點排斥特定Pod
對象的能量。
Kubernetes
使用PodToleratesNodeTaints
預選策略和TaintTolerationPriority
優選函數來完成此種類型的高級調度機制。
定義污點和容忍度
污點定義在節點的nodeSpec
中,容忍度定義在Pod
的podSpec
中,都是鍵值型數據,都額外支持(effect)標記,語法格式為"key=value:effect",其中key
和value
的用法及格式與資源注解信息相似,而effect
則用於定義對Pod
對象的排斥等級,主要包含以下三種類型:
- NoSchedule:不能容忍此污點的新
Pod
對象不可調度至當前節點,屬於強制型約束關系,節點上現存的Pod
對象不受影響。 - PreferNoSchedule:
NoSchedule
的柔性約束版本,即不能容忍此污點的新Pod
對象盡量不要調度至當前節點,不過無其它節點可供調度時也允許接受相應的Pod
對象。節點上現存的Pod
對象不受影響。 - NoExecute:不能容忍此污點的新
Pod
對象不可調度至當前節點,屬於強制型約束關系,而且節點上現存的Pod
對象因節點污點變動或Pod
容忍度變動而不再滿足匹配規則時,Pod
對象將被驅逐。
在Pod
對象上定義容忍度時,支持兩種操作符:一種是等值比較(Equal),表示容忍度與污點必須在key
、value
和effect
三者之上完全匹配;另一種是存在性判斷(Exists),表示二者的key
和effect
必須完全匹配,而容忍度中的value
字段要使用空值。
❗ 注意:一個節點可以配置使用多個污點,一個Pod
對象也可以有多個容忍度,不過二者在進行匹配檢查時遵循如下邏輯:
-
首先處理每個有着與之匹配的容忍度的污點。
-
不能匹配到的污點上,如果存在了一個使用
NoSchedule
效用標識,則拒絕調度Pod
對象至此節點。 -
不能匹配到的污點上,若沒有任何一個使用了
NoSchedule
效用標識,但至少有一個使用了PreferNoScheduler
,則應盡量避免將Pod
對象調度至此節點。 -
如果至少有一個不匹配的污點使用了
NoExecute
效用標識,則節點將立即驅逐Pod
對象,或者不予調度至給定節點;另外,即便容忍度可以匹配到使用了NoExecute
效用標識的污點,若在定義容忍度時還同時使用tolerationSeconds
屬性定義了容忍時限,則超出時限后期也將被節點驅逐。
使用kubeadm
部署的Kubernetes
集群,其Master
節點將自動添加污點信息以阻止不能容忍此污點的Pod
對象調度至此節點,因此,用戶手動創建的未特意添加容忍此污點容忍度的Pod
對象將不會被調度至此節點:
# kubectl describe node k8s-master
Name: k8s-master
Roles: master
......
Taints: node-role.kubernetes.io/master:NoSchedule
不過,有些系統級應用,如kube-proxy
或者kube-flannel
等,都在資源創建時就添加上了相應的容忍度以確保它們被DaemonSet
控制器創建時能夠調度至Master
節點運行一個實例:
# kubectl describe pods kube-flannel-ds-amd64-bg7gc -n kube-system
......
Node-Selectors: <none>
Tolerations: :NoSchedule
node.kubernetes.io/disk-pressure:NoSchedule
node.kubernetes.io/memory-pressure:NoSchedule
node.kubernetes.io/network-unavailable:NoSchedule
node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/pid-pressure:NoSchedule
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unschedulable:NoSchedule
另外,這類Pod
是構成Kubernetes
系統的基礎且關鍵性的組件,它們甚至還定義了更大的容忍度。通過上面的kube-flannel
實例的容忍度定義來看,它還能容忍那些報告了磁盤壓力或內存壓力的節點,以及未就緒的節點和不可達的節點,以確保它們能在任何狀態下正常調度至集群節點上運行。
管理節點的污點
任何符合其鍵值規范要求的字符串均可用於定義污點信息:僅可使用字母、數字、連接符、點號和下划線,且僅能以字母或數字開頭,其中鍵名長度上限為253個字符,值最長為63個字符。可以用污點用來描述具體的部署規划,鍵名比如node-type、node-role、node-project或node-geo等,還可以在必要時戴上域名以描述其額外的信息,如node-type.ilinux.io等。
📌 添加污點
kubectl taint
命令即可向節點添加污點,命令格式如下:
kubectl taint nodes <node-name> <key>=<value>:<effect> ...
例如,使用"node-type=production:NoSchedule"定義節點k8s-node1
# kubectl taint nodes k8s-node1 node-type=production:NoSchedule
node/k8s-node1 tainted
此時,k8s-node1
上已有的Pod
對象不受影響,但新建的Pod
若不能容忍此污點將不能再被調度至此節點。通過下面的命令可以查看節點的污點信息:
# kubectl get nodes k8s-node1 -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node-type value:production]]
需要注意的是,即便是同一個鍵值數據,若其效用標識不同,則其也分屬於不同的污點信息,例如,將上面命令中的效用標識定義為PreferNoSchedule
再添加一次:
# kubectl taint nodes k8s-node1 node-type=production:PreferNoSchedule
node/k8s-node1 tainted
📌 刪除污點
刪除污點,仍通過kubectl taint
命令進行,但是要使用如下的命令格式,省略效用標識則表示刪除使用指定鍵名的所有污點,否則就只是刪除指定健名上對應效用標識的污點:
kubectl taint nodes <node-name> <key>[:<effect>]-
例如,刪除k8s-node1
上node-type
鍵的效用標識為NoSchedule
的污點信息:
# kubectl taint nodes k8s-node1 node-type:NoSchedule-
node/k8s-node1 untainted
若要刪除使用指定健名的所有污點,則在刪除命令中省略效用標識即能實現,例如:
# kubectl taint nodes k8s-node1 node-type-
node/k8s-node1 untainted
刪除節點上的全部污點信息,通過kubectp patch
命令將節點屬性spec.taints
的值直接置空,例如:
# kubectl patch nodes k8s-node1 -p '{"spce":{"taints":[]}}'
節點污點的變動會影響到新建Pod
對象的調度結果,而且使用NoExecute
進行標識時,還會影響到節點上現有的Pod
對象。
Pod 對象的容忍度
Pod
對象的容忍度可通過其spec.tolerations
字段進行添加,根據使用的操作符不同,主要有兩種可用的形式:一種是與污點信息完全匹配的等值關系;另一種是判斷污點信息存在性的匹配方式。使用Equal
操作符的示例如下所示,其中tolerationSeconds
用於定義延遲驅逐當前Pod
對象的時長:
📝 示例
首先給兩個工作節點添加不同的污點
# kubectl taint node k8s-node1 node-type=production:NoSchedule
# kubectl taint node k8s-node2 node-type=dev:NoExecute
編寫示例yaml文件
# vim deploy-taint-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-taint-demo-pod
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
ports:
- name:
containerPort: 80
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoExecute"
tolerationSeconds: 30
創建資源,可以發現目前pod一直處於Pending
狀態,這是因為兩個節點都不能容忍。
# kubectl apply -f deploy-taint-demo.yaml
deployment.apps/deploy-taint-demo-pod created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-taint-demo-pod-7459f65d64-426vg 0/1 Pending 0 4s <none> <none> <none> <none>
deploy-taint-demo-pod-7459f65d64-h6fbp 0/1 Pending 0 4s <none> <none> <none> <none>
deploy-taint-demo-pod-7459f65d64-k4xjw 0/1 Pending 0 4s <none> <none> <none> <none>
修改資源配置清單,將容忍度改為NoSchedule
, 這個示例中表示容忍key為node-type
值為production
的污點
# vim deploy-taint-demo.yaml
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoSchedule"
重新創建,可以發現已經調度到k8s-node1
節點上
# kubectl apply -f deploy-taint-demo.yaml
deployment.apps/deploy-taint-demo-pod configured
[root@k8s-master1 schedule]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-taint-demo-pod-87cffffdd-9tm29 1/1 Running 0 3s 10.244.1.24 k8s-node1 <none> <none>
deploy-taint-demo-pod-87cffffdd-qt8wf 1/1 Running 0 2s 10.244.1.25 k8s-node1 <none> <none>
deploy-taint-demo-pod-87cffffdd-zgmrl 1/1 Running 0 4s 10.244.1.23 k8s-node1 <none> <none>
還可以通過Exists
進行判斷,修改資源配置清單如下,下面這段表示,只要有污點key為node-type
,不論值為什么且是什么類型的污點,都能容忍
# vim deploy-taint-demo.yaml
tolerations:
- key: "node-type"
operator: "Exists"
value: ""
effect: ""
查看結果可以看出,兩個節點都已經進行調度了。
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-taint-demo-pod-7c87f4957f-4njh7 1/1 Running 0 16s 10.244.2.27 k8s-node2 <none> <none>
deploy-taint-demo-pod-7c87f4957f-mj46d 1/1 Running 0 14s 10.244.1.26 k8s-node1 <none> <none>
deploy-taint-demo-pod-7c87f4957f-vp9t2 1/1 Running 0 13s 10.244.2.28 k8s-node2 <none> <none>
問題節點標識
Kubernetes
從1.6
版本起支持使用污點自動標識問題節點,通過節點控制器在特定條件下自動為節點添加污點信息實現。它們都是用NoExecute
效用標識,因此不能容忍此類污點的現有Pod
對象也會遭到驅逐。目前,內建使用的此類污點包含如下幾個:
✏️ node.kubernetes.io/not-ready:節點進入“NotReady”狀態時被自動添加的污點。
✏️ node.alpha.kubernetes.io/unreachable:節點進入“NotReachable”狀態時被自動添加的污點。
✏️ node.kubernetes.io/out-of-disk:節點進入“OutOfDisk”狀態時被自動添加的污點。
✏️ node.kubernetes.io/memory-pressure:節點內存資源面臨壓力。
✏️ node.kubernetes.io/disk-pressure:節點磁盤資源面臨壓力。
✏️ node.kubernetes.io/network-unavailable:節點網絡不可用。
✏️ node.cloudprovider.kubernetes.io/uninitialized:kubelet
由外部的雲環境程序啟動時,它將自動為節點添加此污點,待到雲控制器管理器中的控制器初始化此節點時再將其刪除。