Kubernetes 從零開始 - 資源排隊調度神器 Kueue
Introduction to Kueue
你可以在 Kubernetes 裡面塞入任一數量的 job,但這只是理論上
實務上會因為硬體資源的限制,你只可以執行有限數量的 job
Kueue 這個工具可以根據這些 限制,允許有限數量的 job 同時執行
它可以做到一些基礎的排程機制,如
- Job 要不要等待,可不可以開始執行(i.e. 排隊)
- Job 該不該被搶佔(i.e. preemption)
Kueue 保證了所有 Job 對於資源的使用是公平的
並且可以根據偏好的資源進行分配,如 CPU, Memory, GPU 等等
而既然音同 Queue,那麼它的核心概念就是 Queue
Kueue 本身有兩種策略
StrictFiFo: 先進先出,並且是阻塞的(如果當前 Job 沒辦法被排程,它會卡在那擋到後面的人)BestEffort: 先進先出,但不是阻塞的(如果當前 Job 沒辦法被排程,它會讓位)
其實 Kueue 本身是 priority queue
它會根據 1.priority2.creation time來決定順序
Installation
1
$ kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.9.0/manifests.yaml
或者是用 Helm
1
2
3
4
$ helm install kueue oci://us-central1-docker.pkg.dev/k8s-staging-images/charts/kueue \
--version=0.9.1 \
--create-namespace \
--namespace=kueue-system
注意到如果是用 OCI,你在指定版本的時候記得不能加 prefix
v不然會報錯
Error pulling latest chart from OCI registry if semver version has a v prefix #11107
Kueue Architecture
ref: Cluster Queue
所謂的資源管理到底是怎麼個管理法
Kueue 具現化你所擁有的資源,比如說你總共有多少 CPU 多少 Memory
定義清楚之後,每一個 Job 都會從中取得資源,並且執行
你所擁有的資源都將儲存在 Cluster Queue 裡面
同一個類型的 Cluster Queue 會組成一個抽象的資源群組(Cohort)
當一個新的 Job 等待資源的時候,Local Queue 會向 Cluster Queue 請求資源
並根據 Resource Flavor 的設定,將資源分配給 Job
就可以執行,結束之後釋放資源
ref: Run A Kubernetes Job
Kueue Workload
雖然我們一直提 Job, 但實際上 Kueue 是管理所謂的 Workload
Workload 可以把它想像成是 “一件事情”,所以最直接的例子就是 Kubernetes Job
它可以是 Kubernetes 的 Job, CronJob, StatefulSet, Deployment 等等的資源
本文還是就 Kubernetes Job 進行說明與操作
Deployment 以及 StatefulSet,Kueue 是透過
pod integration來達成的
可參考 Run Plain Pods
Resource Flavor
這裡的 Flavor 就是上面提到的 偏好
比方說你的任務需要有 GPU,你可以定義一個 gpu-flavor
為了能夠順利的使用 Kueue, 預設情況下還是要有一個 default-flavor(如下所示)
1
2
3
4
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: default-flavor
Cluster Queue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: cluster-q
spec:
cohortName: "research-team"
resourceGroups:
- coveredResources: ["jobs"]
flavors:
- name: "default-flavor"
resources:
- name: "jobs"
nominalQuota: 5
- name: "maintain-flavor"
resources:
- name: "jobs"
nominalQuota: 1
# borrowingLimit: 1
# lendingLimit: 1
這個 Cluster Queue 屬於
research-teamcohort 底下
上述定義了一個簡單的 Cluster Queue
我可以允許有 1 個 job 可以在 maintain-flavor 的資源上執行
但大多數還是希望可以在 default-flavor 上執行,而它可以有 5 個 Job 同時執行
而前面也提到,Cluster Queue 可以不只有一個
所以你可以根據業務邏輯,拆分多種資源群組
但有時候 Cluster Queue 上的資源真的不夠,你可以有條件的向其他 Cluster Queue 請求資源
borrowingLimit: 最多拿別人多少資源lendingLimit: 最多借給別人多少資源
只有相同 Cohort 的 Cluster Queue 才能夠互相借用資源
Cohort
Cohort 是用來組織你的 Quota 的
好處是擁有相同 cohort 的 Cluster Queue 可以共享 quota
1
2
3
4
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: "research-team"
然後你的 Cluster Queue 定義是這樣子的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: "john-cluster-queue"
spec:
cohortName: "research-team"
preemption:
reclaimWithinCohort: Any
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: "alice-cluster-queue"
spec:
cohortName: "research-team"
preemption:
reclaimWithinCohort: Any
那麼 john-cluster-queue 以及 alice-cluster-queue 的 quota 就可以進行共享
比方說空閒的時候可以把 quota 借給相同 Cohort 的人
當你需要的時候,也可以透過 preemption 的機制把 quota 要回來
好處就是,你的任務受到 starvation 的狀況會減輕很多,並且對於重要任務的 turnaround time 也會縮減(因為 preemption)
Local Queue
1
2
3
4
5
6
7
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
namespace: default
name: local-q
spec:
clusterQueue: cluster-q
Local Queue 會指向一個 Cluster Queue
並且像它請求資源
Local Queue 本身是 per namespace 的設計
屬於該 namespace 下的 Job 會提交到 Local Queue
Netflix Batch Compute Migrate to Kueue
Netflix 內部本身就有需要執行 Batch Compute 的需求
早期他們有自己的一套 solution,Compute Managed Batch(CMB) 以及 Titus 搭配組成
CMB 系統主要是一個調度平台
使用者將任務提交到 CMB,系統會根據任務的 priority, capacity 等等的進行指派
主要運行任務的是 Titus,他是一個容器化的平台
為了更細微的控制運算平台的高效執行,必須引入一個抽象層的機制
任務的指派通常可以被歸類,比如說他是來自哪個團隊、平台亦或者是組織,被稱為 租戶(tenant)
所以我可以這樣做
- 工程部: 擁有
1500個單位- 雲端大數據部: 使用 quota 是
100個單位 - 金流系統部: 使用 quota 是
1500個單位
- 雲端大數據部: 使用 quota 是
租戶其實還有分,組織架構一定是多層級,如同上述,工程部底下細分不同部門
internal tenant: 大型部門,負責發佈指令(e.g. 工程部),對應到 Cohortleaf tenant: 實際工作的部門,可以接收指令(e.g. 雲端大數據部, 金流系統部),對應到 Cluster Queue 以及 Local Queue

