Kubernets 親和性
Kubernetes
的預設排程器以預選、優選、選定機制完成將每個新的Pod
資源繫結至為其選出的目標節點上,不過,它只是Pod
物件的預設排程器,預設情況下排程器考慮的是資源足夠,並且負載儘量平均。
在使用中,使用者還可以自定義排程器外掛,並在定義Pod
資源配置清單時通過spec.schedulerName
指定即可使用,這就是親和性排程。
一、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: 192.168.99.181:5000/wod/nginx:1.15.1
將上面配置清單中定義的資源創建於叢集之中,由其狀態資訊可知它處於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: 192.168.99.181:5000/wod/nginx:1.15.1
構建標籤選擇器表示式中支援使用操作符有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: 192.168.99.181:5000/wod/nginx:1.15.1
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: 192.168.99.181:5000/wod/nginx:1.15.1
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
的方式分置於相關的節點之上。
二、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: 192.168.99.181:5000/wod/nginx:1.15.1
事實上,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: 192.168.99.181:5000/wod/nginx:1.15.1
在排程示例中的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: 192.168.99.181:5000/wod/nginx:1.15.1
它定義了兩組親和性判定機制,一個是選擇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: 192.168.99.181:5000/wod/nginx:1.15.1
由於定義的強制性反親和約束,因此,建立的4
個Pod
副本必須運行於不同的節點中。不過,如果叢集中一共只存在3
個節點,因此,必然地會有一個Pod
物件處於Pending
狀態。
類似地,Pod
反親和性排程也支援使用柔性約束機制,在排程時,它將盡量滿足不把位置相斥的Pod
物件排程於同一位置,但是,當約束關係無法得到滿足時,也可以違反約束而排程。可參考podAffinity
的柔性約束示例將上面的Deployment
資源myapp-with-pod-anti-affinity
修改為柔性約束並進行排程測試。