(十四)Kubernetes Pod調度器


Pod調度器

API Server 接收客戶端提交Pod對象創建請求后的操作過程中,有一個重要的步驟是由調度程序(kube-scheduler)從當前集群中選擇一個可用的最佳節點來接收並運行它,通常是默認的調度器(default-scheduler)負責執行此類任務。對於每個待創建的Pod對象來說,調度過程通常分為三個階段——預選、優選和選定三個步驟,以篩選執行任務的最佳節點。

Kubernetes 調度器概述

Kubernetes系統的核心任務在於創建客戶端請求創建的Pod對象並確保其以期望的狀態運行。創建Pod對象時,調度器scheduler負責為每一個未經調度的Pod資源、基於一系列的規則集從集群中挑選一個合適的節點來運行它,因此也稱為Pod調度器。調度過程中,調度器不會修改Pod資源,而是從中讀取數據,並根據配置的策略挑選出最適合的節點,而后通過API調用將Pod綁定至挑選出的節點之上以完成調度過程。

Pod調度器其核心目標是基於資源可用性將各Pod資源公平地分布於集群節點之上。通過三個步驟完成調度操作:節點預選(Predicate)、節點優先級排序(Priority)及節點擇優(Select);如下圖所示

📌 節點預選

基於一系列預選規則(如PodFitsResources和MatchNode-Selector等)對每個節點進行檢查,將那些不符合條件的節點過濾掉從而完成節點預選。

📌 節點優選

對預選出的節點進行優先級排序,以便選出最適合運行Pod對象的一個節點。

📌 節點擇優

從優選級排序結果中挑選出優先級最高的節點運行Pod對象,當此類節點多於一個時,則從中隨機選擇一個。

常用的預選策略

簡單來說,預選策略就是節點過濾器,列如節點標簽必須能夠匹配到Pod資源的標簽選擇器(由MatchNodeSelector實現的規則),以及Pod容器的資源請求量不能大於節點上剩余的可分配資源(由PodFitsResources實現的規則)等。執行預選操作時,調度器將每個節點基於配置使用的預選策略以特定次序逐一篩選,並根據一票否決制進行節點淘汰。若預選后不存在任何一個滿足條件的節點,則Pod處於Pending狀態,直到至少有一個節點可用為止。

常用的預選策略如下:

✏️ CheckNodeCondition:檢查是否可以在節點報告磁盤、網絡不可用或未准備好的情況下將Pod對象調度於其上。

✏️ HostName:若Pod對象擁有spec.hostname屬性,則檢查節點名稱字符串與此屬性值是否匹配。

✏️ PodFitsHostPorts:若Pod容器定義了ports.hostPort屬性,則檢查其值指定的端口是否已被節點上的其他容器或服務占用。

✏️ MatchNodeSelector:若Pod對象定義了sepc.nodeSelector屬性,則檢查節點標簽是否能匹配此屬性值。

✏️ NoDiskConflict:檢查Pod對象請求的存儲卷在此節點是否可用,若不存在沖突則通過檢查。

✏️ PodFitsResources:檢查節點是否有足夠的資源(如CPU、內存和GPU等)滿足Pod對象的運行需求。

✏️ PodToleratesNodeTaints:若Pod對象定義了spec.tolerations屬性,則檢查其值是否能夠接納節點定義的污點(taints),不過,它僅關注具有NoScheduleNoExecute兩個效用標識的污點。

✏️ PodToleratesNodeNoExecuteTaints:若Pod對象定義了spec.tolerations屬性,則檢查其值是否能夠接納節點定義的NoExecute類型的污點。

✏️ CheckNodeLabelPresence:僅檢查節點上指定的所有標簽的存在性,要檢查的標簽以及其可否存在取決於用戶的定義。

✏️ CheckServiceAffinity:根據當前Pod對象所屬的Service已有的其他Pod對象所運行的節點進行調度,其目的在於將相同的ServicePod對象放置在同一個或同一類節點上以提高效率。

✏️ MaxEBSVolumeCount:檢查節點上已掛載的EBS存儲卷數量是否超過了設置的最大值,默認值為39。

✏️ MaxGCEPDVolumeCount:檢查節點上已掛載的GCE PD存儲卷數量是否超過了設置的最大值,默認值為16。

✏️ MaxAzureDiskVolumeCount:檢查節點上已掛載的Azure Disk存儲卷數量是否超過了設置的最大值,默認值為16。

✏️ CheckVolumeBinding:檢查節點上已綁定和未綁定的PVC是否能夠滿足Pod對象的存儲卷需求,對於已綁定的PVC,此預選策略將檢查給定的節點是否能夠兼容相應的PV,而對於未綁定的PVC,預選策略將搜索那些可滿足PVC申請的可用的PV,並確保它可與給定的節點兼容。

✏️ NoVolumeZoneConflict:在給定了區域(zone)限制的前提下,檢查在此節點上部署Pod對象是否存在存儲卷沖突。某些存儲卷存在區域調度約束,於是,此類存儲卷的區域標簽(zone-labels)必須與節點上的區域標簽完全匹配方可滿足綁定條件。

