使用 ?Koordinator 實(shí)現(xiàn)資源負(fù)載感知(重)調(diào)度
Koordinator 是一個(gè)基于 QoS 的 Kubernetes 混合工作負(fù)載調(diào)度系統(tǒng)。它旨在提高對延遲敏感的工作負(fù)載和批處理作業(yè)的運(yùn)行時(shí)效率和可靠性,簡化與資源相關(guān)的配置調(diào)整的復(fù)雜性,并增加 Pod 部署密度以提高資源利用率。
混部:在同一套硬件資源上,同時(shí)運(yùn)行多個(gè)不同類型的工作負(fù)載,例如:批處理作業(yè)、交互式任務(wù)、實(shí)時(shí)數(shù)據(jù)處理等。
Koordinator 是具有高性能、可擴(kuò)展的, 在大規(guī)模生產(chǎn)環(huán)境中得到了驗(yàn)證的,可以構(gòu)建支持企業(yè)生產(chǎn)環(huán)境的容器編排系統(tǒng)。
Koordinator 通過提供以下功能增強(qiáng)了在 Kubernetes 中管理工作負(fù)載的用戶體驗(yàn):
- 精心設(shè)計(jì)的優(yōu)先級(jí)和 QoS 機(jī)制,可將不同類型的工作負(fù)載混跑在集群中,并在單個(gè)節(jié)點(diǎn)上運(yùn)行不同類型的 Pod 。
- 允許資源超賣以實(shí)現(xiàn)高資源利用率,但仍通過利用應(yīng)用程序分析機(jī)制來滿足 QoS 保證。
- 細(xì)粒度的資源協(xié)調(diào)和隔離機(jī)制,以提高延遲敏感的工作負(fù)載和批處理作業(yè)的效率。
- 靈活的作業(yè)調(diào)度機(jī)制,支持特定領(lǐng)域的工作負(fù)載,例如大數(shù)據(jù)、人工智能、音頻和視頻。
- 一整套用于監(jiān)控、故障排除和操作的工具。
Koordinator QoS vs Kubernetes QoS
Kubernetes 提供三種類型的 QoS:Guaranteed/Burstable/BestEffort,其中 Guaranteed/Burstable 被廣泛使用 BestEffort 很少使用。Koordinator 與 Kubernetes QoS 兼容,并且對每種類型都有許多增強(qiáng)功能。為了避免干擾原生 QoS 語義,Koordinator 引入了一個(gè)獨(dú)立的字段 koordinator.sh/qosClass 來描述混部 QoS。該 QoS 描述了在混部場景中節(jié)點(diǎn)上運(yùn)行的 Pod 的服務(wù)質(zhì)量。它是混合系統(tǒng)最關(guān)鍵的語義。
Koordinator 與 Kubernetes QoS 兼容,并且對每種類型都有許多增強(qiáng)功能。
Koordinator scheduler vs kube-scheduler
Koordinator 調(diào)度器并非旨在取代 kube-scheduler,而是為了讓混部的工作負(fù)載在 kubernetes 上運(yùn)行得更好。
Koordinator 調(diào)度器是基于 schedule-framework 開發(fā)的,在原生調(diào)度能力之上增加了與混部和優(yōu)先級(jí)搶占相關(guān)的調(diào)度插件。Koordinator 將致力于推動(dòng)相關(guān)的增強(qiáng)進(jìn)入 Kubernetes 的上游社區(qū),推動(dòng)混部技術(shù)的標(biāo)準(zhǔn)化。
架構(gòu)
Koordinator 由兩個(gè)控制面(Koordinator Scheduler/Koordinator Manager)和一個(gè) DaemonSet 組件(Koordlet)組成。Koordinator 在 Kubernetes 原有的能力基礎(chǔ)上增加了混部功能,并兼容了 Kubernetes 原有的工作負(fù)載。
組件
下面是 Koordinator 的核心相關(guān)組件:
Koord-Scheduler
Koord-Scheduler 以 Deployment 的形式部署在集群中,用于增強(qiáng) Kubernetes 在 QoS-aware,差異化 SLO 以及任務(wù)調(diào)度場景的資源調(diào)度能力,具體包括:
- QoS-aware 調(diào)度,包括負(fù)載感知調(diào)度讓節(jié)點(diǎn)間負(fù)載更佳平衡,資源超賣的方式支持運(yùn)行更多的低優(yōu)先級(jí)工作負(fù)載。
- 差異化 SLO,包括 CPU 精細(xì)化編排,為不同的工作負(fù)載提供不同的 QoS 隔離策略(cfs、LLC、memory 帶寬、網(wǎng)絡(luò)帶寬、磁盤 io)。
- 任務(wù)調(diào)度,包括彈性額度管理,Gang 調(diào)度,異構(gòu)資源調(diào)度等,以支持更好的運(yùn)行大數(shù)據(jù)和 AI 工作負(fù)載。
Gang Scheduling:將一組具有相同調(diào)度需求的 Pod 視為一個(gè)調(diào)度單元,一起調(diào)度,一起遷移,一起銷毀。在 AI 場景中很多任務(wù)都需要使用 Gang scheduling,社區(qū)已經(jīng)有很多相關(guān)實(shí)現(xiàn),比如 Coscheduling、Vocalno。
為了更好的支持不同類型的工作負(fù)載,Koord-scheduler 還包括了一些通用性的能力增強(qiáng):
- Reservation,支持為特定的 Pod 或者工作負(fù)載預(yù)留節(jié)點(diǎn)資源。資源預(yù)留特性廣泛應(yīng)用于重調(diào)度,資源搶占以及節(jié)點(diǎn)碎片整理等相關(guān)優(yōu)化過程。
- Node Reservation,支持為 kubernetes 之外的工作負(fù)載預(yù)留節(jié)點(diǎn)資源,一般應(yīng)用于節(jié)點(diǎn)上運(yùn)行著非容器化的負(fù)載場景。
Koord-Decheduler
Koord-Decheduler 以 Deployment 的形式部署在集群中,它是 kubernetes 上游社區(qū)的增強(qiáng)版本,當(dāng)前包含:
- 重調(diào)度框架, Koord-Decheduler 重新設(shè)計(jì)了全新重調(diào)度框架,在可擴(kuò)展性、資源確定性以及安全性上增加了諸多的加強(qiáng),更多的細(xì)節(jié).
- 負(fù)載感知重調(diào)度,基于新框架實(shí)現(xiàn)的一個(gè)負(fù)載感知重調(diào)度插件,支持用戶配置節(jié)點(diǎn)的安全水位,以驅(qū)動(dòng)重調(diào)度器持續(xù)優(yōu)化集群編排,從而規(guī)避集群中出現(xiàn)局部節(jié)點(diǎn)熱點(diǎn).
水位:節(jié)點(diǎn)上資源使用的一個(gè)閾值,當(dāng)資源使用達(dá)到水位時(shí),會(huì)觸發(fā)重調(diào)度。
重調(diào)度:當(dāng)節(jié)點(diǎn)資源不足時(shí),將節(jié)點(diǎn)上的 Pod 調(diào)度到其他節(jié)點(diǎn)上。
Koord-Manager
Koord-Manager 以 Deployment 的形式部署,通常由兩個(gè)實(shí)例組成,一個(gè) leader 實(shí)例和一個(gè) backup 實(shí)例。Koordinator Manager 由幾個(gè)控制器和 webhooks 組成,用于協(xié)調(diào)混部場景下的工作負(fù)載,資源超賣(resource overcommitment)和 SLO 管理。
目前,提供了三個(gè)組件:
- Colocation Profile,用于支持混部而不需要修改工作負(fù)載。用戶只需要在集群中做少量的配置,原來的工作負(fù)載就可以在混部模式下運(yùn)行。
- SLO 控制器,用于資源超賣(resource overcommitment)管理,根據(jù)節(jié)點(diǎn)混部時(shí)的運(yùn)行狀態(tài),動(dòng)態(tài)調(diào)整集群的超發(fā)(overcommit)配置比例。該控制器的核心職責(zé)是管理混部時(shí)的 SLO,如智能識(shí)別出集群中的異常節(jié)點(diǎn)并降低其權(quán)重,動(dòng)態(tài)調(diào)整混部時(shí)的水位和壓力策略,從而保證集群中 pod 的穩(wěn)定性和吞吐量。
- Recommender(即將推出),它使用 histograms 來統(tǒng)計(jì)和預(yù)測工作負(fù)載的資源使用細(xì)節(jié),用來預(yù)估工作負(fù)載的峰值資源需求,從而支持更好地分散熱點(diǎn),提高混部的效率。此外,資源 profiling 還將用于簡化用戶資源規(guī)范化配置的復(fù)雜性,如支持 VPA。
Koordlet
Koordlet 以 DaemonSet 的形式部署在 Kubernetes 集群中,用于支持混部場景下的資源超賣(resource overcommitment)、干擾檢測、QoS 保證等。
在 Koordlet 內(nèi)部,它主要包括以下模塊:
- 資源 Profiling,估算 Pod 資源的實(shí)際使用情況,回收已分配但未使用的資源,用于低優(yōu)先級(jí) Pod 的 overcommit。
- 資源隔離,為不同類型的 Pod 設(shè)置資源隔離參數(shù),避免低優(yōu)先級(jí)的 Pod 影響高優(yōu)先級(jí) Pod 的穩(wěn)定性和性能。
- 干擾檢測,對于運(yùn)行中的 Pod,動(dòng)態(tài)檢測資源爭奪,包括 CPU 調(diào)度、內(nèi)存分配延遲、網(wǎng)絡(luò)、磁盤 IO 延遲等。
- QoS 管理器,根據(jù)資源剖析、干擾檢測結(jié)果和 SLO 配置,動(dòng)態(tài)調(diào)整混部節(jié)點(diǎn)的水位,抑制影響服務(wù)質(zhì)量的 Pod。
- 資源調(diào)優(yōu),針對混部場景進(jìn)行容器資源調(diào)優(yōu),優(yōu)化容器的 CPU Throttle、OOM 等,提高服務(wù)運(yùn)行質(zhì)量。
Koord-RuntimeProxy
Koord-RuntimeProxy 以 systemd service 的形式部署在 Kubernetes 集群的節(jié)點(diǎn)上,用于代理 Kubelet 與 containerd/docker 之間的 CRI 請求。這一個(gè)代理被設(shè)計(jì)來支持精細(xì)化的資源管理策略,比如為不同 QoS Pod 設(shè)置不同的 cgroup 參數(shù),包括內(nèi)核 cfs quota,resctl 等等技術(shù)特性,以改進(jìn) Pod 的運(yùn)行時(shí)質(zhì)量。
資源模型
混部是一套資源調(diào)度解決方案,用于對延遲敏感的工作負(fù)載與大數(shù)據(jù)計(jì)算工作負(fù)載進(jìn)行精細(xì)化編排。它需要解決兩個(gè)主要問題:
- 如何為延遲敏感的工作負(fù)載調(diào)度資源,以滿足性能和長尾延遲的要求。這里涉及到的關(guān)鍵點(diǎn)是資源調(diào)度策略和 QoS 感知策略。
- 如何調(diào)度和編排大數(shù)據(jù)計(jì)算工作負(fù)載,以較低的成本滿足任務(wù)對計(jì)算資源的需求。這里涉及到的關(guān)鍵是如何在極端異常情況下實(shí)現(xiàn)合理的資源超額配置和 QoS 保障。
定義
Resource Model
上圖是 Koordinator 的混部資源模型,其基本思想是利用那些已分配但未使用的資源來運(yùn)行低優(yōu)先級(jí)的 pod。如圖所示,有四條線:
- limit:灰色,高優(yōu)先級(jí) Pod 所請求的資源量,對應(yīng)于 Kubernetes 的 Pod 請求。
- usage:紅色,Pod 實(shí)際使用的資源量,橫軸為時(shí)間線,紅線為 Pod 負(fù)載隨時(shí)間變化的波動(dòng)曲線。
- short-term reservation:深藍(lán)色,這是基于過去(較短)時(shí)期內(nèi)的資源使用量,對未來一段時(shí)間內(nèi)其資源使用量的估計(jì)。預(yù)留和限制的區(qū)別在于,分配的未使用(未來不會(huì)使用的資源)可以用來運(yùn)行短期執(zhí)行的批處理 Pod。
- long-term reservation:淺藍(lán)色,與 short-term reservation 類似,但估計(jì)的歷史使用期更長。從保留到限制的資源可以用于生命周期較長的 Pod,與短期的預(yù)測值相比,可用的資源較少,但更穩(wěn)定。
整個(gè)混部資源調(diào)度是基于上圖所示的資源模型構(gòu)建的,不僅可以滿足各種工作負(fù)載的資源需求,還可以充分利用集群的閑置資源。
SLO 描述
在集群中運(yùn)行的 Pod 資源 SLO 由兩個(gè)概念組成,即優(yōu)先級(jí)和 QoS。
- 優(yōu)先級(jí),即資源的優(yōu)先級(jí),代表了請求資源被調(diào)度的優(yōu)先級(jí)。通常情況下,優(yōu)先級(jí)會(huì)影響 Pod 在調(diào)度器待定隊(duì)列中的相對位置。
- QoS,代表 Pod 運(yùn)行時(shí)的服務(wù)質(zhì)量。如 cgroups cpu share、cfs 配額、LLC、內(nèi)存、OOM 優(yōu)先級(jí)等等。
需要注意的是,Priority 和 QoS 是兩個(gè)維度的概念,但在實(shí)際業(yè)務(wù)場景中,兩者之間會(huì)有一些約束(不是所有的組合都是合法的)。
優(yōu)先級(jí)
Koordinator 在 Kubernetes 優(yōu)先級(jí)類型的基礎(chǔ)上定義了一套規(guī)范,并擴(kuò)展了優(yōu)先級(jí)的一個(gè)維度以對混部場景的細(xì)粒度支持。
優(yōu)先級(jí)用數(shù)字表示,目前定義了四個(gè)類:
PriorityClass 目前留有一些暫未使用的區(qū)間,以支持未來可能的擴(kuò)展。
Koordinator 將不同類型的工作負(fù)載匹配到不同的優(yōu)先級(jí):
- koord-prod,運(yùn)行典型的延遲敏感型服務(wù),一般是指需要 "實(shí)時(shí) "響應(yīng)的服務(wù)類型,比如通過點(diǎn)擊移動(dòng) APP 中的按鈕調(diào)用的典型服務(wù)。
- koord-mid,對應(yīng)于長周期的可用資源,一般用于運(yùn)行一些實(shí)時(shí)計(jì)算、人工智能訓(xùn)練任務(wù)/作業(yè),如 tensorflow/pytorch 等。
- koord-batch,對應(yīng)于的短周期可用資源,運(yùn)行典型的離線批處理作業(yè),一般指離線分析類作業(yè),如日級(jí)大數(shù)據(jù)報(bào)告、非交互式 SQL 查詢。
- koord-free,運(yùn)行低優(yōu)先級(jí)的離線批處理作業(yè),一般指不做資源預(yù)算,利用閑置資源盡量完成,如開發(fā)人員為測試目提交的作業(yè)。
Koordinator 在 Kubernetes 集群中部署時(shí)會(huì)初始化這四個(gè) 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."
在每個(gè) PriorityClass 內(nèi),Koordinator 允許用戶為精細(xì)化資源調(diào)度設(shè)置混部 Pod 的優(yōu)先級(jí)。
比如下面的 YAML 是一個(gè) Pod 配置的例子,它使用了前面例子中創(chuàng)建的 PriorityClass 和優(yōu)先級(jí)。
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 用于表達(dá)節(jié)點(diǎn)上 Pod 的運(yùn)行質(zhì)量,如獲取資源的方式、獲取資源的比例、QoS 保障策略等。
Koordinator 調(diào)度系統(tǒng)支持的 QoS 有五種類型:
QoS CPU 編排隔離與共享
Koordinator QoS 與 Kubernetes QoS 的對比
從上面可以看出,Koordinator 的 QoS 比 Kubernetes 的 QoS 更復(fù)雜,因?yàn)樵诨觳繄鼍跋?,我們需要對延遲敏感的工作負(fù)載的 QoS 進(jìn)行微調(diào),以滿足混部時(shí)性能的需求。Koordinator 和 Kubernetes QoS 之間是有對應(yīng)關(guān)系的:
Koordlet 根據(jù) Pod 的優(yōu)先級(jí)和 QoS 定義,觸發(fā)相應(yīng)的資源隔離和 QoS 保障。
安裝
Koordinator 依賴 1.18 及以上版本的 Kubernetes。另外 Koordinator 可能會(huì)從 kubelet 只讀端口收集指標(biāo)(默認(rèn)設(shè)置為禁用,即采用了安全端口)。
為了最好的體驗(yàn),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
上面的命令默認(rèn)會(huì)安裝在 koordinator-system namespace 下,如果需要安裝在其他 namespace 下,可以使用 installation.namespace 參數(shù),我們可以使用 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
使用
現(xiàn)在我們就可以使用 Koordinator 來調(diào)度我們的工作負(fù)載了。
負(fù)載感知調(diào)度
負(fù)載感知調(diào)度(Load Aware Scheduling)是 koord-scheduler 提供的一種調(diào)度能力,調(diào)度 Pod 時(shí)根據(jù)節(jié)點(diǎn)的負(fù)載情況選擇合適的節(jié)點(diǎn),均衡節(jié)點(diǎn)間的負(fù)載情況。
負(fù)載均衡是資源調(diào)度中的常見問題。資源未充分利用的節(jié)點(diǎn)會(huì)帶來很大的資源浪費(fèi),而過度使用的節(jié)點(diǎn)可能會(huì)導(dǎo)致性能下降。這些問題都不能高效的管理和使用資源。原生 Kubernetes Scheduler 根據(jù) Requests 和節(jié)點(diǎn)可分配總量來調(diào)度 Pod,既不考慮實(shí)時(shí)負(fù)載,也不估計(jì)使用量。當(dāng)我們期望使用原生調(diào)度器均勻的打散 Pod 并保持節(jié)點(diǎn)間的負(fù)載均衡,我們需要為應(yīng)用程序設(shè)置精確的資源規(guī)格。此外,當(dāng) Koordinator 通過超賣機(jī)制提升資源使用效率時(shí),我們需要一種機(jī)制盡量避免性能回退,并避免負(fù)載過高的問題。
Koordinator 調(diào)度插件過濾異常節(jié)點(diǎn)并根據(jù)資源使用情況對其進(jìn)行評分。這個(gè)調(diào)度插件擴(kuò)展了 Kubernetes 調(diào)度框架中定義的 Filter/Score/Reserve/Unreserve 擴(kuò)展點(diǎn)。
過濾不健康的節(jié)點(diǎn)
默認(rèn)過濾異常節(jié)點(diǎn),但是用戶可以根據(jù)需要通過配置來決定是否開啟。
- 過濾 Koordlet 無法更新 NodeMetric 的節(jié)點(diǎn)。如果配置啟用,插件將排除 nodeMetrics.status.updateTime >= LoadAwareSchedulingArgs.nodeMetricExpirationSeconds 的節(jié)點(diǎn)。
- 按利用率閾值過濾節(jié)點(diǎn)。如果配置啟用,插件將排除 latestUsageUtilization >= 利用率閾值的節(jié)點(diǎn)。在過濾階段,僅從最新的 NodeMetric 中獲取資源利用率,已分配但尚未統(tǒng)計(jì)的 Pod 的資源利用率不參與計(jì)算,以便為新創(chuàng)建的 Pod 分配資源,避免因估算不合理而導(dǎo)致調(diào)度失敗。
評分算法
評分算法的核心邏輯是選擇資源使用量最小的節(jié)點(diǎn)。但是考慮到資源使用上報(bào)的延遲和 Pod 啟動(dòng)時(shí)間的延遲,時(shí)間窗口內(nèi)已經(jīng)調(diào)度的 Pod 和當(dāng)前正在調(diào)度的 Pod 的資源請求也會(huì)被估算出來,并且估算值將參與計(jì)算。
全局配置
對于需要深入定制的用戶,可以通過修改 Helm Chart 中的 ConfigMap koord-scheduler-config
規(guī)則來配置負(fù)載感知調(diào)度。
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 # 啟用負(fù)載感知調(diào)度插件
...
score: # 打分
enabled:
- name: LoadAwareScheduling
weight: 1 # 打分權(quán)重
...
reserve: # 預(yù)留資源
enabled:
- name: LoadAwareScheduling
...
pluginConfig: # 配置閾值和權(quán)重
- name: LoadAwareScheduling
args:
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: LoadAwareSchedulingArgs
filterExpiredNodeMetrics: true # 是否過濾掉無法更新 NodeMetric 的節(jié)點(diǎn)
nodeMetricExpirationSeconds: 300 # 使用 NodeMetric 的過期時(shí)間
# 資源權(quán)重
resourceWeights:
cpu: 1
memory: 1
usageThresholds: # 資源利用率閾值
cpu: 75
memory: 85
prodUsageThresholds: # prod資源利用率閾值
cpu: 55
memory: 65
scoreAccordingProdUsage: true # 是否根據(jù)prod資源利用率閾值進(jìn)行打分
estimatedScalingFactors: # 資源利用率估算因子
cpu: 80
memory: 70
aggregated: # 資源利用率百分比統(tǒng)計(jì)
usageThresholds:
cpu: 65
memory: 75
usageAggregationType: "p99"
scoreAggregationType: "p99"
可配置的參數(shù)如下所示:
字段 | 說明 | 版本 |
filterExpiredNodeMetrics | filterExpiredNodeMetrics 表示是否過濾 koordlet 更新 NodeMetric 失敗的節(jié)點(diǎn)。默認(rèn)情況下啟用,但在 Helm chart 中,它被禁用。 | >= v0.4.0 |
nodeMetricExpirationSeconds | nodeMetricExpirationSeconds 指示 NodeMetric 過期時(shí)間(以秒為單位)。當(dāng) NodeMetrics 過期時(shí),節(jié)點(diǎn)被認(rèn)為是異常的。默認(rèn)為 180 秒。 | >= v0.4.0 |
resourceWeights | resourceWeights 表示資源的權(quán)重。CPU 和 Memory 的權(quán)重默認(rèn)都是 1。 | >= v0.4.0 |
usageThresholds | usageThresholds 表示整機(jī)的資源利用率閾值。CPU 的默認(rèn)值為 65%,內(nèi)存的默認(rèn)值為 95%。 | >= v0.4.0 |
estimatedScalingFactors | estimatedScalingFactors 表示估計(jì)資源使用時(shí)的因子。CPU 默認(rèn)值為 85%,Memory 默認(rèn)值為 70%。 | >= v0.4.0 |
prodUsageThresholds | prodUsageThresholds 表示 Prod Pod 相對于整機(jī)的資源利用率閾值。默認(rèn)情況下不啟用。 | >= v1.1.0 |
scoreAccordingProdUsage | scoreAccordingProdUsage 控制是否根據(jù) Prod Pod 的利用率進(jìn)行評分。 | >= v1.1.0 |
aggregated | aggregated 支持基于百分位數(shù)統(tǒng)計(jì)的資源利用率過濾和評分。 | >= v1.1.0 |
Aggregated 支持的字段:
字段 | 說明 | 版本 |
usageThresholds | usageThresholds 表示機(jī)器基于百分位統(tǒng)計(jì)的資源利用率閾值。 | >= v1.1.0 |
usageAggregationType | usageAggregationType 表示過濾時(shí)機(jī)器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
usageAggregatedDuration | usageAggregatedDuration 表示過濾時(shí)機(jī)器利用率百分位數(shù)的統(tǒng)計(jì)周期。不設(shè)置該字段時(shí),調(diào)度器默認(rèn)使用 NodeMetrics 中最大周期的數(shù)據(jù)。 | >= v1.1.0 |
scoreAggregationType | scoreAggregationType 表示評分時(shí)機(jī)器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
scoreAggregatedDuration | scoreAggregatedDuration 表示打分時(shí) Prod Pod 利用率百分位的統(tǒng)計(jì)周期。不設(shè)置該字段時(shí),調(diào)度器默認(rèn)使用 NodeMetrics 中最大周期的數(shù)據(jù)。 | >= v1.1.0 |
通過插件的配置可以作為集群默認(rèn)的全局配置,當(dāng)然用戶也可以通過在節(jié)點(diǎn)上附加 annotation 來設(shè)置節(jié)點(diǎn)維度的負(fù)載閾值。當(dāng)節(jié)點(diǎn)上存在 annotation 時(shí),會(huì)根據(jù)注解指定的參數(shù)進(jìn)行過濾。
測試
我們這里的集群一共三個(gè)節(jié)點(diǎn):
- master:2 核 4G
- node1:4 核 8G
- node2:4 核 8G
下面我們創(chuàng)建一個(gè)如下所示的 Deployment 來測試負(fù)載感知調(diào)度:
# 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
直接創(chuàng)建上面的 Deployment 即可:
$ kubectl apply -f pod-stress.yaml
deployment.apps/stress-demo created
創(chuàng)建完成后觀察 Pod 的狀態(tài):
$ 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 已經(jīng)調(diào)度到了 node2 節(jié)點(diǎn)上。
現(xiàn)在我們可以檢查下每個(gè)節(jié)點(diǎn)的負(fù)載情況:
$ 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%
從上面輸出結(jié)果顯示,節(jié)點(diǎn) node1 的負(fù)載最低(master 不考慮),node2 的負(fù)載最高。
接下來我們再部署幾個(gè) 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
同樣直接創(chuàng)建上面的 Deployment:
$ kubectl apply -f nginx-with-loadaware.yaml
deployment.apps/nginx-with-loadaware created
創(chuàng)建完成后檢查 nginx 這些 Pods 的調(diào)度結(jié)果:
$ 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 絕大部分都被調(diào)度在 node2 (負(fù)載最高的節(jié)點(diǎn)) 以外的節(jié)點(diǎn)上。
感知 Prod Pods 的負(fù)載進(jìn)行調(diào)度
如果一個(gè) Node 中調(diào)度了很多 BestEffort Pod,可能會(huì)因?yàn)楣?jié)點(diǎn)的負(fù)載已達(dá)到使用限制而導(dǎo)致延遲敏感的 Pod 無法調(diào)度。在 Koordinator v1.1.0 中,負(fù)載感知調(diào)度針對這種場景進(jìn)行了優(yōu)化。對于延遲敏感(LSE/LSR/LS)的 Pod,優(yōu)先調(diào)度到 Prod Pod 總利用率較低的節(jié)點(diǎn),而 BestEffort(BE) Pod 根據(jù)整機(jī)利用率水平進(jìn)行調(diào)度。
通過設(shè)置以下參數(shù)啟用相關(guān)優(yōu)化:
字段 | 說明 | 版本 |
prodUsageThresholds | prodUsageThresholds 表示 Prod Pod 相對于整機(jī)的資源利用率閾值。默認(rèn)情況下不啟用。 | >= v1.1.0 |
scoreAccordingProdUsage | scoreAccordingProdUsage 控制是否根據(jù) Prod Pod 的利用率進(jìn)行評分。 | >= v1.1.0 |
感知基于百分位數(shù)統(tǒng)計(jì)的利用率進(jìn)行調(diào)度
Koordinator v1.0 及以前的版本都是按照 koordlet 上報(bào)的平均利用率數(shù)據(jù)進(jìn)行過濾和打分。但平均值隱藏了比較多的信息,因此在 Koordinator v1.1 中 koordlet 新增了根據(jù)百分位數(shù)統(tǒng)計(jì)的利用率聚合數(shù)據(jù)。調(diào)度器側(cè)也跟著做了相應(yīng)的適配。
通過設(shè)置以下參數(shù)啟用相關(guān)優(yōu)化:
字段 | 說明 | 版本 |
aggregated | aggregated 支持基于百分位數(shù)統(tǒng)計(jì)的資源利用率過濾和評分。 | >= v1.1.0 |
Aggregated 支持的字段:
字段 | 說明 | 版本 |
usageThresholds | usageThresholds 表示機(jī)器基于百分位統(tǒng)計(jì)的資源利用率閾值。 | >= v1.1.0 |
usageAggregationType | usageAggregationType 表示過濾時(shí)機(jī)器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
usageAggregatedDuration | usageAggregatedDuration 表示過濾時(shí)機(jī)器利用率百分位數(shù)的統(tǒng)計(jì)周期。不設(shè)置該字段時(shí),調(diào)度器默認(rèn)使用 NodeMetrics 中最大周期的數(shù)據(jù)。 | >= v1.1.0 |
scoreAggregationType | scoreAggregationType 表示評分時(shí)機(jī)器利用率的百分位類型。目前支持 avg、p50、p90、p95 和 p99。 | >= v1.1.0 |
scoreAggregatedDuration | scoreAggregatedDuration 表示打分時(shí) Prod Pod 利用率百分位的統(tǒng)計(jì)周期。不設(shè)置該字段時(shí),調(diào)度器默認(rèn)使用 NodeMetrics 中最大周期的數(shù)據(jù)。 | >= v1.1.0 |
aggregated 和 usageThresholds 參數(shù)是互斥的。當(dāng)兩者都配置時(shí),將使用 aggregated。
負(fù)載感知重調(diào)度
調(diào)度器中支持的負(fù)載感知調(diào)度能夠在調(diào)度時(shí)選擇負(fù)載較低的節(jié)點(diǎn)運(yùn)行新的 Pod,但隨著時(shí)間、集群環(huán)境變化以及工作負(fù)載面對的流量/請求的變化時(shí),節(jié)點(diǎn)的利用率會(huì)動(dòng)態(tài)的發(fā)生變化,集群內(nèi)節(jié)點(diǎn)間原本負(fù)載均衡的情況被打破,甚至有可能出現(xiàn)極端負(fù)載不均衡的情況,影響到工作負(fù)載運(yùn)行時(shí)質(zhì)量。
koord-descheduler 感知集群內(nèi)節(jié)點(diǎn)負(fù)載的變化,自動(dòng)的優(yōu)化超過負(fù)載水位安全閾值的節(jié)點(diǎn),防止出現(xiàn)極端負(fù)載不均衡的情況。
koord-descheduler 組件中 LowNodeLoad 插件負(fù)責(zé)感知負(fù)載水位完成熱點(diǎn)打散重調(diào)度工作。LowNodeLoad 插件 與 Kubernetes 原生的 descheduler 的插件 LowNodeUtilization 不同的是,LowNodeLoad 是根據(jù)節(jié)點(diǎn)真實(shí)利用率的情況決策重調(diào)度,而 LowNodeUtilization 是根據(jù)資源分配率決策重調(diào)度。
LowNodeLoad 插件有兩個(gè)最重要的參數(shù):
- highThresholds 表示負(fù)載水位的目標(biāo)安全閾值,超過該閾值的節(jié)點(diǎn)上的 Pod 將參與重調(diào)度;
- lowThresholds 表示負(fù)載水位的空閑安全水位。低于該閾值的節(jié)點(diǎn)上的 Pod 不會(huì)被重調(diào)度。
以下圖為例,lowThresholds 為 45%,highThresholds 為 70%,我們可以把節(jié)點(diǎn)歸為三類:
- 空閑節(jié)點(diǎn)(Idle Node)。資源利用率低于 45% 的節(jié)點(diǎn);
- 正常節(jié)點(diǎn)(Normal Node)。資源利用率高于 45% 但低于 70% 的節(jié)點(diǎn),這個(gè)負(fù)載水位區(qū)間是我們期望的合理的區(qū)間范圍
- 熱點(diǎn)節(jié)點(diǎn)(Hotspot Node)。如果節(jié)點(diǎn)資源利用率高于 70%,這個(gè)節(jié)點(diǎn)就會(huì)被判定為不安全了,屬于熱點(diǎn)節(jié)點(diǎn),應(yīng)該驅(qū)逐一部分 Pod,降低負(fù)載水位,使其不超過 70%。
在識(shí)別出哪些節(jié)點(diǎn)是熱點(diǎn)后,koord-descheduler 將會(huì)執(zhí)行遷移驅(qū)逐操作,驅(qū)逐熱點(diǎn)節(jié)點(diǎn)中的部分 Pod 到空閑節(jié)點(diǎn)上。如果 Idle Node 數(shù)量是 0 或者 Hotspot Node 數(shù)量是 0,則 descheduler 不會(huì)執(zhí)行任何操作。
如果一個(gè)集群中空閑節(jié)點(diǎn)的總數(shù)并不是很多時(shí)會(huì)終止重調(diào)度。這在大型集群中可能會(huì)有所幫助,在大型集群中,一些節(jié)點(diǎn)可能會(huì)經(jīng)常或短時(shí)間使用不足。默認(rèn)情況下,numberOfNodes 設(shè)置為零??梢酝ㄟ^設(shè)置參數(shù) numberOfNodes 來開啟該能力。
在遷移前,koord-descheduler 會(huì)計(jì)算出實(shí)際空閑容量,確保要遷移的 Pod 的實(shí)際利用率之和不超過集群內(nèi)空閑總量。這些實(shí)際空閑容量來自于空閑節(jié)點(diǎn),一個(gè)空閑節(jié)點(diǎn)實(shí)際空閑容量 = (highThresholds - 節(jié)點(diǎn)當(dāng)前負(fù)載) _ 節(jié)點(diǎn)總?cè)萘俊<僭O(shè)節(jié)點(diǎn) A 的負(fù)載水位是 20%,highThresholdss 是 70%,節(jié)點(diǎn) A 的 CPU 總量為 96C,那么 (70%-20%) _ 96 = 48C,這 48C 就是可以承載的空閑容量了。
另外,在遷移熱點(diǎn)節(jié)點(diǎn)時(shí),會(huì)過濾篩選節(jié)點(diǎn)上的 Pod,目前 koord-descheduler 支持多種篩選參數(shù),可以避免遷移驅(qū)逐非常重要的 Pod:
- 按 namespace 過濾??梢耘渲贸芍缓Y選某些 namespace 或者過濾掉某些 namespace
- 按 pod selector 過濾??梢酝ㄟ^ label selector 篩選出 Pod,或者排除掉具備某些 Label 的 Pod
- 配置 nodeFit 檢查調(diào)度規(guī)則是否有備選節(jié)點(diǎn)。當(dāng)開啟后,koord-descheduler 根據(jù)備選 Pod 對應(yīng)的 Node Affinity/Node Selector/Toleration ,檢查集群內(nèi)是否有與之匹配的 Node,如果沒有的話,該 Pod 將不會(huì)去驅(qū)逐遷移。如果設(shè)置 nodeFit 為 false,此時(shí)完全由 koord-descheduler 底層的遷移控制器完成容量預(yù)留,確保有資源后開始遷移。
當(dāng)篩選出 Pod 后,從 QoSClass、Priority、實(shí)際用量和創(chuàng)建時(shí)間等多個(gè)維度對這些 Pod 排序。
篩選 Pod 并完成排序后,開始執(zhí)行遷移操作。遷移前會(huì)檢查剩余空閑容量是否滿足和當(dāng)前節(jié)點(diǎn)的負(fù)載水位是否高于目標(biāo)安全閾值,如果這兩個(gè)條件中的一個(gè)不能滿足,將停止重調(diào)度。每遷移一個(gè) Pod 時(shí),會(huì)預(yù)扣剩余空閑容量,同時(shí)也會(huì)調(diào)整當(dāng)前節(jié)點(diǎn)的負(fù)載水位,直到剩余容量不足或者水位達(dá)到安全閾值。
需要注意 ?? 的是負(fù)載感知重調(diào)度默認(rèn)是禁用的,可以通過修改配置 ConfigMap koord-descheduler-config 啟用該能力。對于需要深入定制的用戶,可以按照需要更改 Helm Chart 中的 ConfigMap koord-descheduler-config 設(shè)置參數(shù)。
apiVersion: v1
kind: ConfigMap
metadata:
name: koord-descheduler-config
...
data:
koord-descheduler-config: |
apiVersion: descheduler/v1alpha2
kind: DeschedulerConfiguration
...
# 執(zhí)行 LowNodeLoad 插件的間隔時(shí)間
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 是互斥的,只能配置一個(gè)
# 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 表示只執(zhí)行重調(diào)度邏輯,但不重復(fù)啊遷移/驅(qū)逐 Pod |
numberOfNodes | NumberOfNodes 可以配置為僅當(dāng)未充分利用的節(jié)點(diǎn)數(shù)高于配置值時(shí)才激活該策略。這在大型集群中可能會(huì)有所幫助,在大型集群中,一些節(jié)點(diǎn)可能會(huì)經(jīng)常或短時(shí)間使用不足。默認(rèn)情況下,NumberOfNodes 設(shè)置為零。 |
evictableNamespaces | 可以參與重調(diào)度的 Namespace??梢耘渲?include 和 exclude 兩種,但兩種策略只能二選一。include 表示只處理指定的 namespace;exclude 表示只處理指定之外的 namespace。 |
nodeSelector | 通過 label selector 機(jī)制選擇目標(biāo)節(jié)點(diǎn)。 |
podSelectors | 通過 label selector 選擇要處理的 Pod。 |
nodeFit | 表示是否按照備選要遷移的 Pod 中指定的 Node Affinity/Node Selector/Resource Requests/TaintToleration 判斷是否有空閑節(jié)點(diǎn)。沒有則不參與調(diào)度。默認(rèn)開啟。可以設(shè)置為 false 禁用該能力。 |
useDeviationThresholds | 如果 useDeviationThresholds 設(shè)置為 true,則閾值被視為與平均資源使用率的百分比偏差。lowThresholds 將從所有節(jié)點(diǎn)的平均值中減去,highThresholds 將添加到平均值中。高于此窗口的資源消耗被視為過度利用的,即熱點(diǎn)節(jié)點(diǎn)。 |
highThresholds | 表示負(fù)載水位的目標(biāo)安全閾值,超過該閾值的節(jié)點(diǎn)上的 Pod 將參與重調(diào)度。 |
lowThresholds | 表示負(fù)載水位的空閑安全水位。低于該閾值的節(jié)點(diǎn)上的 Pod 不會(huì)被重調(diào)度。 |
同樣接下來我們重新創(chuàng)建 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
直接創(chuàng)建上面的 Deployment:
$ kubectl apply -f pod-stress.yaml
deployment.apps/stress-demo created
創(chuàng)建完成后檢查 Pod 的調(diào)度結(jié)果:
$ 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>
可以看到這個(gè) Pod 分別調(diào)度到了 node2 節(jié)點(diǎn)上。
現(xiàn)在我們檢查下節(jié)點(diǎn)的負(fù)載情況:
$ 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 的負(fù)載都挺高的,接下來我們更新下 koord-descheduler-config 配置,啟用負(fù)載感知重調(diào)度的插件 LowNodeLoad。然后觀察前面我們創(chuàng)建的 nginx Pod 的調(diào)度情況:
$ 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%)"
現(xiàn)在 Pod 就從高負(fù)載的 node2 節(jié)點(diǎn)上遷移到了其他節(jié)點(diǎn)上去。
總結(jié)
上面介紹的這些功能只是 Koordinator 提供的負(fù)載感知功能,還有更多強(qiáng)大的功能等待大家去探索。
- 參考文檔:https://koordinator.sh/zh-Hans/docs。