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進階實戰》