✏️ CheckNodeMemoryPressure:若給定的節點已經報告了存在內存資源壓力過大的狀態,則檢查當前Pod對象是否可調度至此類節點之上。

✏️ CheckNodePIDPressure:若給定的節點已經報告了存在PID資源壓力過大的狀態,則檢查當前Pod對象是否可調度至此節點之上。

✏️ CheckNodeDiskPressure:若給定的節點已經報告了存在磁盤資源壓力過大的狀態,則檢查當前Pod對象是否可調度至此節點之上。

✏️ MatchInterPodAffinity:檢查給定節點是否能夠滿足Pod對象的親和性或反親和性條件,以用於實現Pod親和性調度或反親和性調度。

常用的優選函數

預選策略篩選並生成一個節點列表后即進入第二階段的優選過程。在這個過程中,調度器向每個通過預選的節點傳遞一系列的優選函數來計算其優先級分值,優先級分值介於0到10之間,其中0表示不適合,10表示最適合托管該Pod對象。

常見的優選函數相關說明如下:

✏️ LeastRequestedPriority:由節點空閑資源與節點總容量的比值計算而來,即由CPU或內存資源的總容量減去節點上已有Pod對象需求的容量總和,再減去當前要創建的Pod對象的需求容量得到的結果除以總容量。CPU和內存具有相同的權重,資源空閑比例越高的節點得分就越高,其計算公式為:(cpu((capacity - sum(requested)) * 10/capacity) + memory((capacity - sum(requested)) * 10/capacity))/2 。

✏️ BalancedResourceAllocation:以CPU和內存資源占用率的相近程序作為評估標准,二者越接近的節點權重越高。該優選函數不能單獨使用,需要和LeastRequestedPriority組合使用來平衡優化節點資源的使用情況,以選擇那些在部署當前Pod資源后系統資源更為均衡的節點。

✏️ NodePreferAvoidPodsPriority:此優選級函數權限默認為10000,根據節點是否設置了注解信息"scheduler.alpha.kubernetes.io/preferAvoidPods"來計算其優選級。計算方式是:給定的節點無此注解信息時,其得分為10乘以權重10000;存在此注解信息時,對於那些由ReplicationControllerReplicaSet控制器管控的Pod對象的得分為0,其他Pod對象會被忽略(得最高分)。

✏️ NodeAffinityPriority:基於節點親和性調度偏好進行優先級評估,它將根據Pod資源中的nodeSelector對給定節點進行匹配度檢查,成功匹配到的條目越多則節點的分越高。

✏️ TaintTolerationPriority:基於Pod資源對節點的污點容忍調度偏好進行其優先級評估,它將Pod對象的tolerations列表與節點的污點進行匹配度檢查,成功匹配的條目越多,則節點得分越低。

✏️ SelectorSpreadPriority:首先查找與當前Pod對相匹配的Service、ReplicationController、ReplicaSet(RS)和StatefulSet,而后查找與這些選擇器匹配的現存Pod對象及其所在的節點,則運行此類Pod對象越少的節點得分將越高。簡單來說,會盡量將同一標簽選擇器匹配到的Pod資源分散到不同的節點之上來運行。

✏️ InterPodAffinityPriority:遍歷Pod對象的親和性條目,並將那些能夠匹配給定節點的條目的權重相加,結果值越大的節點得分越高。

✏️ MostRequestedPriority:與優選函數LeastRequestedPriority的評估節點得分的方法相似,不同的是,資源站用比例越大的節點,其得分越高。

✏️ NodeLabelPriority:根據節點是否擁有特定的標簽來評估其得分,而無論其值為何。需要其存在時,擁有相應標簽的節點將獲得優先級,否則,不具有相應標簽的節點將獲得優先級。

✏️ ImageLocalItyPriority:基於給定節點上擁有的運行當前Pod對象中的容器所依賴到的鏡像文件來計算節點得分,不具有Pod依賴到的任何鏡像文件的節點其得分為0,而擁有相應鏡像文件的各節點中,所擁有的被依賴到的鏡像文件其體積之和越大則節點得分越高。

節點親和調度

節點親和性是調度程序用來確定Pod對象調度位置的一組規則,這些規則基於節點上的自定義標簽和Pod對象上指定的標簽選擇器進行定義。節點親和性允許Pod對象定義針對一組可以調度於其上的節點的親和性或反親和性,不過,它無法具體到某個特定的節點。如將Pod調度至有着特殊CPU的節點或一個可用區域內的節點之上。

節點親和性規則有兩種類型:硬親和性(required)和軟親和性(preferred)。硬親和性實現的是強制性規則,Pod調度時必須要滿足的規則,而在不存在滿足規則的節點時,Pod對象會被置為Pending狀態。而軟親和性規則實現的是一種柔和性調度限制,傾向於將Pod對象運行於某類特定的節點之上,而調度器也將盡量滿足此需求,但在無法滿足調度需求時它將退而求其次地選擇一個不匹配規則的節點。

