Kubernetes 調度均衡器 Descheduler 使用

從 kube-scheduler 的角度來看,它是通過一系列算法計算出最佳節點運行 Pod,當出現新的 Pod 進行調度時,調度程序會根據其當時對 Kubernetes 集羣的資源描述做出最佳調度決定,但是 Kubernetes 集羣是非常動態的,由於整個集羣範圍內的變化,比如一個節點爲了維護,我們先執行了驅逐操作,這個節點上的所有 Pod 會被驅逐到其他節點去,但是當我們維護完成後,之前的 Pod 並不會自動回到該節點上來,因爲 Pod 一旦被綁定了節點是不會觸發重新調度的,由於這些變化,Kubernetes 集羣在一段時間內就可能會出現不均衡的狀態,所以需要均衡器來重新平衡集羣。

當然我們可以去手動做一些集羣的平衡,比如手動去刪掉某些 Pod,觸發重新調度就可以了,但是顯然這是一個繁瑣的過程,也不是解決問題的方式。爲了解決實際運行中集羣資源無法充分利用或浪費的問題,可以使用 descheduler 組件對集羣的 Pod 進行調度優化,descheduler 可以根據一些規則和配置策略來幫助我們重新平衡集羣狀態,其核心原理是根據其策略配置找到可以被移除的 Pod 並驅逐它們,其本身並不會進行調度被驅逐的 Pod,而是依靠默認的調度器來實現,目前支持的策略有:

這些策略都是可以啓用或者禁用的,作爲策略的一部分,也可以配置與策略相關的一些參數,默認情況下,所有策略都是啓用的。另外,還有一些通用配置,如下:

我們可以通過如下所示的 DeschedulerPolicy 來配置:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
nodeSelector: prod=dev
evictLocalStoragePods: true
maxNoOfPodsToEvictPerNode: 40
ignorePvcPods: false
strategies:  # 配置策略
  ...

安裝

descheduler 可以以 JobCronJob 或者 Deployment 的形式運行在 k8s 集羣內,同樣我們可以使用 Helm Chart 來安裝 descheduler

➜ helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/

通過 Helm Chart 我們可以配置 deschedulerCronJob 或者 Deployment 方式運行,默認情況下 descheduler 會以一個 critical pod 運行,以避免被自己或者 kubelet 驅逐了,需要確保集羣中有 system-cluster-critical 這個 Priorityclass:

➜ kubectl get priorityclass system-cluster-critical
NAME                      VALUE        GLOBAL-DEFAULT   AGE
system-cluster-critical   2000000000   false            87d

使用 Helm Chart 安裝默認情況下會以 CronJob 的形式運行,執行週期爲 schedule: "*/2 * * * *",這樣每隔兩分鐘會執行一次 descheduler 任務,默認的配置策略如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      LowNodeUtilization:
        enabled: true
        params:
          nodeResourceUtilizationThresholds:
            targetThresholds:
              cpu: 50
              memory: 50
              pods: 50
            thresholds:
              cpu: 20
              memory: 20
              pods: 20
      RemoveDuplicates:
        enabled: true
      RemovePodsViolatingInterPodAntiAffinity:
        enabled: true
      RemovePodsViolatingNodeAffinity:
        enabled: true
        params:
          nodeAffinityType:
          - requiredDuringSchedulingIgnoredDuringExecution
      RemovePodsViolatingNodeTaints:
        enabled: true

通過配置 DeschedulerPolicystrategies,可以指定 descheduler 的執行策略,這些策略都是可以啓用或禁用的,下面我們會詳細介紹,這裏我們使用默認策略即可,使用如下命令直接安裝即可:

➜ helm upgrade --install descheduler descheduler/descheduler --set image.repository=cnych/descheduler,podSecurityPolicy.create=false -n kube-system
Release "descheduler" does not exist. Installing it now.
NAME: descheduler
LAST DEPLOYED: Fri Jan 21 10:35:55 2022
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
NOTES:
Descheduler installed as a cron job.

部署完成後會創建一個 CronJob 資源對象來平衡集羣狀態:

➜ kubectl get cronjob -n kube-system
NAME          SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
descheduler   */2 * * * *   False     1        27s             31s
➜ kubectl get job -n kube-system
NAME                   COMPLETIONS   DURATION   AGE
descheduler-27378876   1/1           72s        79s
➜ kubectl get pods -n kube-system -l job-name=descheduler-27378876
NAME                            READY   STATUS      RESTARTS   AGE
descheduler-27378876--1-btjmd   0/1     Completed   0          2m21s

