1. 程式人生 > 實用技巧 >k8s 親和性

k8s 親和性

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又可由 一到多 個標籤選擇器組成,多個標籤選擇器彼此間為“邏輯與”的關係 。

下面的資源配置清單示例中定義了排程擁有兩個標籤選擇器的節點挑選條目,兩個標籤選擇器彼此之間為“邏輯與”的關係,因此,滿足其條件的節點為node01node03

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

構建標籤選擇器表示式中支援使用操作符有InNotlnExistsDoesNotExistLtGt

  • 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=foossd標籤(無論其值為何)的節點之上, 其中zone=foo是更為重要的傾向性規則, 它的權重為60,相比較來說,ssd標籤就沒有那麼關鍵, 它的權重為30。 這麼一來, 如果叢集中擁有足夠多的節點,那麼它將被此規則分為四類 : 同時滿足擁有zone=foossd標籤、僅具有zoo=foo標 籤、 僅具有ssd標籤, 以及不具備此兩個標籤, 如圖所示

示例環境共有三個節點,相對於定義的節點親和性規則來說,它們所擁有的傾向性權重分別如圖所示。在建立需要3Pod物件的副本時,其執行效果為三個Pod物件被分散運行於叢集中的三個節點之上,而非集中運行於某一個節點 。

之所以如此,是因為使用了節點軟親和性的預選方式,所有節點均能夠通過排程器上MatchNodeSelector預選策略的篩選,因此,可用節點取決於其他預選策略的篩選結果。在第二階段的優選過程中,除了NodeAffinityPriority優選函式之外,還有其他幾個優選函式參與優先順序評估,尤其是SelectorSpreadPriority,它會將同一個ReplicaSet控制器管控的所有Pod物件分散到不同的節點上執行以抵禦節點故障帶來的風險 。不過,這種節點親和性的權重依然在發揮作用,如果把副本數量擴充套件至越過節點數很多,如15個, 那麼它們將被排程器以接近節點親和性權重比值90:60:30的方式分置於相關的節點之上。

2、Pod親和性排程

2.1、位置拓撲

Pod親和性排程需要各相關的Pod物件運行於“同一位置”, 而反親和性排程則要求它們不能運行於“同一位置” 。同一位置取決於節點的位置拓撲, 拓撲的方式不同。

如果以基於各節點的kubernetes.io/hostname標籤作為評判標準,那麼很顯然,“同一位置” 意味著同一個節點,不同節點即不同的位置, 如圖所示

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

因此,在定義Pod物件的親和性與反親和性時,需要藉助於標籤選擇器來選擇被依賴的Pod物件,並根據選出的Pod物件所在節點的標籤來判定“同一位置”的具體意義。

2.2、Pod硬親和

Pod強制約束的親和性排程也使用requiredDuringSchedulinglgnoredDuringExecution屬性進行定義。Pod親和性用於描述一個Pod物件與具有某特徵的現存Pod物件執行位置的依賴關係,因此,測試使用Pod親和性約束,需要事先存在被依賴的Pod物件,它們具有特別的識別標籤。例如建立一個有著標籤app=tomcatDeployment資源部署一個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可能會部署於如上圖所示的foobar這兩個區域中的某節點之上,依賴於資料服務的myapp Pod物件可部署於db Pod所在區域內的節點上。當然,如果db Pod在兩個區域foobar中各有副本執行,那麼myapp Pod將可以運行於這兩個區域的任何節點之上。

依賴於親和於這兩個Pod的其他Pod物件可運行於zone標籤值為foobar的區域內的所有節點之上。資源配置清單如下

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 Poddb Pod同時所屬的zonecache Pod單獨所屬的zonedb 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

由於定義的強制性反親和約束,因此,建立的4Pod副本必須運行於不同的節點中。不過,如果叢集中一共只存在3個節點,因此,必然地會有一個Pod物件處於Pending狀態。

類似地,Pod反親和性排程也支援使用柔性約束機制,在排程時,它將盡量滿足不把位置相斥的Pod物件排程於同一位置,但是,當約束關係無法得到滿足時,也可以違反約束而排程。可參考podAffinity的柔性約束示例將上面的Deployment資源myapp-with-pod-anti-affinity修改為柔性約束並進行排程測試。