那你說每個團隊會每分每秒都把資源利用到最滿嗎? 其實並不一定
假如我 10:00 ~ 12:00 這個時段比較空閒,我是不是能把算力讓出去給其他團隊使用?
同時也可以設定我最少要保留多少給自己
就是對應到 borrowingLimit 以及 lendingLimit
Why Migrate to Kueue
你說他們原本的架構都做得還不錯啊? 那為什麼要改
- Kubernetes 社群高度發展,許多早期 CMB 要自幹的事情都被一一實現
- CMB 目前有的功能,都能夠在 Kueue 裡頭找到相對應的實作
- CMB 未來想要實作的功能如 preemption 已經越加繁瑣
- Kueue 支援異質硬體的排程(透過 Resource Flavor)
- Kueue 後續的功能開發符合團隊的改進方向
他們也看過其他類似功能的套件,不過最終選擇 Kueue 的原因是他仍然使用原生 kube-scheduler
比起 Apache YuniKorn 或是 volcano-sh/volcano
這兩個都是擁有自己的 scheduler
使得 Netflix 原本的 Titus scheduler profile 不兼容,或者說他會造成排程的不穩定,進而降低效率
執行這種大型遷移的時候首先要考慮的反而是兼容性
Kueue 介入的點其實就只是 queueing 跟 scheduling 的部分而已,而原本的架構下是 CMB 負責的
所以只是單純拆開其他都不需要動
Run
要執行 kueue, 你需要將 Cluster Queue, Local Queue 與 Resource Flavor 部署到你的 cluster 上(缺一不可)
然後透過以下的範例 Job 觀察排隊的行為
你會需要加兩個設定
- Job metadata 裡面需要新增
kueue.x-k8s.io/queue-name的 label, 它需要指定到你的 Local Queue 的名稱 - 將 Job 預設的狀態設定成
suspend
為什麼要 suspend Job 呢?
原因也是很簡單,因為我們要讓 Kueue 控制 Job 的執行
如果你直接讓它執行不就沒用了
因此,所有要使用 Kueue 的 Job 預設都要讓它暫停
把控制權交給 Kueue 進行處理
這裡使用 completions 紀錄我們要有 10 個 Job 成功的次數
而 parallelism 則是同時執行的 Job 數量
根據上述 Cluster Queue 的設定,同一時間只能有 1 個 Job 在執行
又因為我們設定了 parallelism: 2,所以整個完成預計只要 (10 / 2) * 30s = 150s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: batch/v1
kind: Job
metadata:
name: kjob
labels:
kueue.x-k8s.io/queue-name: local-q
spec:
parallelism: 2
completions: 10
suspend: true
template:
spec:
containers:
- name: dummy-job
image: gcr.io/k8s-staging-perf-tests/sleep:v0.1.0
args: ["30s"]
restartPolicy: Never
Issues with Kueue
Internal error occurred: failed calling webhook “myresourceflavor.kb.io”: failed to call webhook
如果你在 apply Kueue 的設定檔的時候碰到類似以下的錯誤
1
2
3
4
5
Error from server (InternalError): error when creating "mykueue.yaml":
Internal error occurred: failed calling webhook "myresourceflavor.kb.io":
failed to call webhook:
Post "https://kueue-webhook-service.mynamespace.svc:443/mutate-kueue-x-k8s-io-v1beta1-resourceflavor?timeout=10s":
no endpoints available for service "kueue-webhook-service"
這是因為 Kueue 的 webhook service 還沒有起來
稍微的等它一下就可以了
你可以使用 $ kubectl wait 的指令等待
service 本身其實是 expose kueue-controller-manager 這個 deployment
另外,你也可以等待 ClusterQueue, LocalQueue 等的資源
注意到他們聽的狀態是不同的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ kubectl wait \
-n my-ns \
--timeout=1h \
--for='jsonpath={.status.conditions[?(@.type=="Available")].status}=True' \
deployments.apps/kueue-controller-manager
$ kubectl wait \
-n my-ns \
--timeout=1h \
--for='jsonpath={.status.conditions[?(@.type=="Active")].status}=True' \
ClusterQueue/my-cluster-queue
$ kubectl wait \
-n my-ns \
--timeout=1h \
--for='jsonpath={.status.conditions[?(@.type=="Active")].status}=True' \
LocalQueue/my-local-queue
Leave a comment