定義節點親和性規則的關鍵點有兩個,一是為節點配置合乎需求的標簽,另一個是為Pod對象定義合理的標簽選擇器,從而能夠基於標簽選擇出符合期望的目標節點。不過preferredDuringSchedulingIgnoredDuringExecutionrequiredDuringSchedulingIgnoredDuringExecution名字中的后半段字符串IgnoredDuringExecution隱含的意義所指,在Pod資源基於節點親和性規則調度至某節點之后,節點標簽發生了改變而不再符合此節點親和性規則時,調度器不會將Pod對象從此節點上移除,因為它僅對新建的Pod對象生效。節點親和性模型如下:

節點硬親和性

📝 示例一

下面的配置清單示例中定義的Pod對象,其使用節點硬親和性規則定義可將當前Pod對象調度至擁有zone標簽其值為foo的節點之上:

# vim required-nodeaffinity-demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-required-nodeaffinity
  namespace: default
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    image: blwy/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: zone, operator: In, values: ["foo"]}

接下來創建pod,並查看其狀態

# kubectl apply -f required-nodeaffinity-demo-pod.yaml
pod/with-required-nodeaffinity created

# kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
with-required-nodeaffinity   0/1     Pending   0          4s

# 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.
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

通過kubectl get podskubectl describe pods查看pod,可以發現pod一直處於Pending狀態,調度失敗,這是由於強制型的節點親和性和限制場景中不存在能夠滿足匹配條件的節點所致。

接下來給node節點設置節點標簽,這也是設置節點親和性的前提之一。

# kubectl label node k8s-node1 zone=foo
node/k8s-node1 labeled

# kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
with-required-nodeaffinity   1/1     Running   0          1m

# 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.
  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-node1
  Normal   Pulled            9s         kubelet, k8s-node1  Container image "blwy/myapp:v1" already present on machine
  Normal   Created           9s         kubelet, k8s-node1  Created container myapp
  Normal   Started           9s         kubelet, k8s-node1  Started container myapp

通過給其中一個node節點打上標簽后,可以看出pod狀態一下就變成了Running狀態,這是因為節點硬親和性匹配到了規則。

在定義節點親和性時,requiredDuringSchedulingIgnoredDuringExecution字段的值是一個對象列表,用於定義節點硬親和性,可以定義多個nodeSelectorTerm對象,彼此之間為“邏輯或“的關系,進行匹配度檢查時,在多個nodeSelectorTerm之間只要滿足其中之一即可。nodeSelectorTerm用於定義節點選擇器條目,其值為對象列表,由一個或多個matchExpressions對象定義的匹配規則組成,多個規則彼此之間為”邏輯與“的關系,意味着某節點的標簽需要完全匹配同一個nodeSelectorTerm下所有的matchExpression對象定義的規則才算成功通過節點選擇器條目的檢查。而matchExpressions又可由一到多個標簽選擇器組成,多個標簽選擇器彼此間為”邏輯與“的關系。

📝 示例二

下面的資源配置清單示例中定義了調度擁有兩個標簽選擇器的節點選擇頭目。兩個標簽選擇器彼此間為“邏輯與”的關系。

為了測試,這里先給節點多打幾個標簽

# kubectl label node k8s-node1 ssd=true
node/k8s-node1 labeled
# kubectl label node k8s-node2 ssd=true
node/k8s-node2 labeled
# kubectl label node k8s-node2 zone=bar
node/k8s-node2 labeled

查看節點標簽
# kubectl get nodes --show-labels
NAME          STATUS   ROLES    AGE   VERSION   LABELS
k8s-master1   Ready    master   47d   v1.16.6   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master1,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-node1     Ready    <none>   47d   v1.16.6   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node1,kubernetes.io/os=linux,ssd=true,zone=foo
k8s-node2     Ready    <none>   47d   v1.16.6   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node2,kubernetes.io/os=linux,ssd=true,zone=bar

編寫yaml文件

# vim required-nodeaffinity-demo-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-required-nodeaffinity-2
  namespace: default
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    image: blwy/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: zone, operator: In, values: ["foo"]}
          - {key: ssd, operator: Exists, values: []}

接下來創建pode2,可以明確知道滿足條件的只有node2節點,因為同時滿足了zone=foossd=true

# kubectl apply -f required-nodeaffinity-demo-pod2.yaml
pod/with-required-nodeaffinity-2 created

# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
with-required-nodeaffinity-2   1/1     Running   0          4s

# kubectl describe pods with-required-nodeaffinity-2
Events:
  Type    Reason     Age        From                Message
  ----    ------     ----       ----                -------
  Normal  Scheduled  <unknown>  default-scheduler   Successfully assigned default/with-required-nodeaffinity-2 to k8s-node1
  Normal  Pulled     15s        kubelet, k8s-node1  Container image "blwy/myapp:v1" already present on machine
  Normal  Created    15s        kubelet, k8s-node1  Created container myapp
  Normal  Started    15s        kubelet, k8s-node1  Started container myapp

注意:調度器在調度Pod資源時,節點親和性僅是其節點預選策略中遵循的預選機制之一,其他配置使用的預選策略依然正常參與節點預選過程。例如下面的測試,將上面的資源配置清單修改如下

