Kubernetes 調度均衡器 Descheduler 使用
從 kube-scheduler 的角度來看,它是通過一系列算法計算出最佳節點運行 Pod,當出現新的 Pod 進行調度時,調度程序會根據其當時對 Kubernetes 集羣的資源描述做出最佳調度決定,但是 Kubernetes 集羣是非常動態的,由於整個集羣範圍內的變化,比如一個節點爲了維護,我們先執行了驅逐操作,這個節點上的所有 Pod 會被驅逐到其他節點去,但是當我們維護完成後,之前的 Pod 並不會自動回到該節點上來,因爲 Pod 一旦被綁定了節點是不會觸發重新調度的,由於這些變化,Kubernetes 集羣在一段時間內就可能會出現不均衡的狀態,所以需要均衡器來重新平衡集羣。
當然我們可以去手動做一些集羣的平衡,比如手動去刪掉某些 Pod,觸發重新調度就可以了,但是顯然這是一個繁瑣的過程,也不是解決問題的方式。爲了解決實際運行中集羣資源無法充分利用或浪費的問題,可以使用 descheduler 組件對集羣的 Pod 進行調度優化,descheduler
可以根據一些規則和配置策略來幫助我們重新平衡集羣狀態,其核心原理是根據其策略配置找到可以被移除的 Pod 並驅逐它們,其本身並不會進行調度被驅逐的 Pod,而是依靠默認的調度器來實現,目前支持的策略有:
-
RemoveDuplicates
-
LowNodeUtilization
-
RemovePodsViolatingInterPodAntiAffinity
-
RemovePodsViolatingNodeAffinity
-
RemovePodsViolatingNodeTaints
-
RemovePodsViolatingTopologySpreadConstraint
-
RemovePodsHavingTooManyRestarts
-
PodLifeTime
這些策略都是可以啓用或者禁用的,作爲策略的一部分,也可以配置與策略相關的一些參數,默認情況下,所有策略都是啓用的。另外,還有一些通用配置,如下:
-
nodeSelector
:限制要處理的節點 -
evictLocalStoragePods
: 驅逐使用 LocalStorage 的 Pods -
ignorePvcPods
: 是否忽略配置 PVC 的 Pods,默認是 False -
maxNoOfPodsToEvictPerNode
:節點允許的最大驅逐 Pods 數
我們可以通過如下所示的 DeschedulerPolicy
來配置:
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
nodeSelector: prod=dev
evictLocalStoragePods: true
maxNoOfPodsToEvictPerNode: 40
ignorePvcPods: false
strategies: # 配置策略
...
安裝
descheduler
可以以 Job
、CronJob
或者 Deployment
的形式運行在 k8s 集羣內,同樣我們可以使用 Helm Chart 來安裝 descheduler
:
➜ helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
通過 Helm Chart 我們可以配置 descheduler
以 CronJob
或者 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
通過配置 DeschedulerPolicy
的 strategies
,可以指定 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 數量的百分比進行配置。thresholds
和 targetThresholds
可以根據你的集羣需求進行動態調整,如下所示示例:
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
需要注意的是:
-
僅支持以下三種資源類型:cpu、memory、pods
-
thresholds
和targetThresholds
必須配置相同的類型 -
參數值的訪問是 0-100(百分制)
-
相同的資源類型,
thresholds
的配置不能高於targetThresholds
的配置
如果未指定任何資源類型,則默認是 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
提供了兩種主要的方式進行過濾:命名空間過濾和優先級過濾。
命名空間過濾
該策略可以配置是包含還是排除某些名稱空間。可以使用該策略的有:
-
PodLifeTime
-
RemovePodsHavingTooManyRestarts
-
RemovePodsViolatingNodeTaints
-
RemovePodsViolatingNodeAffinity
-
RemovePodsViolatingInterPodAntiAffinity
-
RemoveDuplicates
-
RemovePodsViolatingTopologySpreadConstraint
比如只驅逐某些命令空間下的 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"
不過需要注意不能同時配置 thresholdPriority
和 thresholdPriorityClassName
,如果指定的優先級類不存在,則 descheduler 不會創建它,並且會引發錯誤。
注意事項
當使用 descheduler 驅除 Pods 的時候,需要注意以下幾點:
-
關鍵性 Pod 不會被驅逐,比如
priorityClassName
設置爲system-cluster-critical
或system-node-critical
的 Pod -
不屬於 RS、Deployment 或 Job 管理的 Pods 不會被驅逐
-
DaemonSet 創建的 Pods 不會被驅逐
-
使用
LocalStorage
的 Pod 不會被驅逐,除非設置evictLocalStoragePods: true
-
具有 PVC 的 Pods 不會被驅逐,除非設置
ignorePvcPods: true
-
在
LowNodeUtilization
和RemovePodsViolatingInterPodAntiAffinity
策略下,Pods 按優先級從低到高進行驅逐,如果優先級相同,Besteffort
類型的 Pod 要先於Burstable
和Guaranteed
類型被驅逐 -
annotations
中帶有descheduler.alpha.kubernetes.io/evict
字段的 Pod 都可以被驅逐,該註釋用於覆蓋阻止驅逐的檢查,用戶可以選擇驅逐哪個 Pods -
如果 Pods 驅逐失敗,可以設置
--v=4
從descheduler
日誌中查找原因,如果驅逐違反 PDB 約束,則不會驅逐這類 Pods
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/92gUxuTLXMJZA7F4n3FjTA