Kubernetes的默認調度器以預選、優選、選定機制完成將每個新的Pod資源綁定至為其選出的目標節點上,不過,它只是Pod對象的默認調度器,默認情況下調度器考慮的是資源足夠,並且負載盡量平均。
在使用中,用戶還可以自定義調度器插件,並在定義Pod資源配置清單時通過spec.schedulerName指定即可使用,這就是親和性調度。
1、Node親和性調度
NodeAffinity意為Node節點親和性的調度策略,是用於替換NodeSelector的全新調度策略。
這些規則基於節點上的自定義標簽和Pod對象上指定的標簽選擇器進行定義 。 節點親和性允許Pod對象定義針對一組可以調度於其上的節點的親和性或反親和性,不過,它無法具體到某個特定的節點 。
例如,將Pod調度至有着特殊CPU的節點或一個可用區域內的節點之上 。
定義節點親和性規則時有兩種類型的節點親和性規則 :硬親和性 required和軟親和性preferred。 硬親和性實現的是強制性規則,它是Pod調度時必須要滿足的規則,而在不存在滿足規則的節點時 , Pod對象會被置為Pending狀態。 而軟親和性規則實現的是一種柔性調度限制,它傾向於將Pod對象運行於某類特定的節點之上,而調度器也將盡量滿足此需求,但在無法滿足調度需求時它將退而求其次地選擇一個不匹配規則的節點。
定義節點親和規則的關鍵點有兩個,一是為節點配置合乎需求的標簽,另一個是為Pod對象定義合理的標簽選擇器,從而能夠基於標簽選擇出符合期望的目標節點。不過,如preferredDuringSchedulinglgnoredDuringExecution和requiredDuringSchedulinglgnoredDuringExecution名字中的后半段符串lgnoredDuringExecution隱含的意義所指,在Pod資源基於節點親和性規則調度至某節點之后,節點標簽發生了改變而不再符合此節點親和性規則時 ,調度器不會將Pod對象從此節點上移出,因為,它僅對新建的Pod對象生效。 節點親和性模型如圖所示:

1.1、Node硬親和性
為Pod對象使用nodeSelector屬性可以基於節點標簽匹配的方式將Pod對象強制調度至某一類特定的節點之上 ,不過它僅能基於簡單的等值關系定義標簽選擇器,而nodeAffinity中支持使用 matchExpressions屬性構建更為復雜的標簽選擇機制。例如,下面的配置清單示例中定義的Pod對象,其使用節點硬親和規則定義可將當前Pod對象調度至擁有zone標簽且其值為foo的節點之上
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
containers:
- name: nginx
image: nginx
將上面配置清單中定義的資源創建於集群之中,由其狀態信息可知它處於Pending階段,這是由於強制型的節點親和限制場景中不存在能夠滿足匹配條件的節點所致:
# kubectl apply -f required-nodeAffinity-pod.yaml
pod/with-required-nodeaffinity created
# kubectl get pods with-required-nodeaffinity
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity 0/1 Pending 0 8s
通過describe查看對應的events
# 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.
規划為各節點設置節點標簽 ,這也是設置節點親和性的前提之一
# kubectl label node k8s-node-01 zone=foo
node/k8s-node-01 labeled
# kubectl label node k8s-node-02 zone=foo
node/k8s-node-02 labeled
# kubectl label node k8s-node-03 zone=bar
node/k8s-node-03 labeled
查看調度結果
# 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.
Normal Scheduled <unknown> default-scheduler Successfully assigned default/with-required-nodeaffinity to k8s-node-01
在定義節點親和性時,requiredDuringSchedulinglgnoredDuringExecution字段的值是一個對象列表,用於定義節點硬親和性,它可由一到多個nodeSelectorTerm定義的對象組成, 彼此間為“邏輯或”的關系,進行匹配度檢查時,在多個nodeSelectorTerm之間只要滿足其中之一 即可。nodeSelectorTerm用於定義節點選擇器條目,其值為對象列表,它可由一個或多個matchExpressions對象定義的匹配規則組成,多個規則彼此之間為“邏輯與”的關系, 這就意味着某節點的標簽需要完全匹配同一個nodeSelectorTerm下所有的matchExpression對象定義的規則才算成功通過節點選擇器條目的檢查。而matchExmpressions又可由 一到多 個標簽選擇器組成,多個標簽選擇器彼此間為“邏輯與”的關系 。
下面的資源配置清單示例中定義了調度擁有兩個標簽選擇器的節點挑選條目,兩個標簽選擇器彼此之間為“邏輯與”的關系,因此,滿足其條件的節點為node01和node03
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-2
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo", "bar"]}
- {key: ssd, operator: Exists, values: []}
containers:
- name: nginx
image: nginx
構建標簽選擇器表達式中支持使用操作符有In、Notln、 Exists、DoesNotExist、Lt和Gt等
- In:
label的值在某個列表中 - NotIn:
label的值不在某個列表中 - Gt:
label的值大於某個值 - Lt:
label的值小於某個值 - Exists:某個
label存在 - DoesNotExist:某個
label不存在
另外,調度器在調度Pod資源時,節點親和性 MatchNodeSelector僅是其節點預選策 略中遵循的預選機制之一,其他配置使用的預選策略依然正常參與節點預選過程。 例如將上面資源配置清單示例中定義的Pod對象容器修改為如下內容並進行測試
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-3
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo", "bar"]}
- {key: ssd, operator: Exists, values: []}
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: 6
memory: 20Gi
在預選策略PodFitsResources根據節點資源可用性進行節點預選的過程中,它會獲取給定節點的可分配資源量(資源問題減去已被運行於其上的各Pod對象的requests屬性之和),去除那些無法容納新Pod對象請求的資源量的節點,如果資源不夠,同樣會調度失敗。
由上述操作過程可知,節點硬親和性實現的功能與節點選擇器nodeSelector相似, 但親和性支持使用匹配表達式來挑選節點,這一點提供了靈活且強大的選擇機制,因此可被理解為新一代的節點選擇器。
1.2、Node軟親和性
節點軟親和性為節點選擇機制提供了一種柔性控制邏輯,被調度的Pod對象不再是“必須”而是“應該”放置於某些特定節點之上,當條件不滿足時它也能夠接受被編排於其他不符合條件的節點之上。另外,它還為每種傾向性提供了weight屬性以便用戶定義其優先級,取值范圍是1 ~ 100,數字越大優先級越高 。
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-with-node-affinity
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
containers:
- name: nginx
image: nginx
Pod資源模板定義了節點軟親和性以選擇運行在擁有zone=foo和ssd標簽(無論其值為何)的節點之上, 其中zone=foo是更為重要的傾向性規則, 它的權重為60,相比較來說,ssd標簽就沒有那么關鍵, 它的權重為30。 這么一來, 如果集群中擁有足夠多的節點,那么它將被此規則分為四類 : 同時滿足擁有zone=foo和ssd標簽、僅具有zoo=foo標 簽、 僅具有ssd標簽, 以及不具備此兩個標簽, 如圖所示

示例環境共有三個節點,相對於定義的節點親和性規則來說,它們所擁有的傾向性權重分別如圖所示。在創建需要3個Pod對象的副本時,其運行效果為三個Pod對象被分散運行於集群中的三個節點之上,而非集中運行於某一個節點 。
之所以如此,是因為使用了節點軟親和性的預選方式,所有節點均能夠通過調度器上MatchNodeSelector預選策略的篩選,因此,可用節點取決於其他預選策略的篩選結果。在第二階段的優選過程中,除了NodeAffinityPriority優選函數之外,還有其他幾個優選函數參與優先級評估,尤其是SelectorSpreadPriority,它會將同一個ReplicaSet控制器管控的所有Pod對象分散到不同的節點上運行以抵御節點故障帶來的風險 。不過,這種節點親和性的權重依然在發揮作用,如果把副本數量擴展至越過節點數很多,如15個, 那么它們將被調度器以接近節點親和性權重比值90:60:30 的方式分置於相關的節點之上。
2、Pod親和性調度
2.1、位置拓撲
Pod親和性調度需要各相關的Pod對象運行於“同一位置”, 而反親和性調度則要求它們不能運行於“同一位置” 。同一位置取決於節點的位置拓撲, 拓撲的方式不同。
如果以基於各節點的kubernetes.io/hostname標簽作為評判標准,那么很顯然,“同一位置” 意味着同一個節點,不同節點即不同的位置, 如圖所示

如果是基於所划分的故障轉移域來進行評判,同一位置, 而server2和server3屬於另一個意義上的同一位置