# vim required-nodeaffinity-demo-pod3.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-required-nodeaffinity-3
  namespace: default
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    image: blwy/myapp:v1
    resources:
      requests:
        cpu: 6
        memory: 20Gi
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: zone, operator: In, values: ["foo"]}
          - {key: ssd, operator: Exists, values: []}


# kubectl apply -f required-nodeaffinity-demo-pod3.yaml
pod/with-required-nodeaffinity-3 created

# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
with-required-nodeaffinity-3   0/1     Pending   0          4s

# kubectl describe pods with-required-nodeaffinity-3
....
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 2 node(s) didn't match node selector, 3 Insufficient cpu, 3 Insufficient memory.
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 2 node(s) didn't match node selector, 3 Insufficient cpu, 3 Insufficient memory.

上面的with-required-nodeaffinity-3 pod資源一直處於Pending狀態,這是因為這里所有節點都無法滿足需求,這里所有節點配置在2核心CPU2GB內存,無法滿足容器myapp的需求,所以調度失敗。

節點軟親和性

節點軟親和性為節點選擇機制提供了一種柔性控制邏輯,被調度的Pod對象不再是"必須"而是"應該"放置於某些特定的節點之上,當條件不滿足時,也能夠接受編排於其他不符合條件的節點之上。還提供了weight屬性以便用戶自定義其優先級,取值范圍是1~100,數字越大優先級越高。

📝 示例

# vim deploy-with-preferred-nodeaffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy-with-node-affinity
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      name: myapp-pod
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: blwy/myapp:v1
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 60
            preference:
              matchExpressions:
              - {key: zone, operator: In, values: ["foo"]}
          - weight: 30
            preference:
              matchExpressions:
              - {key: ssd, operator: Exists, values: []}

上面資源配置清單中定義了節點軟親和性以選擇運行在擁有zone=foossd標簽(無論其值為何)的節點之上,其中zone=foo是更為重要的傾向性規則,權重為60,相比ssd標簽就沒有那么關鍵,權重為30。這樣一來,如果集群中擁有足夠多的節點,那么它將被此規則分為四類:同時滿足擁有zone=foossd標簽、僅具有zone=foo標簽、僅具有ssd標簽,以及不具備此兩個標簽,如下圖所示:

# kubectl apply -f deploy-with-preferred-nodeaffinity-demo.yaml
deployment.apps/myapp-deploy-with-node-affinity created

# kubectl get pods -o wide
NAME                                               READY   STATUS    RESTARTS   AGE    IP            NODE        NOMINATED NODE   READINESS GATES
myapp-deploy-with-node-affinity-6d7884b4c8-kdg5n   1/1     Running   0          116s   10.244.1.11   k8s-node1   <none>           <none>
myapp-deploy-with-node-affinity-6d7884b4c8-rjn99   1/1     Running   0          116s   10.244.1.10   k8s-node1   <none>           <none>
myapp-deploy-with-node-affinity-6d7884b4c8-rnd4r   1/1     Running   0          116s   10.244.2.10   k8s-node2   <none>           <none>

這里只有兩個工作節點,可以從上面的結果看出,node1節點運行了兩個pod,這是因為上面定義的規則中,node1節點同時具有zone=foo標簽和ssd標簽,且權重為60。

Pod 資源親和調度

出於高效通信的需求,偶爾需要把一些Pod對象組織在相近的位置(同一節點、機架、區域或地區等),如一個業務的前端Pod和后端Pod等,此時可以將這些Pod對象間的關系稱為親和性。偶爾,出於安全或分布式等原因也有可能需要將一些Pod對象在其運行的位置上隔離開來,如在每個區域運行一個應用代理Pod對象等,此時可以把這些Pod對象間的關系稱為反親和性(anti-affinity)

Kubernetes調度器通過內建的MatchInterPodAffinity預選策略為這種調度方式完成節點預選,並給予InterPodAffinityPriority優選函數進行各節點的優選級評估。

位置拓撲

Pod親和性調度需要各相關的Pod對象運行於"同一位置",而反親和性調度則需要它們不能運行於"同一位置"。何謂同一位置?事實上,取決於節點的位置拓撲,拓撲的方式不同,如下圖"Pod資源與位置拓撲"中的Pod-APod-B是否在同一位置的判定結果也可能有所不同

如果以基於各節點的kubernetes.io/hostname標簽作為評判標准,那么"同一位置"意味着同一節點,不同節點即不同的位置,如"基於節點的位置拓撲"圖所示

如果是基於"故障轉移域"來進行評判,那么server1server4屬於同一位置,而server2server3屬於另一個意義上的同一位置。

故此,在定義Pod對象的親和性與反親和性時,需要借助於標簽選擇器來選擇被依賴的Pod對象 ,並根據選出的Pod對象所在的節點的標簽來判定"同一位置"的具體意義。

Pod 硬親和調度

Pod硬親和性調度也是用requiredDuringSchedulingIgnoredDuringExecution屬性進行定義。Pod親和性用於描述一個Pod對象與具有某特征的現存Pod對象運行位置的依賴關系,因此,測試使用Pod親和性約束,需要事先存在被依賴的Pod對象,具有特別的識別標簽。

