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.priority
2.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="v0.9.1" \
--create-namespace \
--namespace=kueue-system
Affinity
Affinity
指的是親和力,在計算機裡面通常指 CPU 的親和力
由於 CPU 會 context switch, 同一個 process 可能會被排程到不同的 CPU 核心上執行
而這對於效能而言是不好的,因為 CPU cache 會被清空,所以 CPU 會重新從記憶體中讀取資料
導致效能低落
你可以透過 taskset 指令將 process 綁定到特定的 CPU 核心上
操作起來長這樣
1
$ taskset 0x1 ./hello_world
在做 benchmark 的時候,taskset 很好用
因為你可以減少變因,使得你的 benchmark 更加準確
在 Kubernetes 裡面,Affinity
通常指的是 Pod 與 Node 之間的親和力(i.e. Node Affinity)
application 會希望擁有某些特定的資源
如節點本身的 cache 抑或是節點的位置等等的
你當然可以依據自己的偏好要將你的服務執行在某些節點上
比如說,考量到地理位置,你會希望服務運行在美國的節點上(因為它可以有較低的 latency)
Taints
則是 Node 與 Pod 之間的 排斥性
舉例來說,以下的指令會將 node1
標記為 maintain
,並且不允許有任何的 Pod 在上面執行
1
$ kubectl taint nodes node1 maintain=true:NoSchedule
effect 欄位共有
NoSchedule
,PreferNoSchedule
,NoExecute
三種
Taints 是由一個類似 map 的結構表示
你可以在一個節點上標記上多個 taints,表示這個節點上有多個限制
唯有可以 容忍這些限制 的 Pod 才能夠在這個節點上執行
換言之,只要節點上有任何限制,預設情況下 Pod 都會盡量避開這些節點
Toleration
則是 Pod 與 Node 之間的 容忍性
我可以容忍某些節點上有某些限制的時候,就可以使用 Toleration
比方說我可以容忍節點正在維護,因此我的 Pod 還是可以被排程到這個節點上,然後執行
下方的 nginx 仍然可以被排程到有 maintain
taint 的節點上執行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "maintain"
operator: "Exists"
effect: "NoSchedule"
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 就是上面提到的 偏好
但對於 CPU, Memory 等等的設定並不是在這裡做的
所以本質上 Flavor 管理的跟原生 Kubernetes 是一致的(Taints
以及 Toleration
, 可參考 Affinity)
為了能夠順利的使用 Kueue, 預設情況下還是要有一個 default-flavor
(如下所示)
1
2
3
4
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
name: default-flavor
Cohort
你可以透過 label 定義 cohort 的隸屬關係
比如說 john
以及 alice
都是 research-team
的一部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Namespace
metadata:
name: john
labels:
research-cohort: research-team
---
apiVersion: v1
kind: Namespace
metadata:
name: alice
labels:
research-cohort: research-team
那麼,john
以及 alice
就會共享同一個 Cohort
也就可以存取特定 research-team 底下的 Cluster Queue
Cluster Queue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: cluster-q
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: 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-team
存取
namespaceSelector: {}
代表所有的 namespace 都可以存取
上述定義了一個簡單的 Cluster Queue
我可以允許有 1 個 job 可以在 maintain-flavor
的資源上執行
但大多數還是希望可以在 default-flavor
上執行,而它可以有 5 個 Job 同時執行
而前面也提到,Cluster Queue 可以不只有一個
所以你可以根據業務邏輯,拆分多種資源群組
但有時候 Cluster Queue 上的資源真的不夠,你可以有條件的向其他 Cluster Queue 請求資源
borrowingLimit
: 最多拿別人多少資源lendingLimit
: 最多借給別人多少資源
只有相同 Cohort 的 Cluster Queue 才能夠互相借用資源
Local Queue
1
2
3
4
5
6
7
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
namespace: default
name: local-q
spec:
clusterQueue: cluster-q
Local Queue 會指向一個 Cluster Queue
並且像它請求資源
Local Queue 本身是 per namespace 的設計
屬於該 namespace 下的 Job 會提交到 Local Queue
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 在執行
又因為同時可以有 2 個 Job 在執行,所以整個完成預計要 (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
Internal error occurred: failed calling webhook “mresourceflavor.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 "mresourceflavor.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 還沒有起來
稍微的等它一下就可以了
Leave a comment