正常情況下就會創建一個對應的 Job 來執行 descheduler 任務,我們可以通過查看日誌可以瞭解做了哪些平衡操作:

➜ kubectl logs -f descheduler-27378876--1-btjmd -n kube-system
I0121 02:37:10.127266       1 named_certificates.go:53] "Loaded SNI cert" index=0 cert\"apiserver-loopback-client@1642732630\" [serving] validServingFor=[apiserver-loopback-client] issuer=\"apiserver-loopback-client-ca@1642732629\" (2022-01-21 01:37:09 +0000 UTC to 2023-01-21 01:37:09 +0000 UTC (now=2022-01-21 02:37:10.127237 +0000 UTC))"
I0121 02:37:10.127324       1 secure_serving.go:195] Serving securely on [::]:10258
I0121 02:37:10.127363       1 tlsconfig.go:240] "Starting DynamicServingCertificateController"
I0121 02:37:10.138724       1 node.go:46] "Node lister returned empty list, now fetch directly"
I0121 02:37:10.172264       1 nodeutilization.go:167] "Node is overutilized" node="master1" usage=map[cpu:1225m memory:565Mi pods:16] usagePercentage=map[cpu:61.25 memory:15.391786081415567 pods:14.545454545454545]
I0121 02:37:10.172313       1 nodeutilization.go:164] "Node is underutilized" node="node1" usage=map[cpu:675m memory:735Mi pods:16] usagePercentage=map[cpu:16.875 memory:9.542007959787252 pods:14.545454545454545]
I0121 02:37:10.172328       1 nodeutilization.go:170] "Node is appropriately utilized" node="node2" usage=map[cpu:975m memory:1515Mi pods:15] usagePercentage=map[cpu:24.375 memory:19.66820054018583 pods:13.636363636363637]
I0121 02:37:10.172340       1 lownodeutilization.go:100] "Criteria for a node under utilization" CPU=20 Mem=20 Pods=20
I0121 02:37:10.172346       1 lownodeutilization.go:101] "Number of underutilized nodes" totalNumber=1
I0121 02:37:10.172355       1 lownodeutilization.go:114] "Criteria for a node above target utilization" CPU=50 Mem=50 Pods=50
I0121 02:37:10.172360       1 lownodeutilization.go:115] "Number of overutilized nodes" totalNumber=1
I0121 02:37:10.172374       1 nodeutilization.go:223] "Total capacity to be moved" CPU=1325 Mem=3267772416 Pods=39
I0121 02:37:10.172399       1 nodeutilization.go:226] "Evicting pods from node" node="master1" usage=map[cpu:1225m memory:565Mi pods:16]
I0121 02:37:10.172485       1 nodeutilization.go:229] "Pods on node" node="master1" allPods=16 nonRemovablePods=13 removablePods=3
I0121 02:37:10.172495       1 nodeutilization.go:236] "Evicting pods based on priority, if they have same priority, they'll be evicted based on QoS tiers"
I0121 02:37:10.180353       1 evictions.go:130] "Evicted pod" pod="default/topo-demo-6bbf65d967-lzlfh" reason="LowNodeUtilization"
I0121 02:37:10.181506       1 nodeutilization.go:269] "Evicted pods" pod="default/topo-demo-6bbf65d967-lzlfh" err=<nil>
I0121 02:37:10.181541       1 nodeutilization.go:294] "Updated node usage" node="master1" CPU=1225 Mem=592445440 Pods=15
I0121 02:37:10.182496       1 event.go:291] "Event occurred" object="default/topo-demo-6bbf65d967-lzlfh" kind="Pod" apiVersion="v1" type="Normal" reason="Descheduled" message="pod evicted by sigs.k8s.io/deschedulerLowNodeUtilization"
......

從日誌中我們就可以清晰的知道因爲什麼策略驅逐了哪些 Pods。

PDB