📝 示例1

下面創建一個有着標簽app=tomcat的deployment資源部署一個Pod對象:

# kubectl run tomcat -l app=tomcat --image tomcat:alpine

下面資源配置清單中定義了一個Pod對象,通過labelsSelector定義的標簽選擇器挑選感興趣的現存Pod對象,然后根據挑選出的Pod對象所在節點的標簽kubenetes.io/hostname 來判斷同一位置的具體含義,並將當前Pod對象調度至這一位置的某節點之上:

# vim required-podAffinity-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-paffinity-1
  namespace: default
spec:
  containers:
  - name: myapp
    image: blwy/myapp:v1
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app, operator: In, values: ["tomcat"]}
        topologyKey: kubernetes.io/hostname

事實上,kubernetes.io/hostname標簽是kubernetes集群節點的內建標簽,它的值為當前節點的節點主機名稱標識,各個節點均不相同。因此,新建的Pod對象將部署至被依賴的Pod對象的同一節點之上。requiredDuringSchedulingIgnoredDuringExecution表示這種親和性為強制約束。

# kubectl apply -f required-podAffinity-pod1.yaml
pod/with-pod-paffinity-1 created

# kubectl get pods  -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
tomcat-6c999f4c7d-x5x95   1/1     Running   0          12m   10.244.1.41   k8s-node1   <none>           <none>
with-pod-paffinity-1      1/1     Running   0          25s   10.244.2.42   k8s-node2   <none>           <none>

基於單一節點的Pod親和性只在極個別的情況下才可能會用到,較為常用的是基於同一地區、區域或幾家的拓撲位置約束。例如部署應用程序myapp與數據庫db服務相關的Pod時,db pod可能會部署於如下圖"pod硬親和性"所示的foobar這兩個區域中的某個節點之上,依賴與數據服務的myapp pod對象可部署於 db pod所在區域的節點上。如果db pod在兩個區域foobar中各有副本運行,那么myapp將可以運行於這兩個區域的任何節點之上。

📝 示例2

創建具有兩個擁有標簽為"app=db"的副本Pod作為被依賴的資源,這里隨機分配給了兩個節點。

# kubectl run db -l app=db --image=redis:alpine --replicas=2

# kubectl get pods -l app=db -o wide
NAME                  READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
db-86f4768855-cdtq7   1/1     Running   0          32s   10.244.1.43   k8s-node1   <none>           <none>
db-86f4768855-pftfv   1/1     Running   0          32s   10.244.2.32   k8s-node2   <none>           <none>

於是,依賴於親和於這兩個Pod的其他pod對象可運行於zone標簽值為foobar的區域內的所有節點之上。下面進行定義一個Deployment控制器管理的Pod資源示例

# cat deploy-with-required-podAffinity-pod2.yaml
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:
      containers:
      - name: myapp
        image: blwy/myapp:v1

在調度器示例中的Deployment控制器創建的Pod資源時,調度器首先會基於標簽選擇器查詢擁有標簽"app=db"的所有Pod資源,接着獲取它們分別所屬的節點的zone標簽值,接下來再查詢擁有匹配這些標簽值的所有節點,從而完成節點預選。根據優選函數計算這些節點的優先級,從而挑選出運行新建Pod對象的節點。

# kubectl apply -f deploy-with-required-podAffinity-pod2.yaml
deployment.apps/myapp-with-pod-affinity created

# kubectl get pods -o wide
NAME                                      READY   STATUS    RESTARTS   AGE    IP           NODE           NOMINATED NODE   READINESS GATES
db-86f4768855-cdtq7                       1/1     Running   0          132m   10.244.1.43   k8s-node1   <none>           <none>
db-86f4768855-pftfv                       1/1     Running   0          132m   10.244.2.32   k8s-node2   <none>           <none>
myapp-with-pod-affinity-c775766d5-86c48   1/1     Running   0          5s     10.244.1.44   k8s-node1   <none>           <none>
myapp-with-pod-affinity-c775766d5-gdfxp   1/1     Running   0          5s     10.244.2.39   k8s-node2   <none>           <none>
myapp-with-pod-affinity-c775766d5-ptcfd   1/1     Running   0          5s     10.244.2.40   k8s-node2   <none>           <none>

注意:如果節點上的標簽在運行時發生了變更,以致不再滿足Pod上的親和性規則,但該Pod還將繼續在該節點上運行,因此它僅會影響新建的Pod資源;另外、labelSelector屬性僅匹配與被調度器的Pod在同一名稱空間中的Pod資源,不過也可以通過為其天劍namespace字段以指定其他名稱空間。

Pod 軟親和調度

類似於節點親和性機制,Pod也支持使用preferredDuringSchedulingIgnoredDuringExecution屬性定義柔性親和性機制,調度器會盡力確保滿足親和性約束的調度邏輯,然而在約束條件不能得到滿足時,它也允許將Pod對象調度至其他節點運行。

📝 示例

# vim deploy-with-perferred-podAffinity-pod.yaml
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:
      containers:
      - name: myapp
        image: blwy/myapp:v1
      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

