節點污點可以用來讓pod遠離特定的節點,盡量在不修改已有pod信息的前提,通過在節點添加污點信息,來拒絕pod在某些節點上的部署。
而現在介紹一種叫做節點親緣性,通過明確的在pod中添加的信息,來決定一個pod可以或者不可以被調度到哪些節點上。
對比節點親緣性和節點選擇器
在早期版本的Kubernetes中,初始的節點親緣性機制,就是pod描述中的nodeSelector字段。節點必須包含所有pod對應字段中的指定label,才能成為pod調度的目標節點。
節點選擇器實現簡單,但是它不能滿足你的所有需求。正因為如此,一種更強大的機制被引入。節點選擇器最終會被棄用,所以現在了解新的節點親緣性機制就變得重要起來。
與節點選擇器類似,每個pod可以定義自己的節點親緣性規則。這些規則可以允許指定硬性限制或者偏好。如果指定一種偏好的話,你將告知Kubernetes對於某個特定的pod,它更傾向於調度到某些節點上,之后Kubernetes將盡量把這個pod調度到這些節點上面。如果沒法實現的話,pod將被調度到其他某個節點上。
檢查默認的節點標簽
節點親緣性根據節點的標簽來進行選擇,這點跟節點選擇器是一致的。現在檢查一個Google Kubernetes引擎集群(GKE)中節點的標簽,來看一下它們默認的標簽是什么,如以下代碼所示。
#代碼16.7 GKE節點的默認標簽 $ kubectl describe node gke-kubia-default-pool-db274c5a-mjnf Name: gke-kubia-default-pool-db274c5a-mjnf Role: Labels: beta.kubernetes.io/arch=amd64 beta.kubernetes.io/fluentd-ds-ready=true beta.kubernetes.io/instance-type=f1-micro beta.kubernetes.io/os=linux cloud.google.com/gke-nodepool=default-pool failure-domain.beta.kubernetes.io/region=europe-west1 #最后這三個標簽對於節點親緣性來說最為重要 failure-domain.beta.kubernetes.io/zone=europe-west1-d kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-mjnf
這個節點有很多標簽,但涉及節點親緣性和pod親緣性時,最后三個標簽是最重要的。這三個標簽的含義如下:
-
- failure-domain.beta.kubernetes.io/region表示該節點所在的地理地域。
- failure-domain.beta.kubernetes.io/zone表示該節點所在的可用性區域(availability zone)。
- kubernetes.io/hostname很顯然是該節點的主機名。
這三個以及其他標簽,將被用於pod親緣性規則。在第三章中,你己經學會 如何給一個節點添加自定義標簽,並且在pod的節點選擇器中使用它。可以通過給 pod加上節點選擇器的方式,將節點部署到含有這個自定義標簽的節點上。現在, 你將學習到怎么用節點親緣性規則實現同樣的功能。
1.指定強制性節點親緣性規則
有這么一個例子,使用了節點選擇器使得需要GPU的pod只被調度到有GPU的節點上。包含了nodeSelector字段的pod描述如以下代碼所示。
#代碼16.8 使用了節點選擇器的pod : kubia-gpu-nodeselector.yaml apiVersion: v1 kind: Pod metadata: name: kubia-gpu spec: nodeSelector: gpu: "true" containers: - image: luksa/kubia #這個pod會被調度到包含了gpu=true標簽的節點上 name: kubia
nodeSelector字段表示,pod只能被部署在包含了gpu=true標簽的節點上。 如果將節點選擇器替換為節點親緣性規則,pod定義將會如以下代碼清單所示。
#代碼16.9 使用了節點親緣性規則的pod : kubia-gpu-nodeaffinity.yaml apiVersion: v1 kind: Pod metadata: name: kubia-gpu spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: gpu operator: In values: - "true" containers: - image: luksa/kubia name: kubia
首先會注意到的是,這種寫法比簡單的節點選擇器要復雜得多,但這是因為它的表達能力更強,再詳細看一下這個規則。
較長的節點親緣性屬性名的意義
正如你所看到的,這個pod的描述包含了affinity字段,該字段又包含了nodeAffinity字段,這個字段有一個極其長的名字,所以,先重點關注這個。
把這個名字分成兩部分,然后分別看下它們的含義:
-
- requiredDuringScheduling...表明了該字段下定義的規則,為了讓pod能調度到該節點上,明確指出了該節點必須包含的標簽。
- ...IgnoredDuringExecution表明了該字段下定義的規則,不會影響己經在節點上運行着的pod。
目前,當你知道當前的親緣性規則只會影響正在被調度的pod,並且不會導致己經在運行的pod被剔除時,情況可能會更簡單一些。這就是為什么目前的規則都是以IgnoredDuringExecution結尾的。最終,Kubernetes也會支持RequiredDuringExecution,表示如果去除掉節點上的某個標簽,那些需要節點包含該標簽的pod將會被剔除,但是,Kubernetes目前還不支持次特性。所以,可以暫時不去關心這個長字段的第二部分。
了解節點選擇器條件
記住上一節所解釋的內容,將更容易理解nodeSelectorTerms和matchExpressions字段,這兩個字段定義了節點的標簽必須滿足哪一種表達式,才能滿足pod調度的條件。樣例中的單個表達式比較容易理解,節點必須包含一個叫作gpu的標簽,並且這個標簽的值必須是true。
因此,這個pod只會被調度到包含gpu=true的節點上。如圖16.2所示。
現在,更有趣的部分來了,節點親緣性也可以在調度時指定節點的優先級,將在接下來的部分看到。
2.調度pod時優先考慮某些節點
最近介紹的節點親緣性的最大好處就是,當調度某一個pod時,指定調度器可以優先考慮哪些節點,這個功能是通過preferredDuringSchedulingIgnoredDuringExecution字段來實現的。
想象一下擁有一個跨越多個國家的多個數據中心,每一個數據中心代表了一個單獨的可用性區域。在每個區域中,有一些特定的機器,只提供給你自己或者你的合作公司使用。現在,你想要部署一些pod,希望將pod優先部署在區域zone1,並且是為你公司部署預留的機器上。如果你的機器沒有足夠的空間用於這些pod,或者出於其他一些重要的原因不希望這些pod調度到上面,那么就會調度到其他區域的其他機器上面,這種情況你也是可以接受的。節點親緣性就可以實現這樣的功能。
給節點加上標簽
首先,節點必須加上合適的標簽。每個節點需要包含兩個標簽,一個用於表示所在的這個節點所歸屬的可用性區域,另一個用於表示這是一個獨占的節點還是一個共享的節點。
在接下來假設雙節點的集群環境,將使用這個集群中的兩個工作節點,當然也可以使用GKE或者其他多節點的集群。
首先,給這些節點加上標簽,如以下代碼所示。
#代碼16.10 給節點加上標簽 $ kubectl label node node1.k8s availability-zone=zone1 node "node1.k8s" labeled $ kubectl label node node1.k8s share-type=dedicated node "node1.k8s" labeled $ kubectl label node node2.k8s availability-zone=zone2 node "node2.k8s" labeled $ kubectl label node node2.k8s share-type=shared node "node2.k8s" labeled $ kubectl get node -L availability-zone -L share-type NAME STATUS AGE VERSION AVAILABILITY-ZONE SHARE-TYPE master.k8s Ready 4d v1.6.4 <none> <none> node1.k8s Ready 4d v1.6.4 zone1 dedicated node2.k8s Ready 4d v1.6.4 zone2 shared
指定優先級節點親緣性規則
當這些節點的標簽設置好,現在可以創建一個Deployment,其中優先選擇zone1中的dedicated節點。下面的代碼清單顯示了這個Deployment的描述。
#代碼16.11 含有優先級節點親緣性規則的Deployment: preferred-deployment.yamI apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pref spec: replicas: 5 template: metadata: labels: app: pref spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: #指定優先級,這不是必需的 - weight: 80 #這塊是節點有限調度到zone1,這是最重要的偏好 preference: matchExpressions: - key: availability-zone operator: In values: - zone1 - weight: 20 #同時優先調度pod到獨占節點,但是該優先級為zone1優先級的1/4 preference: matchExpressions: - key: share-type operator: In values: - dedicated containers: - args: - sleep - "99999" image: busybox name: main
查看上面的代碼清單,定義了一個節點親緣性優先級,而不是強制要求(后面會講解)。想要pod被調度到包含標簽availability-zone=zone1以及share-type=dedicated的節點上。第一個優先級規則是相對重要的,因此將其weight設置為80,而第二個優先級規則就不那么重要(weight設置為20)。
了解節點優先級是如何工作的
如果集群包含多個節點,當調度上面的代碼中的Deployment pod時,節點將會分成4個組,如圖16.3所示。那些包含availability-zone以及share-type標簽,並且匹配pod親緣性的節點,將排在最前面。然后,由於pod的節點親緣性規則配置的權重,接下來是zone1的shared節點,然后是其他區域的dedicated節點,優先級最低的是剩下的其他節點。
在一個包含兩個節點的集群中部署節點
如果在一個包含兩個節點的集群中創建該部署,看到的最多的應該是pod被部署在了node1上面。檢查下面的代碼清單看情況是否屬實。
#代碼16.12 查看pod調度情況 $ kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE pref-607515-1rnwv 1/1 Running 0 4m 10.47.0.1 node2.k8s pref-607515-27wp0 1/1 Running 0 4m 10.44.0.8 node1.k8s pref-607515-5xd0z 1/1 Running 0 4m 10.44.0.5 node1.k8s pref-607515-jx9wt 1/1 Running 0 4m 10.44.0.4 node1.k8s pref-607515-mlgqm 1/1 Running 0 4m 10.44.0.6 node1.k8s
5個pod被創建,其中4個部署在了node1,1個部署在了node2。為什么會有1個pod會被調度到node2而不是node1?原因是除了節點親緣性的優先級函數,調度器還是使用其他的優先級函數來決定節點被調度到哪。其中之一就是Selector SpreadPriority函數,這個函數確保了屬於同一個ReplicaSet或者Service的pod,將分散部署在不同節點上,以避免單個節點失效導致這個服務也宕機。這就是有1個pod被調度到node2的最大可能。
可以去試着擴容部署至20個實例或更多,將看到大多數的pod被調度到node1。如果沒有設置任何節點親緣性優先級,pod將會被均勻地分配在兩個節點上面。
3.更多的親緣性匹配規則及語法
上面也簡單介紹了簡單的原理以及過程,現在介紹更多的規則以及案例。
節點親和調度分成軟策略(soft)和硬策略(hard),在軟策略下,如果沒有滿足調度條件的節點,pod會忽略這條規則,繼續完成調度。
- requiredDuringSchedulingIgnoredDuringExecution
表示pod必須部署到滿足條件的節點上,如果沒有滿足條件的節點,就不停重試。其中IgnoreDuringExecution表示pod部署之后運行的時候,如果節點標簽發生了變化,不再滿足pod指定的條件,pod也會繼續運行。
- requiredDuringSchedulingRequiredDuringExecution
表示pod必須部署到滿足條件的節點上,如果沒有滿足條件的節點,就不停重試。其中RequiredDuringExecution表示pod部署之后運行的時候,如果節點標簽發生了變化,不再滿足pod指定的條件,則重新選擇符合要求的節點。
- preferredDuringSchedulingIgnoredDuringExecution
表示優先部署到滿足條件的節點上,如果沒有滿足條件的節點,就忽略這些條件,按照正常邏輯部署。
- preferredDuringSchedulingRequiredDuringExecution
表示優先部署到滿足條件的節點上,如果沒有滿足條件的節點,就忽略這些條件,按照正常邏輯部署。其中RequiredDuringExecution表示如果后面節點標簽發生了變化,滿足了條件,則重新調度到滿足條件的節點。
再來一個官方示例
apiVersion: v1 kind: Pod metadata: name: with-node-affinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/e2e-az-name operator: In values: - e2e-az1 - e2e-az2 preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: another-node-label-key operator: In values: - another-node-label-value containers: - name: with-node-affinity image: gcr.io/google_containers/pause:2.0
這個pod同時定義了requiredDuringSchedulingIgnoredDuringExecution和 preferredDuringSchedulingIgnoredDuringExecution 兩種 nodeAffinity。第一個要求 pod 運行在特定AZ的節點上,第二個希望節點最好有對應的another-node-label-key:another-node-label-value 標簽。
這里的匹配邏輯是label在某個列表中,可選的操作符有:
-
- In: label的值在某個列表中
- NotIn:label的值不在某個列表中
- Exists:某個label存在
- DoesNotExist:某個label不存在
- Gt:label的值大於某個值(字符串比較)
- Lt:label的值小於某個值(字符串比較)
如果nodeAffinity中nodeSelector有多個選項,節點滿足任何一個條件即可;如果matchExpressions有多個選項,則節點必須同時滿足這些選項才能運行pod 。