由於使用 descheduler 會將 Pod 驅逐進行重調度,但是如果一個服務的所有副本都被驅逐的話,則可能導致該服務不可用。如果服務本身存在單點故障,驅逐的時候肯定就會造成服務不可用了,這種情況我們強烈建議使用反親和性和多副本來避免單點故障,但是如果服務本身就被打散在多個節點上,這些 Pod 都被驅逐的話,這個時候也會造成服務不可用了,這種情況下我們可以通過配置 PDB(PodDisruptionBudget) 對象來避免所有副本同時被刪除,比如我們可以設置在驅逐的時候某應用最多隻有一個副本不可用,則創建如下所示的資源清單即可:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pdb-demo
spec:
  maxUnavailable: 1  # 設置最多不可用的副本數量,或者使用 minAvailable,可以使用整數或百分比
  selector:
    matchLabels:    # 匹配Pod標籤
      app: demo

關於 PDB 的更多詳細信息可以查看官方文檔:https://kubernetes.io/docs/tasks/run-application/configure-pdb/。

所以如果我們使用 descheduler 來重新平衡集羣狀態,那麼我們強烈建議給應用創建一個對應的 PodDisruptionBudget 對象進行保護。

策略

PodLifeTime

該策略用於驅逐比 maxPodLifeTimeSeconds 更舊的 Pods,可以通過 podStatusPhases 來配置哪類狀態的 Pods 會被驅逐,建議爲每個應用程序創建一個 PDB,以確保應用程序的可用性,比如我們可以配置如下所示的策略來驅逐運行超過 7 天的 Pod:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "PodLifeTime":
    enabled: true
    params:
      maxPodLifeTimeSeconds: 604800  # Pods 運行最多7天

RemoveDuplicates

該策略確保只有一個和 Pod 關聯的 RS、Deployment 或者 Job 資源對象運行在同一節點上。如果還有更多的 Pod 則將這些重複的 Pod 進行驅逐,以便更好地在集羣中分散 Pod。如果某些節點由於某些原因崩潰了,這些節點上的 Pod 漂移到了其他節點,導致多個與 RS 關聯的 Pod 在同一個節點上運行,就有可能發生這種情況,一旦出現故障的節點再次準備就緒,就可以啓用該策略來驅逐這些重複的 Pod。

配置策略的時候,可以指定參數 excludeOwnerKinds 用於排除類型,這些類型下的 Pod 不會被驅逐:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemoveDuplicates":
     enabled: true
     params:
       removeDuplicates:
         excludeOwnerKinds:
         - "ReplicaSet"

LowNodeUtilization

該策略主要用於查找未充分利用的節點,並從其他節點驅逐 Pod,以便 kube-scheudler 重新將它們調度到未充分利用的節點上。該策略的參數可以通過字段 nodeResourceUtilizationThresholds 進行配置。

節點的利用率不足可以通過配置 thresholds 閾值參數來確定,可以通過 CPU、內存和 Pods 數量的百分比進行配置。如果節點的使用率均低於所有閾值,則認爲該節點未充分利用。

此外,還有一個可配置的閾值 targetThresholds,用於計算可能驅逐 Pods 的潛在節點,該參數也可以配置 CPU、內存以及 Pods 數量的百分比進行配置。thresholdstargetThresholds 可以根據你的集羣需求進行動態調整,如下所示示例:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "LowNodeUtilization":
     enabled: true
     params:
       nodeResourceUtilizationThresholds:
         thresholds:
           "cpu" : 20
           "memory"20
           "pods"20
         targetThresholds:
           "cpu" : 50
           "memory"50
           "pods"50

需要注意的是:

如果未指定任何資源類型,則默認是 100%,以避免節點從未充分利用變爲過度利用。和 LowNodeUtilization 策略關聯的另一個參數是 numberOfNodes,只有當未充分利用的節點數大於該配置值的時候,纔可以配置該參數來激活該策略,該參數對於大型集羣非常有用,其中有一些節點可能會頻繁使用或短期使用不足,默認情況下,numberOfNodes 爲 0。

RemovePodsViolatingInterPodAntiAffinity

該策略可以確保從節點中刪除違反 Pod 反親和性的 Pod,比如某個節點上有 podA 這個 Pod,並且 podB 和 podC(在同一個節點上運行)具有禁止它們在同一個節點上運行的反親和性規則,則 podA 將被從該節點上驅逐,以便 podB 和 podC 運行正常運行。當 podB 和 podC 已經運行在節點上後,反親和性規則被創建就會發送這樣的問題。