上面資源配置清單中定義了兩組親和性判定機制,一個是選出cache Pod所在節點的zone標簽,並賦予了較高的權重80,另一個是選擇db Pod所在節點的zone標簽,有着略低的權重20。於是,調度器會將目標節點分為四類:cache Poddb Pod同時所屬的zonecache Pod單獨所屬的zonedb Pod單獨所屬的zone,以及其他所有的zone

# kubectl apply -f deploy-with-perferred-podAffinity-pod.yaml
deployment.apps/myapp-with-preferred-pod-affinity created

# kubectl get pods  -o wide
NAME                                                 READY   STATUS    RESTARTS   AGE    IP           NODE           NOMINATED NODE   READINESS GATES
db-86f4768855-cdtq7                                  1/1     Running   0          160m   10.244.1.43   k8s-node1   <none>           <none>
db-86f4768855-pftfv                                  1/1     Running   0          160m   10.244.2.32   k8s-node2   <none>           <none>
myapp-with-preferred-pod-affinity-79b96db78b-24mnl   1/1     Running   0          7s     10.244.1.45   k8s-node1   <none>           <none>
myapp-with-preferred-pod-affinity-79b96db78b-l9rt9   1/1     Running   0          7s     10.244.2.41   k8s-node2   <none>           <none>
myapp-with-preferred-pod-affinity-79b96db78b-vwfbw   1/1     Running   0          7s     10.244.2.42   k8s-node2   <none> 

Pod 反親和調度

podAffinity 用於定義Pod對象的親和約束,對應地,將其替換為podAntiAffinity即可用於定義Pod對象的反親和約束。反親和性調度一般用於分散同一類應用的Pod對象等,也包括將不同安全級別的Pod對象調度至不同的區域、機架或節點等。

📝 示例

這里將定義由同一Deployment創建但彼此基於節點位置互斥的Pod對象:

# vim deploy-with-required-podAntiAffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-with-pod-anti-affinity
  namespace: default
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      name: myapp
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: blwy/myapp:v1
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - {key: app, operator: In, values: ["myapp"]}
            topologyKey: kubernetes.io/hostname

由於定義的強制性反親和約束,因此,創建的4個Pod副本必須運行於不同的節點中,不過,由於這里實驗只有兩台工作節點,因此,必然會有兩個Pod對象處於Pending狀態。

# kubectl apply -f deploy-with-required-podAntiAffinity-demo.yaml
deployment.apps/myapp-with-pod-anti-affinity created

# kubectl get pods -o wide
NAME                                            READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
myapp-with-pod-anti-affinity-5d7ddf7766-8rql7   0/1     Pending   0          5s    <none>        <none>      <none>           <none>
myapp-with-pod-anti-affinity-5d7ddf7766-b85c6   1/1     Running   0          5s    10.244.2.25   k8s-node2   <none>           <none>
myapp-with-pod-anti-affinity-5d7ddf7766-t25xt   0/1     Pending   0          5s    <none>        <none>      <none>           <none>
myapp-with-pod-anti-affinity-5d7ddf7766-tbb94   1/1     Running   0          5s    10.244.1.15   k8s-node1   <none>           <none>

Pod反親和性調度也支持使用柔性約束機制,在調度時,將盡量滿足不把位置相斥的Pod對象調度於同一位置,但是,當約束條件無法得到滿足時,也可以違反約束而調度。

污點和容忍度

污點(taints)是定義節點之上的鍵值型屬性數據,用於讓節點拒絕將Pod調度運行於其上,除非該Pod對象具有接納節點污點的容忍度。而容忍度(tolerations)是定義在Pod對象上的鍵值型屬性數據,用於配置其可容忍的節點污點,而且調度器僅能將Pod對象調度至其能夠容忍該節點污點的節點之上,如下圖所示:

上面的節點選擇器(nodeSelector)和節點親和性(nodeAffinity)兩種調度方式都是通過在Pod對象上添加標簽選擇器來完成對特定類型節點標簽的匹配,它們實現的是由Pod選擇節點的機制。而污點和容忍度則是通過向節點添加污點信息來控制Pod對象的調度結果,從而賦予了節點控制何種Pod對象能夠調度於其上的主控權。簡單來說,節點親和性使得Pod對象被吸引到一類特定的節點,而污點則相反,提供了讓節點排斥特定Pod對象的能量。

Kubernetes使用PodToleratesNodeTaints預選策略和TaintTolerationPriority優選函數來完成此種類型的高級調度機制。

定義污點和容忍度

污點定義在節點的nodeSpec中,容忍度定義在PodpodSpec中,都是鍵值型數據,都額外支持(effect)標記,語法格式為"key=value:effect",其中keyvalue的用法及格式與資源注解信息相似,而effect則用於定義對Pod對象的排斥等級,主要包含以下三種類型:

  • NoSchedule:不能容忍此污點的新Pod對象不可調度至當前節點,屬於強制型約束關系,節點上現存的Pod對象不受影響。
  • PreferNoScheduleNoSchedule的柔性約束版本,即不能容忍此污點的新Pod對象盡量不要調度至當前節點,不過無其它節點可供調度時也允許接受相應的Pod對象。節點上現存的Pod對象不受影響。
  • NoExecute:不能容忍此污點的新Pod對象不可調度至當前節點,屬於強制型約束關系,而且節點上現存的Pod對象因節點污點變動或Pod容忍度變動而不再滿足匹配規則時,Pod對象將被驅逐。

