使用 ?Koordinator 實現資源負載感知(重)調度
Koordinator 是一個基于 QoS 的 Kubernetes 混合工作負載調度系統。它旨在提高對延遲敏感的工作負載和批處理作業的運行時效率和可靠性,簡化與資源相關的配置調整的復雜性,并增加 Pod 部署密度以提高資源利用率。
混部:在同一套硬件資源上,同時運行多個不同類型的工作負載,例如:批處理作業、交互式任務、實時數據處理等。
Koordinator 是具有高性能、可擴展的, 在大規模生產環境中得到了驗證的,可以構建支持企業生產環境的容器編排系統。
Koordinator 通過提供以下功能增強了在 Kubernetes 中管理工作負載的用戶體驗:
- 精心設計的優先級和 QoS 機制,可將不同類型的工作負載混跑在集群中,并在單個節點上運行不同類型的 Pod 。
- 允許資源超賣以實現高資源利用率,但仍通過利用應用程序分析機制來滿足 QoS 保證。
- 細粒度的資源協調和隔離機制,以提高延遲敏感的工作負載和批處理作業的效率。
- 靈活的作業調度機制,支持特定領域的工作負載,例如大數據、人工智能、音頻和視頻。
- 一整套用于監控、故障排除和操作的工具。
Koordinator QoS vs Kubernetes QoS
Kubernetes 提供三種類型的 QoS:Guaranteed/Burstable/BestEffort,其中 Guaranteed/Burstable 被廣泛使用 BestEffort 很少使用。Koordinator 與 Kubernetes QoS 兼容,并且對每種類型都有許多增強功能。為了避免干擾原生 QoS 語義,Koordinator 引入了一個獨立的字段 koordinator.sh/qosClass 來描述混部 QoS。該 QoS 描述了在混部場景中節點上運行的 Pod 的服務質量。它是混合系統最關鍵的語義。
Koordinator 與 Kubernetes QoS 兼容,并且對每種類型都有許多增強功能。
Koordinator scheduler vs kube-scheduler
Koordinator 調度器并非旨在取代 kube-scheduler,而是為了讓混部的工作負載在 kubernetes 上運行得更好。
Koordinator 調度器是基于 schedule-framework 開發的,在原生調度能力之上增加了與混部和優先級搶占相關的調度插件。Koordinator 將致力于推動相關的增強進入 Kubernetes 的上游社區,推動混部技術的標準化。
架構
Koordinator 由兩個控制面(Koordinator Scheduler/Koordinator Manager)和一個 DaemonSet 組件(Koordlet)組成。Koordinator 在 Kubernetes 原有的能力基礎上增加了混部功能,并兼容了 Kubernetes 原有的工作負載。
組件
下面是 Koordinator 的核心相關組件:
Koord-Scheduler
Koord-Scheduler 以 Deployment 的形式部署在集群中,用于增強 Kubernetes 在 QoS-aware,差異化 SLO 以及任務調度場景的資源調度能力,具體包括:
- QoS-aware 調度,包括負載感知調度讓節點間負載更佳平衡,資源超賣的方式支持運行更多的低優先級工作負載。
- 差異化 SLO,包括 CPU 精細化編排,為不同的工作負載提供不同的 QoS 隔離策略(cfs、LLC、memory 帶寬、網絡帶寬、磁盤 io)。
- 任務調度,包括彈性額度管理,Gang 調度,異構資源調度等,以支持更好的運行大數據和 AI 工作負載。
Gang Scheduling:將一組具有相同調度需求的 Pod 視為一個調度單元,一起調度,一起遷移,一起銷毀。在 AI 場景中很多任務都需要使用 Gang scheduling,社區已經有很多相關實現,比如 Coscheduling、Vocalno。
為了更好的支持不同類型的工作負載,Koord-scheduler 還包括了一些通用性的能力增強:
- Reservation,支持為特定的 Pod 或者工作負載預留節點資源。資源預留特性廣泛應用于重調度,資源搶占以及節點碎片整理等相關優化過程。
- Node Reservation,支持為 kubernetes 之外的工作負載預留節點資源,一般應用于節點上運行著非容器化的負載場景。
Koord-Decheduler
Koord-Decheduler 以 Deployment 的形式部署在集群中,它是 kubernetes 上游社區的增強版本,當前包含:
- 重調度框架, Koord-Decheduler 重新設計了全新重調度框架,在可擴展性、資源確定性以及安全性上增加了諸多的加強,更多的細節.
- 負載感知重調度,基于新框架實現的一個負載感知重調度插件,支持用戶配置節點的安全水位,以驅動重調度器持續優化集群編排,從而規避集群中出現局部節點熱點.
水位:節點上資源使用的一個閾值,當資源使用達到水位時,會觸發重調度。
重調度:當節點資源不足時,將節點上的 Pod 調度到其他節點上。
Koord-Manager
Koord-Manager 以 Deployment 的形式部署,通常由兩個實例組成,一個 leader 實例和一個 backup 實例。Koordinator Manager 由幾個控制器和 webhooks 組成,用于協調混部場景下的工作負載,資源超賣(resource overcommitment)和 SLO 管理。
目前,提供了三個組件:
- Colocation Profile,用于支持混部而不需要修改工作負載。用戶只需要在集群中做少量的配置,原來的工作負載就可以在混部模式下運行。
- SLO 控制器,用于資源超賣(resource overcommitment)管理,根據節點混部時的運行狀態,動態調整集群的超發(overcommit)配置比例。該控制器的核心職責是管理混部時的 SLO,如智能識別出集群中的異常節點并降低其權重,動態調整混部時的水位和壓力策略,從而保證集群中 pod 的穩定性和吞吐量。
- Recommender(即將推出),它使用 histograms 來統計和預測工作負載的資源使用細節,用來預估工作負載的峰值資源需求,從而支持更好地分散熱點,提高混部的效率。此外,資源 profiling 還將用于簡化用戶資源規范化配置的復雜性,如支持 VPA。
Koordlet
Koordlet 以 DaemonSet 的形式部署在 Kubernetes 集群中,用于支持混部場景下的資源超賣(resource overcommitment)、干擾檢測、QoS 保證等。
在 Koordlet 內部,它主要包括以下模塊:
- 資源 Profiling,估算 Pod 資源的實際使用情況,回收已分配但未使用的資源,用于低優先級 Pod 的 overcommit。
- 資源隔離,為不同類型的 Pod 設置資源隔離參數,避免低優先級的 Pod 影響高優先級 Pod 的穩定性和性能。
- 干擾檢測,對于運行中的 Pod,動態檢測資源爭奪,包括 CPU 調度、內存分配延遲、網絡、磁盤 IO 延遲等。
- QoS 管理器,根據資源剖析、干擾檢測結果和 SLO 配置,動態調整混部節點的水位,抑制影響服務質量的 Pod。
- 資源調優,針對混部場景進行容器資源調優,優化容器的 CPU Throttle、OOM 等,提高服務運行質量。
Koord-RuntimeProxy
Koord-RuntimeProxy 以 systemd service 的形式部署在 Kubernetes 集群的節點上,用于代理 Kubelet 與 containerd/docker 之間的 CRI 請求。這一個代理被設計來支持精細化的資源管理策略,比如為不同 QoS Pod 設置不同的 cgroup 參數,包括內核 cfs quota,resctl 等等技術特性,以改進 Pod 的運行時質量。
資源模型
混部是一套資源調度解決方案,用于對延遲敏感的工作負載與大數據計算工作負載進行精細化編排。它需要解決兩個主要問題:
- 如何為延遲敏感的工作負載調度資源,以滿足性能和長尾延遲的要求。這里涉及到的關鍵點是資源調度策略和 QoS 感知策略。
- 如何調度和編排大數據計算工作負載,以較低的成本滿足任務對計算資源的需求。這里涉及到的關鍵是如何在極端異常情況下實現合理的資源超額配置和 QoS 保障。
定義
Resource Model
上圖是 Koordinator 的混部資源模型,其基本思想是利用那些已分配但未使用的資源來運行低優先級的 pod。如圖所示,有四條線:
- limit:灰色,高優先級 Pod 所請求的資源量,對應于 Kubernetes 的 Pod 請求。
- usage:紅色,Pod 實際使用的資源量,橫軸為時間線,紅線為 Pod 負載隨時間變化的波動曲線。
- short-term reservation:深藍色,這是基于過去(較短)時期內的資源使用量,對未來一段時間內其資源使用量的估計。預留和限制的區別在于,分配的未使用(未來不會使用的資源)可以用來運行短期執行的批處理 Pod。
- long-term reservation:淺藍色,與 short-term reservation 類似,但估計的歷史使用期更長。從保留到限制的資源可以用于生命周期較長的 Pod,與短期的預測值相比,可用的資源較少,但更穩定。
整個混部資源調度是基于上圖所示的資源模型構建的,不僅可以滿足各種工作負載的資源需求,還可以充分利用集群的閑置資源。
SLO 描述
在集群中運行的 Pod 資源 SLO 由兩個概念組成,即優先級和 QoS。
- 優先級,即資源的優先級,代表了請求資源被調度的優先級。通常情況下,優先級會影響 Pod 在調度器待定隊列中的相對位置。
- QoS,代表 Pod 運行時的服務質量。如 cgroups cpu share、cfs 配額、LLC、內存、OOM 優先級等等。
需要注意的是,Priority 和 QoS 是兩個維度的概念,但在實際業務場景中,兩者之間會有一些約束(不是所有的組合都是合法的)。
優先級
Koordinator 在 Kubernetes 優先級類型的基礎上定義了一套規范,并擴展了優先級的一個維度以對混部場景的細粒度支持。
優先級用數字表示,目前定義了四個類:
PriorityClass 目前留有一些暫未使用的區間,以支持未來可能的擴展。
Koordinator 將不同類型的工作負載匹配到不同的優先級:
- koord-prod,運行典型的延遲敏感型服務,一般是指需要 "實時 "響應的服務類型,比如通過點擊移動 APP 中的按鈕調用的典型服務。
- koord-mid,對應于長周期的可用資源,一般用于運行一些實時計算、人工智能訓練任務/作業,如 tensorflow/pytorch 等。
- koord-batch,對應于的短周期可用資源,運行典型的離線批處理作業,一般指離線分析類作業,如日級大數據報告、非交互式 SQL 查詢。
- koord-free,運行低優先級的離線批處理作業,一般指不做資源預算,利用閑置資源盡量完成,如開發人員為測試目提交的作業。
Koordinator 在 Kubernetes 集群中部署時會初始化這四個 PriorityClass:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: koord-prod
value: 9000
description: "This priority class should be used for prod service pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: koord-mid
value: 7000
description: "This priority class should be used for mid service pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: koord-batch
value: 5000
description: "This priority class should be used for batch service pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: koord-free
value: 3000
description: "This priority class should be used for free service pods only."
在每個 PriorityClass 內,Koordinator 允許用戶為精細化資源調度設置混部 Pod 的優先級。
比如下面的 YAML 是一個 Pod 配置的例子,它使用了前面例子中創建的 PriorityClass 和優先級。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
koordinator.sh/priority: "5300"
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: koord-batch
Qos
QoS 用于表達節點上 Pod 的運行質量,如獲取資源的方式、獲取資源的比例、QoS 保障策略等。
Koordinator 調度系統支持的 QoS 有五種類型:
QoS CPU 編排隔離與共享
Koordinator QoS 與 Kubernetes QoS 的對比
從上面可以看出,Koordinator 的 QoS 比 Kubernetes 的 QoS 更復雜,因為在混部場景下,我們需要對延遲敏感的工作負載的 QoS 進行微調,以滿足混部時性能的需求。Koordinator 和 Kubernetes QoS 之間是有對應關系的:
Koordlet 根據 Pod 的優先級和 QoS 定義,觸發相應的資源隔離和 QoS 保障。
安裝
Koordinator 依賴 1.18 及以上版本的 Kubernetes。另外 Koordinator 可能會從 kubelet 只讀端口收集指標(默認設置為禁用,即采用了安全端口)。
為了最好的體驗,koordinator 推薦 linux kernel 4.19 或者更高版本。
這里我們推薦使用 Helm 安裝:
helm repo add koordinator-sh https://koordinator-sh.github.io/charts/
helm repo update
helm install koordinator koordinator-sh/koordinator --version 1.5.0 --set imageRepositoryHost=registry.cn-beijing.aliyuncs.com --set manager.hostNetwork=true
上面的命令默認會安裝在 koordinator-system namespace 下,如果需要安裝在其他 namespace 下,可以使用 installation.namespace 參數,我們可以使用 kubectl get pods -n koordinator-system 命令查看安裝的組件。
$ kubectl get pods -n koordinator-system
NAME READY STATUS RESTARTS AGE
koord-descheduler-7569579bc-knr85 1/1 Running 0 7m8s
koord-manager-8897597b6-79wck 1/1 Running 0 7m8s
koord-scheduler-78bccfc65c-wt2n6 1/1 Running 0 7m8s
koordlet-972kj 1/1 Running 0 7m8s
koordlet-kmtqh 1/1 Running 0 7m8s
koordlet-s9c4s 1/1 Running 0 7m8s
使用
現在我們就可以使用 Koordinator 來調度我們的工作負載了。
負載感知調度
負載感知調度(Load Aware Scheduling)是 koord-scheduler 提供的一種調度能力,調度 Pod 時根據節點的負載情況選擇合適的節點,均衡節點間的負載情況。
負載均衡是資源調度中的常見問題。資源未充分利用的節點會帶來很大的資源浪費,而過度使用的節點可能會導致性能下降。這些問題都不能高效的管理和使用資源。原生 Kubernetes Scheduler 根據 Requests 和節點可分配總量來調度 Pod,既不考慮實時負載,也不估計使用量。當我們期望使用原生調度器均勻的打散 Pod 并保持節點間的負載均衡,我們需要為應用程序設置精確的資源規格。此外,當 Koordinator 通過超賣機制提升資源使用效率時,我們需要一種機制盡量避免性能回退,并避免負載過高的問題。
Koordinator 調度插件過濾異常節點并根據資源使用情況對其進行評分。這個調度插件擴展了 Kubernetes 調度框架中定義的 Filter/Score/Reserve/Unreserve 擴展點。
過濾不健康的節點
默認過濾異常節點,但是用戶可以根據需要通過配置來決定是否開啟。
- 過濾 Koordlet 無法更新 NodeMetric 的節點。如果配置啟用,插件將排除 nodeMetrics.status.updateTime >= LoadAwareSchedulingArgs.nodeMetricExpirationSeconds 的節點。
- 按利用率閾值過濾節點。如果配置啟用,插件將排除 latestUsageUtilization >= 利用率閾值的節點。在過濾階段,僅從最新的 NodeMetric 中獲取資源利用率,已分配但尚未統計的 Pod 的資源利用率不參與計算,以便為新創建的 Pod 分配資源,避免因估算不合理而導致調度失敗。
評分算法
評分算法的核心邏輯是選擇資源使用量最小的節點。但是考慮到資源使用上報的延遲和 Pod 啟動時間的延遲,時間窗口內已經調度的 Pod 和當前正在調度的 Pod 的資源請求也會被估算出來,并且估算值將參與計算。
全局配置
對于需要深入定制的用戶,可以通過修改 Helm Chart 中的 ConfigMap koord-scheduler-config
規則來配置負載感知調度。
apiVersion: v1
kind: ConfigMap
metadata:
name: koord-scheduler-config
...
data:
koord-scheduler-config: |
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: koord-scheduler
plugins:
filter: # 過濾
enabled:
- name: LoadAwareScheduling # 啟用負載感知調度插件
...
score: # 打分
enabled:
- name: LoadAwareScheduling
weight: 1 # 打分權重
...
reserve: # 預留資源
enabled:
- name: LoadAwareScheduling
...
pluginConfig: # 配置閾值和權重
- name: LoadAwareScheduling
args:
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: LoadAwareSchedulingArgs
filterExpiredNodeMetrics: true # 是否過濾掉無法更新 NodeMetric 的節點
nodeMetricExpirationSeconds: 300 # 使用 NodeMetric 的過期時間
# 資源權重
resourceWeights:
cpu: 1
memory: 1
usageThresholds: # 資源利用率閾值
cpu: 75
memory: 85
prodUsageThresholds: # prod資源利用率閾值
cpu: 55
memory: 65
scoreAccordingProdUsage: true # 是否根據prod資源利用率閾值進行打分
estimatedScalingFactors: # 資源利用率估算因子
cpu: 80
memory: 70
aggregated: # 資源利用率百分比統計
usageThresholds:
cpu: 65
memory: 75
usageAggregationType: "p99"
scoreAggregationType: "p99"
可配置的參數如下所示:
字段 | 說明 | 版本 |
filterExpiredNodeMetrics | filterExpiredNodeMetrics 表示是否過濾 koordlet 更新 NodeMetric 失敗的節點。默認情況下啟用,但在 Helm chart 中,它被禁用。 | >= v0.4.0 |
nodeMetricExpirationSeconds | nodeMetricExpirationSeconds 指示 NodeMetric 過期時間(以秒為單位)。當 NodeMetrics 過期時,節點被認為是異常的。默認為 180 秒。 | >= v0.4.0 |
resourceWeights | resourceWeights 表示資源的權重。CPU 和 Memory 的權重默認都是 1。 | >= v0.4.0 |
usageThresholds | usageThresholds 表示整機的資源利用率閾值。CPU 的默認值為 65%,內存的默認值為 95%。 | >= v0.4.0 |
estimatedScalingFactors | estimatedScalingFactors 表示估計資源使用時的因子。CPU 默認值為 85%,Memory 默認值為 70%。 | >= v0.4.0 |
prodUsageThresholds | prodUsageThresholds 表示 Prod Pod 相對于整機的資源利用率閾值。默認情況下不啟用。 | >= v1.1.0 |
scoreAccordingProdUsage | scoreAccordingProdUsage 控制是否根據 Prod Pod 的利用率進行評分。 | >= v1.1.0 |
aggregated | aggregated 支持基于百分位數統計的資源利用率過濾和評分。 | >= v1.1.0 |
Aggregated 支持的字段:
字段 | 說明 | 版本 |
usageThresholds | usageThresholds 表示機器基于百分位統計的資源利用率閾值。 | >= v1.1.0 |
usageAggregationType | usageAggregationType 表示過濾時機器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
usageAggregatedDuration | usageAggregatedDuration 表示過濾時機器利用率百分位數的統計周期。不設置該字段時,調度器默認使用 NodeMetrics 中最大周期的數據。 | >= v1.1.0 |
scoreAggregationType | scoreAggregationType 表示評分時機器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
scoreAggregatedDuration | scoreAggregatedDuration 表示打分時 Prod Pod 利用率百分位的統計周期。不設置該字段時,調度器默認使用 NodeMetrics 中最大周期的數據。 | >= v1.1.0 |
通過插件的配置可以作為集群默認的全局配置,當然用戶也可以通過在節點上附加 annotation 來設置節點維度的負載閾值。當節點上存在 annotation 時,會根據注解指定的參數進行過濾。
測試
我們這里的集群一共三個節點:
- master:2 核 4G
- node1:4 核 8G
- node2:4 核 8G
下面我們創建一個如下所示的 Deployment 來測試負載感知調度:
# pod-stress.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: stress-demo
namespace: default
labels:
app: stress-demo
spec:
selector:
matchLabels:
app: stress-demo
template:
metadata:
name: stress-demo
labels:
app: stress-demo
spec:
containers:
- args:
- "--vm"
- "2"
- "--vm-bytes"
- "1600M"
- "-c"
- "3"
- "--vm-hang"
- "3"
command:
- stress
image: jockerhub.com/polinux/stress
imagePullPolicy: Always
name: stress
schedulerName: koord-scheduler # use the koord-scheduler
直接創建上面的 Deployment 即可:
$ kubectl apply -f pod-stress.yaml
deployment.apps/stress-demo created
創建完成后觀察 Pod 的狀態:
$ kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
stress-demo-64968d6446-8wdtr 1/1 Running 0 89s 10.0.1.5 node2 <none> <none>
可以看到 Pod 已經調度到了 node2 節點上。
現在我們可以檢查下每個節點的負載情況:
$ kubectl top pods stress-demo-64968d6446-8wdtr
NAME CPU(cores) MEMORY(bytes)
stress-demo-64968d6446-8wdtr 2979m 3206Mi
$ kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master 92m 4% 2682Mi 71%
node1 54m 1% 1761Mi 22%
node2 1711m 42% 4381Mi 56%
從上面輸出結果顯示,節點 node1 的負載最低(master 不考慮),node2 的負載最高。
接下來我們再部署幾個 Pod 來測試下效果:
# nginx-with-loadaware.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-with-loadaware
labels:
app: nginx
spec:
replicas: 6
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
schedulerName: koord-scheduler # use the koord-scheduler
containers:
- name: nginx
image: jockerhub.com/library/nginx
resources:
limits:
cpu: 100m
requests:
cpu: 100m
同樣直接創建上面的 Deployment:
$ kubectl apply -f nginx-with-loadaware.yaml
deployment.apps/nginx-with-loadaware created
創建完成后檢查 nginx 這些 Pods 的調度結果:
$ kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-with-loadaware-57db6b7758-6gbfz 1/1 Running 0 78s 10.0.2.187 node1 <none> <none>
nginx-with-loadaware-57db6b7758-gvxdk 1/1 Running 0 78s 10.0.2.243 node1 <none> <none>
nginx-with-loadaware-57db6b7758-hzdqf 1/1 Running 0 77s 10.0.2.200 node1 <none> <none>
nginx-with-loadaware-57db6b7758-n779g 1/1 Running 0 77s 10.0.2.69 node1 <none> <none>
nginx-with-loadaware-57db6b7758-trt64 1/1 Running 0 78s 10.0.1.156 node1 <none> <none>
nginx-with-loadaware-57db6b7758-xtvsh 1/1 Running 0 77s 10.0.1.75 node2 <none> <none>
我們可以看到 nginx pods 絕大部分都被調度在 node2 (負載最高的節點) 以外的節點上。
感知 Prod Pods 的負載進行調度
如果一個 Node 中調度了很多 BestEffort Pod,可能會因為節點的負載已達到使用限制而導致延遲敏感的 Pod 無法調度。在 Koordinator v1.1.0 中,負載感知調度針對這種場景進行了優化。對于延遲敏感(LSE/LSR/LS)的 Pod,優先調度到 Prod Pod 總利用率較低的節點,而 BestEffort(BE) Pod 根據整機利用率水平進行調度。
通過設置以下參數啟用相關優化:
字段 | 說明 | 版本 |
prodUsageThresholds | prodUsageThresholds 表示 Prod Pod 相對于整機的資源利用率閾值。默認情況下不啟用。 | >= v1.1.0 |
scoreAccordingProdUsage | scoreAccordingProdUsage 控制是否根據 Prod Pod 的利用率進行評分。 | >= v1.1.0 |
感知基于百分位數統計的利用率進行調度
Koordinator v1.0 及以前的版本都是按照 koordlet 上報的平均利用率數據進行過濾和打分。但平均值隱藏了比較多的信息,因此在 Koordinator v1.1 中 koordlet 新增了根據百分位數統計的利用率聚合數據。調度器側也跟著做了相應的適配。
通過設置以下參數啟用相關優化:
字段 | 說明 | 版本 |
aggregated | aggregated 支持基于百分位數統計的資源利用率過濾和評分。 | >= v1.1.0 |
Aggregated 支持的字段:
字段 | 說明 | 版本 |
usageThresholds | usageThresholds 表示機器基于百分位統計的資源利用率閾值。 | >= v1.1.0 |
usageAggregationType | usageAggregationType 表示過濾時機器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
usageAggregatedDuration | usageAggregatedDuration 表示過濾時機器利用率百分位數的統計周期。不設置該字段時,調度器默認使用 NodeMetrics 中最大周期的數據。 | >= v1.1.0 |
scoreAggregationType | scoreAggregationType 表示評分時機器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
scoreAggregatedDuration | scoreAggregatedDuration 表示打分時 Prod Pod 利用率百分位的統計周期。不設置該字段時,調度器默認使用 NodeMetrics 中最大周期的數據。 | >= v1.1.0 |
aggregated 和 usageThresholds 參數是互斥的。當兩者都配置時,將使用 aggregated。
負載感知重調度
調度器中支持的負載感知調度能夠在調度時選擇負載較低的節點運行新的 Pod,但隨著時間、集群環境變化以及工作負載面對的流量/請求的變化時,節點的利用率會動態的發生變化,集群內節點間原本負載均衡的情況被打破,甚至有可能出現極端負載不均衡的情況,影響到工作負載運行時質量。
koord-descheduler 感知集群內節點負載的變化,自動的優化超過負載水位安全閾值的節點,防止出現極端負載不均衡的情況。
koord-descheduler 組件中 LowNodeLoad 插件負責感知負載水位完成熱點打散重調度工作。LowNodeLoad 插件 與 Kubernetes 原生的 descheduler 的插件 LowNodeUtilization 不同的是,LowNodeLoad 是根據節點真實利用率的情況決策重調度,而 LowNodeUtilization 是根據資源分配率決策重調度。
LowNodeLoad 插件有兩個最重要的參數:
- highThresholds 表示負載水位的目標安全閾值,超過該閾值的節點上的 Pod 將參與重調度;
- lowThresholds 表示負載水位的空閑安全水位。低于該閾值的節點上的 Pod 不會被重調度。
以下圖為例,lowThresholds 為 45%,highThresholds 為 70%,我們可以把節點歸為三類:
- 空閑節點(Idle Node)。資源利用率低于 45% 的節點;
- 正常節點(Normal Node)。資源利用率高于 45% 但低于 70% 的節點,這個負載水位區間是我們期望的合理的區間范圍
- 熱點節點(Hotspot Node)。如果節點資源利用率高于 70%,這個節點就會被判定為不安全了,屬于熱點節點,應該驅逐一部分 Pod,降低負載水位,使其不超過 70%。
在識別出哪些節點是熱點后,koord-descheduler 將會執行遷移驅逐操作,驅逐熱點節點中的部分 Pod 到空閑節點上。如果 Idle Node 數量是 0 或者 Hotspot Node 數量是 0,則 descheduler 不會執行任何操作。
如果一個集群中空閑節點的總數并不是很多時會終止重調度。這在大型集群中可能會有所幫助,在大型集群中,一些節點可能會經常或短時間使用不足。默認情況下,numberOfNodes 設置為零。可以通過設置參數 numberOfNodes 來開啟該能力。
在遷移前,koord-descheduler 會計算出實際空閑容量,確保要遷移的 Pod 的實際利用率之和不超過集群內空閑總量。這些實際空閑容量來自于空閑節點,一個空閑節點實際空閑容量 = (highThresholds - 節點當前負載) _ 節點總容量。假設節點 A 的負載水位是 20%,highThresholdss 是 70%,節點 A 的 CPU 總量為 96C,那么 (70%-20%) _ 96 = 48C,這 48C 就是可以承載的空閑容量了。
另外,在遷移熱點節點時,會過濾篩選節點上的 Pod,目前 koord-descheduler 支持多種篩選參數,可以避免遷移驅逐非常重要的 Pod:
- 按 namespace 過濾。可以配置成只篩選某些 namespace 或者過濾掉某些 namespace
- 按 pod selector 過濾。可以通過 label selector 篩選出 Pod,或者排除掉具備某些 Label 的 Pod
- 配置 nodeFit 檢查調度規則是否有備選節點。當開啟后,koord-descheduler 根據備選 Pod 對應的 Node Affinity/Node Selector/Toleration ,檢查集群內是否有與之匹配的 Node,如果沒有的話,該 Pod 將不會去驅逐遷移。如果設置 nodeFit 為 false,此時完全由 koord-descheduler 底層的遷移控制器完成容量預留,確保有資源后開始遷移。
當篩選出 Pod 后,從 QoSClass、Priority、實際用量和創建時間等多個維度對這些 Pod 排序。
篩選 Pod 并完成排序后,開始執行遷移操作。遷移前會檢查剩余空閑容量是否滿足和當前節點的負載水位是否高于目標安全閾值,如果這兩個條件中的一個不能滿足,將停止重調度。每遷移一個 Pod 時,會預扣剩余空閑容量,同時也會調整當前節點的負載水位,直到剩余容量不足或者水位達到安全閾值。
需要注意 ?? 的是負載感知重調度默認是禁用的,可以通過修改配置 ConfigMap koord-descheduler-config 啟用該能力。對于需要深入定制的用戶,可以按照需要更改 Helm Chart 中的 ConfigMap koord-descheduler-config 設置參數。
apiVersion: v1
kind: ConfigMap
metadata:
name: koord-descheduler-config
...
data:
koord-descheduler-config: |
apiVersion: descheduler/v1alpha2
kind: DeschedulerConfiguration
...
# 執行 LowNodeLoad 插件的間隔時間
deschedulingInterval: 60s
profiles:
- name: koord-descheduler
plugins:
deschedule:
disabled:
- name: "*"
balance:
enabled:
- name: LowNodeLoad # Configure to enable the LowNodeLoad plugin
....
pluginConfig:
- name: LowNodeLoad
args:
apiVersion: descheduler/v1alpha2
kind: LowNodeLoadArgs
evictableNamespaces:
# include 和 exclude 是互斥的,只能配置一個
# include 表示只處理下面配置的 namespace
# include:
# - test-namespace
# exclude 表示只處理除下面配置的 namespace 以外的 namespace
exclude:
- "kube-system"
- "koordinator-system"
# lowThresholds 定義資源利用率的低閾值
lowThresholds:
cpu: 20
memory: 30
# highThresholds 定義資源利用率的高閾值
highThresholds:
cpu: 50
memory: 60
....
除了上面這些配置之外還有其他一些配置,可以參看下面的表格:
字段 | 說明 |
paused | Paused 控制 LowNodeLoad 插件是否工作. |
dryRun | DryRun 表示只執行重調度邏輯,但不重復啊遷移/驅逐 Pod |
numberOfNodes | NumberOfNodes 可以配置為僅當未充分利用的節點數高于配置值時才激活該策略。這在大型集群中可能會有所幫助,在大型集群中,一些節點可能會經常或短時間使用不足。默認情況下,NumberOfNodes 設置為零。 |
evictableNamespaces | 可以參與重調度的 Namespace。可以配置 include 和 exclude 兩種,但兩種策略只能二選一。include 表示只處理指定的 namespace;exclude 表示只處理指定之外的 namespace。 |
nodeSelector | 通過 label selector 機制選擇目標節點。 |
podSelectors | 通過 label selector 選擇要處理的 Pod。 |
nodeFit | 表示是否按照備選要遷移的 Pod 中指定的 Node Affinity/Node Selector/Resource Requests/TaintToleration 判斷是否有空閑節點。沒有則不參與調度。默認開啟。可以設置為 false 禁用該能力。 |
useDeviationThresholds | 如果 useDeviationThresholds 設置為 true,則閾值被視為與平均資源使用率的百分比偏差。lowThresholds 將從所有節點的平均值中減去,highThresholds 將添加到平均值中。高于此窗口的資源消耗被視為過度利用的,即熱點節點。 |
highThresholds | 表示負載水位的目標安全閾值,超過該閾值的節點上的 Pod 將參與重調度。 |
lowThresholds | 表示負載水位的空閑安全水位。低于該閾值的節點上的 Pod 不會被重調度。 |
同樣接下來我們重新創建 stress Pods 來測試下效果:
# pod-stress.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: stress-demo
namespace: default
labels:
app: stress-demo
spec:
selector:
matchLabels:
app: stress-demo
template:
metadata:
name: stress-demo
labels:
app: stress-demo
spec:
containers:
- args:
- "--vm"
- "2"
- "--vm-bytes"
- "2000M"
- "-c"
- "4"
- "--vm-hang"
- "200"
command:
- stress
image: jockerhub.com/polinux/stress
imagePullPolicy: Always
name: stress
schedulerName: koord-scheduler # use the koord-scheduler
直接創建上面的 Deployment:
$ kubectl apply -f pod-stress.yaml
deployment.apps/stress-demo created
創建完成后檢查 Pod 的調度結果:
$ kubectl get pods -owide -l app=stress-demo
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
stress-demo-7cc84459c9-4b5wc 1/1 Running 0 12s 10.0.1.46 node2 <none> <none>
可以看到這個 Pod 分別調度到了 node2 節點上。
現在我們檢查下節點的負載情況:
$ kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master 78m 3% 2381Mi 63%
node1 64m 1% 1527Mi 19%
node2 1844m 46% 5266Mi 67%
可以看到 node2 的負載都挺高的,接下來我們更新下 koord-descheduler-config 配置,啟用負載感知重調度的插件 LowNodeLoad。然后觀察前面我們創建的 nginx Pod 的調度情況:
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
stress-demo-7cc84459c9-qk85s 1/1 Running 0 4s
nginx-with-loadaware-57c7bbbbc-btz7g 1/1 Running 0 6m13s
nginx-with-loadaware-57c7bbbbc-btz7g 1/1 Terminating 0 6m13s
nginx-with-loadaware-57c7bbbbc-btz7g 1/1 Terminating 0 6m13s
nginx-with-loadaware-57c7bbbbc-8dz96 0/1 Pending 0 0s
nginx-with-loadaware-57c7bbbbc-8dz96 0/1 Pending 0 0s
nginx-with-loadaware-57c7bbbbc-8dz96 0/1 Pending 0 0s
nginx-with-loadaware-57c7bbbbc-8dz96 0/1 ContainerCreating 0 0s
nginx-with-loadaware-57c7bbbbc-2hc8r 1/1 Running 0 6m13s
nginx-with-loadaware-57c7bbbbc-2hc8r 1/1 Terminating 0 6m13s
nginx-with-loadaware-57c7bbbbc-2hc8r 1/1 Terminating 0 6m13s
nginx-with-loadaware-57c7bbbbc-97shv 0/1 Pending 0 0s
nginx-with-loadaware-57c7bbbbc-97shv 0/1 Pending 0 0s
nginx-with-loadaware-57c7bbbbc-97shv 0/1 Pending 0 0s
nginx-with-loadaware-57c7bbbbc-97shv 0/1 ContainerCreating 0 0s
nginx-with-loadaware-57c7bbbbc-btz7g 0/1 Terminating 0 6m14s
nginx-with-loadaware-57c7bbbbc-2hc8r 0/1 Terminating 0 6m14s
nginx-with-loadaware-57c7bbbbc-2hc8r 0/1 Terminating 0 6m14s
nginx-with-loadaware-57c7bbbbc-2hc8r 0/1 Terminating 0 6m14s
nginx-with-loadaware-57c7bbbbc-btz7g 0/1 Terminating 0 6m14s
nginx-with-loadaware-57c7bbbbc-btz7g 0/1 Terminating 0 6m14s
nginx-with-loadaware-57c7bbbbc-8dz96 1/1 Running 0 3s
nginx-with-loadaware-57c7bbbbc-97shv 1/1 Running 0 3s
觀察 Events 信息,可以看到如下所示的遷移記錄:
$ kubectl get event |grep Descheduled
3m37s Normal Descheduled pod/nginx-with-loadaware-57c7bbbbc-2hc8r Pod evicted from node "master" by the reason "node is overutilized, memory usage(57.92%)>threshold(50.00%)"
57s Normal Descheduled pod/nginx-with-loadaware-57c7bbbbc-8dz96 Pod evicted from node "node2" by the reason "node is overutilized, cpu usage(75.22%)>threshold(45.00%), memory usage(65.70%)>threshold(50.00%)"
3m37s Normal Descheduled pod/nginx-with-loadaware-57c7bbbbc-btz7g Pod evicted from node "master" by the reason "node is overutilized, memory usage(57.98%)>threshold(50.00%)"
57s Normal Descheduled pod/nginx-with-loadaware-57c7bbbbc-jhdmd Pod evicted from node "node2" by the reason "node is overutilized, cpu usage(75.22%)>threshold(45.00%), memory usage(65.65%)>threshold(50.00%)"
現在 Pod 就從高負載的 node2 節點上遷移到了其他節點上去。
總結
上面介紹的這些功能只是 Koordinator 提供的負載感知功能,還有更多強大的功能等待大家去探索。
- 參考文檔:https://koordinator.sh/zh-Hans/docs。