要禁用該策略,直接配置成 false 即可:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsViolatingInterPodAntiAffinity":
     enabled: false

RemovePodsViolatingNodeTaints

該策略可以確保從節點中刪除違反 NoSchedule 污點的 Pod,比如有一個名爲 podA 的 Pod,通過配置容忍 key=value:NoSchedule 允許被調度到有該污點配置的節點上,如果節點的污點隨後被更新或者刪除了,則污點將不再被 Pods 的容忍滿足,然後將被驅逐:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsViolatingNodeTaints":
    enabled: true

RemovePodsViolatingNodeAffinity

該策略確保從節點中刪除違反節點親和性的 Pod。比如名爲 podA 的 Pod 被調度到了節點 nodeA,podA 在調度的時候滿足了節點親和性規則 requiredDuringSchedulingIgnoredDuringExecution,但是隨着時間的推移,節點 nodeA 不再滿足該規則了,那麼如果另一個滿足節點親和性規則的節點 nodeB 可用,則 podA 將被從節點 nodeA 驅逐,如下所示的策略配置示例:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsViolatingNodeAffinity":
    enabled: true
    params:
      nodeAffinityType:
      - "requiredDuringSchedulingIgnoredDuringExecution"

RemovePodsViolatingTopologySpreadConstraint

該策略確保從節點驅逐違反拓撲分佈約束的 Pods,具體來說,它試圖驅逐將拓撲域平衡到每個約束的 maxSkew 內所需的最小 Pod 數,不過該策略需要 k8s 版本高於 1.18 才能使用。

默認情況下,此策略僅處理硬約束,如果將參數 includeSoftConstraints 設置爲 True,也將支持軟約束。

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsViolatingTopologySpreadConstraint":
     enabled: true
     params:
       includeSoftConstraints: false

RemovePodsHavingTooManyRestarts

該策略確保從節點中刪除重啓次數過多的 Pods,它的參數包括 podRestartThreshold(這是應將 Pod 逐出的重新啓動次數),以及包括InitContainers,它確定在計算中是否應考慮初始化容器的重新啓動,策略配置如下所示:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsHavingTooManyRestarts":
     enabled: true
     params:
       podsHavingTooManyRestarts:
         podRestartThreshold: 100
         includingInitContainers: true

過濾 Pods

在驅逐 Pods 的時候,有時並不需要所有 Pods 都被驅逐,descheduler 提供了兩種主要的方式進行過濾:命名空間過濾和優先級過濾。

命名空間過濾

該策略可以配置是包含還是排除某些名稱空間。可以使用該策略的有:

比如只驅逐某些命令空間下的 Pods,則可以使用 include 參數進行配置,如下所示:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "PodLifeTime":
     enabled: true
     params:
        podLifeTime:
          maxPodLifeTimeSeconds: 86400
        namespaces:
          include:
          - "namespace1"
          - "namespace2"

又或者要排除掉某些命令空間下的 Pods,則可以使用 exclude 參數配置,如下所示:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "PodLifeTime":
     enabled: true
     params:
        podLifeTime:
          maxPodLifeTimeSeconds: 86400
        namespaces:
          exclude:
          - "namespace1"
          - "namespace2"

優先級過濾

所有策略都可以配置優先級閾值,只有在該閾值以下的 Pod 纔會被驅逐,我們可以通過設置 thresholdPriorityClassName(將閾值設置爲指定優先級類別的值)或 thresholdPriority(直接設置閾值)參數來指定該閾值。默認情況下,該閾值設置爲 system-cluster-critical 這個 PriorityClass 類的值。

比如使用 thresholdPriority

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "PodLifeTime":
     enabled: true
     params:
        podLifeTime:
          maxPodLifeTimeSeconds: 86400
        thresholdPriority: 10000

或者使用 thresholdPriorityClassName 進行過濾:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "PodLifeTime":
     enabled: true
     params:
        podLifeTime:
          maxPodLifeTimeSeconds: 86400
        thresholdPriorityClassName: "priorityclass1"

不過需要注意不能同時配置 thresholdPrioritythresholdPriorityClassName,如果指定的優先級類不存在,則 descheduler 不會創建它,並且會引發錯誤。

注意事項

當使用 descheduler 驅除 Pods 的時候,需要注意以下幾點:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/92gUxuTLXMJZA7F4n3FjTA