Pod對象上定義容忍度時,支持兩種操作符:一種是等值比較(Equal),表示容忍度與污點必須在keyvalueeffect三者之上完全匹配;另一種是存在性判斷(Exists),表示二者的keyeffect必須完全匹配,而容忍度中的value字段要使用空值。

注意:一個節點可以配置使用多個污點,一個Pod對象也可以有多個容忍度,不過二者在進行匹配檢查時遵循如下邏輯:

  1. 首先處理每個有着與之匹配的容忍度的污點。

  2. 不能匹配到的污點上,如果存在了一個使用NoSchedule效用標識,則拒絕調度Pod對象至此節點。

  3. 不能匹配到的污點上,若沒有任何一個使用了NoSchedule效用標識,但至少有一個使用了PreferNoScheduler,則應盡量避免將Pod對象調度至此節點。

  4. 如果至少有一個不匹配的污點使用了NoExecute效用標識,則節點將立即驅逐Pod對象,或者不予調度至給定節點;另外,即便容忍度可以匹配到使用了NoExecute效用標識的污點,若在定義容忍度時還同時使用tolerationSeconds屬性定義了容忍時限,則超出時限后期也將被節點驅逐。

使用kubeadm部署的Kubernetes集群,其Master節點將自動添加污點信息以阻止不能容忍此污點的Pod對象調度至此節點,因此,用戶手動創建的未特意添加容忍此污點容忍度的Pod對象將不會被調度至此節點:

# kubectl describe node k8s-master
Name:               k8s-master
Roles:              master
......
Taints:             node-role.kubernetes.io/master:NoSchedule

不過,有些系統級應用,如kube-proxy或者kube-flannel等,都在資源創建時就添加上了相應的容忍度以確保它們被DaemonSet控制器創建時能夠調度至Master節點運行一個實例:

# kubectl describe pods kube-flannel-ds-amd64-bg7gc -n kube-system
......
Node-Selectors:  <none>
Tolerations:     :NoSchedule
                 node.kubernetes.io/disk-pressure:NoSchedule
                 node.kubernetes.io/memory-pressure:NoSchedule
                 node.kubernetes.io/network-unavailable:NoSchedule
                 node.kubernetes.io/not-ready:NoExecute
                 node.kubernetes.io/pid-pressure:NoSchedule
                 node.kubernetes.io/unreachable:NoExecute
                 node.kubernetes.io/unschedulable:NoSchedule

另外,這類Pod是構成Kubernetes系統的基礎且關鍵性的組件,它們甚至還定義了更大的容忍度。通過上面的kube-flannel實例的容忍度定義來看,它還能容忍那些報告了磁盤壓力或內存壓力的節點,以及未就緒的節點和不可達的節點,以確保它們能在任何狀態下正常調度至集群節點上運行。

管理節點的污點

任何符合其鍵值規范要求的字符串均可用於定義污點信息:僅可使用字母、數字、連接符、點號和下划線,且僅能以字母或數字開頭,其中鍵名長度上限為253個字符,值最長為63個字符。可以用污點用來描述具體的部署規划,鍵名比如node-type、node-role、node-project或node-geo等,還可以在必要時戴上域名以描述其額外的信息,如node-type.ilinux.io等。

📌 添加污點

kubectl taint命令即可向節點添加污點,命令格式如下:

kubectl taint nodes <node-name> <key>=<value>:<effect> ...

例如,使用"node-type=production:NoSchedule"定義節點k8s-node1

# kubectl taint nodes k8s-node1 node-type=production:NoSchedule
node/k8s-node1 tainted

此時,k8s-node1上已有的Pod對象不受影響,但新建的Pod若不能容忍此污點將不能再被調度至此節點。通過下面的命令可以查看節點的污點信息:

# kubectl get nodes k8s-node1 -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node-type value:production]]

需要注意的是,即便是同一個鍵值數據,若其效用標識不同,則其也分屬於不同的污點信息,例如,將上面命令中的效用標識定義為PreferNoSchedule再添加一次:

# kubectl taint nodes k8s-node1 node-type=production:PreferNoSchedule
node/k8s-node1 tainted

📌 刪除污點

刪除污點,仍通過kubectl taint命令進行,但是要使用如下的命令格式,省略效用標識則表示刪除使用指定鍵名的所有污點,否則就只是刪除指定健名上對應效用標識的污點:

kubectl taint nodes <node-name> <key>[:<effect>]-

例如,刪除k8s-node1node-type鍵的效用標識為NoSchedule的污點信息:

# kubectl taint nodes k8s-node1 node-type:NoSchedule-
node/k8s-node1 untainted