因此,在定義Pod對象的親和性與反親和性時,需要借助於標簽選擇器來選擇被依賴的Pod對象,並根據選出的Pod對象所在節點的標簽來判定“同一位置”的具體意義。
2.2、Pod硬親和
Pod強制約束的親和性調度也使用requiredDuringSchedulinglgnoredDuringExecution屬性進行定義。Pod親和性用於描述一個Pod對象與具有某特征的現存Pod對象運行位置的依賴關系,因此,測試使用Pod親和性約束,需要事先存在被依賴的Pod對象,它們具有特別的識別標簽。例如創建一個有着標簽app=tomcat的Deployment資源部署一個Pod對象:
kubectl run tomcat -l app=tomcat --image tomcat:alpine
再通過資源清單定義一個Pod對象,它通過labelSelector定義的標簽選擇器挑選感興趣的現存Pod對象, 而后根據挑選出的Pod對象所在節點的標簽kubernetes. io/hostname來判斷同一位置的具體含義,並將當前Pod對象調度至這一位置的某節點之上:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity-1
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["tomcat"]}
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
事實上,kubernetes.io/hostname標簽是Kubernetes集群節點的內建標簽,它的值為當前節點的節點主機名稱標識,對於各個節點來說,各有不同。因此,新建的Pod 象將被部署至被依賴的Pod對象的同一節點上,requiredDuringSchedulingIgnoredDuringExecution表示這種親和性為強制約束。
基於單一節點的Pod親和性只在極個別的情況下才有可能會用到,較為常用的通常是基於同一地區 region、區域zone或機架rack的拓撲位置約束。例如部署應用程序服務myapp與數據庫db服務相關的Pod時,db Pod可能會部署於如上圖所示的foo或bar這兩個區域中的某節點之上,依賴於數據服務的myapp Pod對象可部署於db Pod所在區域內的節點上。當然,如果db Pod在兩個區域foo和bar中各有副本運行,那么myapp Pod將可以運行於這兩個區域的任何節點之上。
依賴於親和於這兩個Pod的其他Pod對象可運行於zone標簽值為foo和bar的區域內的所有節點之上。資源配置清單如下
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:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: nginx
image: nginx
在調度示例中的Deployment控制器創建的Pod資源時,調度器首先會基於標簽選擇器 查詢擁有標簽app=db的所有Pod資源,接着獲取到它們分別所屬 的節點的zone標簽值,接下來再查詢擁有匹配這些標簽值的所有節點,從而完成節點預選。而后根據優選函數計算這些節點的優先級,從而挑選出運行新建Pod對象的節點。
需要注意的是,如果節點上的標簽在運行時發生了更改,以致它不再滿足Pod上的親和性規則,但該Pod還將繼續在該節點上運行,因此它僅會影響新建的Pod資源;另外,labelSelector屬性僅匹配與被調度器的Pod在同一名稱空間中的Pod資源,不過也可以通過為其添加 namespace字段以指定其他名稱空間 。
2.3、Pod軟親和
類似於節點親和性機制,Pod也支持使用preferredDuringSchedulinglgnoredDuringExecution屬性定義柔性親和機制,調度器會盡力確保滿足親和約束的調度邏輯,然而在約束條 件不能得到滿足時,它也允許將Pod對象調度至其他節點運行。下面是一個使用了Pod軟親和性調度機制的資源配置清單示例
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:
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
containers:
- name: nginx
image: nginx
它定義了兩組親和性判定機制,一個是選擇cache Pod所在節點的zone標簽,並賦予了較高的權重80,另一個是選擇db Pod所在節點的 zone標簽,它有着略低的權重20。於是,調度器會將目標節點分為四類 :cache Pod和db Pod同時所屬的zone、cache Pod單獨所屬的zone、db Pod單獨所屬的zone,以及其他所有的zone。
2.4、Pod反親和
podAffinity用於定義Pod對象的親和約束,對應地,將其替換為podAntiAffinty即可用於定義Pod對象的反親和約束。不過,反親和性調度一般用於分散同一類應用的Pod對象等,也包括將不同安全級別的Pod對象調度至不同的區域、機架或節點等。下面的資源配置清單中定義了由同一Deployment創建但彼此基於節點位置互斥的Pod對象:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-anti-affinity
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["myapp"]}
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx
由於定義的強制性反親和約束,因此,創建的4個Pod副本必須運行於不同的節點中。不過,如果集群中一共只存在3個節點,因此,必然地會有一個Pod對象處於Pending狀態。
類似地,Pod反親和性調度也支持使用柔性約束機制,在調度時,它將盡量滿足不把位置相斥的Pod對象調度於同一位置,但是,當約束關系無法得到滿足時,也可以違反約束而調度。可參考podAffinity的柔性約束示例將上面的Deployment資源myapp-with-pod-anti-affinity修改為柔性約束並進行調度測試。
文章參考來源:《kubernetes進階實戰》