若要刪除使用指定健名的所有污點,則在刪除命令中省略效用標識即能實現,例如:

# kubectl taint nodes k8s-node1 node-type-
node/k8s-node1 untainted

刪除節點上的全部污點信息,通過kubectp patch命令將節點屬性spec.taints的值直接置空,例如:

# kubectl patch nodes k8s-node1 -p '{"spce":{"taints":[]}}'

節點污點的變動會影響到新建Pod對象的調度結果,而且使用NoExecute進行標識時,還會影響到節點上現有的Pod對象。

Pod 對象的容忍度

Pod對象的容忍度可通過其spec.tolerations字段進行添加,根據使用的操作符不同,主要有兩種可用的形式:一種是與污點信息完全匹配的等值關系;另一種是判斷污點信息存在性的匹配方式。使用Equal操作符的示例如下所示,其中tolerationSeconds用於定義延遲驅逐當前Pod對象的時長:

📝 示例

首先給兩個工作節點添加不同的污點

# kubectl taint node k8s-node1 node-type=production:NoSchedule
# kubectl taint node k8s-node2 node-type=dev:NoExecute

編寫示例yaml文件

# vim deploy-taint-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-taint-demo-pod
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: blwy/myapp:v1
        ports:
        - name:
          containerPort: 80
      tolerations:
      - key: "node-type"
        operator: "Equal"
        value: "production"
        effect: "NoExecute"
        tolerationSeconds: 30

創建資源,可以發現目前pod一直處於Pending狀態,這是因為兩個節點都不能容忍。

# kubectl apply -f deploy-taint-demo.yaml
deployment.apps/deploy-taint-demo-pod created
# kubectl get pods -o wide
NAME                                     READY   STATUS        RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
deploy-taint-demo-pod-7459f65d64-426vg   0/1     Pending       0          4s      <none>        <none>      <none>           <none>
deploy-taint-demo-pod-7459f65d64-h6fbp   0/1     Pending       0          4s      <none>        <none>      <none>           <none>
deploy-taint-demo-pod-7459f65d64-k4xjw   0/1     Pending       0          4s      <none>        <none>      <none>           <none>

修改資源配置清單,將容忍度改為NoSchedule, 這個示例中表示容忍key為node-type值為production的污點

# vim deploy-taint-demo.yaml
      tolerations:
      - key: "node-type"
        operator: "Equal"
        value: "production"
        effect: "NoSchedule"

重新創建,可以發現已經調度到k8s-node1節點上

# kubectl apply -f deploy-taint-demo.yaml
deployment.apps/deploy-taint-demo-pod configured
[root@k8s-master1 schedule]# kubectl get pods -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
deploy-taint-demo-pod-87cffffdd-9tm29   1/1     Running   0          3s    10.244.1.24   k8s-node1   <none>           <none>
deploy-taint-demo-pod-87cffffdd-qt8wf   1/1     Running   0          2s    10.244.1.25   k8s-node1   <none>           <none>
deploy-taint-demo-pod-87cffffdd-zgmrl   1/1     Running   0          4s    10.244.1.23   k8s-node1   <none>           <none>

還可以通過Exists進行判斷,修改資源配置清單如下,下面這段表示,只要有污點key為node-type,不論值為什么且是什么類型的污點,都能容忍

# vim deploy-taint-demo.yaml
      tolerations:
      - key: "node-type"
        operator: "Exists"
        value: ""
        effect: ""

查看結果可以看出,兩個節點都已經進行調度了。

# kubectl get pods -o wide
NAME                                     READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
deploy-taint-demo-pod-7c87f4957f-4njh7   1/1     Running   0          16s   10.244.2.27   k8s-node2   <none>           <none>
deploy-taint-demo-pod-7c87f4957f-mj46d   1/1     Running   0          14s   10.244.1.26   k8s-node1   <none>           <none>
deploy-taint-demo-pod-7c87f4957f-vp9t2   1/1     Running   0          13s   10.244.2.28   k8s-node2   <none>           <none>

問題節點標識

Kubernetes1.6版本起支持使用污點自動標識問題節點,通過節點控制器在特定條件下自動為節點添加污點信息實現。它們都是用NoExecute效用標識,因此不能容忍此類污點的現有Pod對象也會遭到驅逐。目前,內建使用的此類污點包含如下幾個:

✏️ node.kubernetes.io/not-ready:節點進入“NotReady”狀態時被自動添加的污點。

✏️ node.alpha.kubernetes.io/unreachable:節點進入“NotReachable”狀態時被自動添加的污點。

✏️ node.kubernetes.io/out-of-disk:節點進入“OutOfDisk”狀態時被自動添加的污點。

✏️ node.kubernetes.io/memory-pressure:節點內存資源面臨壓力。

✏️ node.kubernetes.io/disk-pressure:節點磁盤資源面臨壓力。

✏️ node.kubernetes.io/network-unavailable:節點網絡不可用。

✏️ ​node.cloudprovider.kubernetes.io/uninitializedkubelet由外部的雲環境程序啟動時,它將自動為節點添加此污點,待到雲控制器管理器中的控制器初始化此節點時再將其刪除。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM