前言
在 Kubernetes 中,默认调度器主要负责将 Pod 调度到合适的节点上,但它并不负责从团队配额、任务优先级和批任务排队等角度决定一个 Job 是否应该立即启动。
Kueue 是 Kubernetes 原生的 Job 队列与准入控制系统,主要用于管理批处理、HPC、AI/ML 等类型的工作负载。它负责决定 Job 什么时候可以获得资源配额并开始运行,以及在资源竞争时哪些 Workload 应该等待或被抢占。Pod 最终运行在哪个节点上,仍然由 Kubernetes 调度器负责。
本文将通过三个实验验证 Kueue 的核心功能:
-
创建基础队列,验证配额充足和配额不足时 Job 的状态。
-
验证同一 Cohort 中不同 ClusterQueue 之间的配额借用。
-
验证高优先级 Job 抢占低优先级 Job。
本文实验环境如下:
Kubernetes:v1.35.5
Kueue:v0.18.2
工作负载:batch/v1 Job
测试资源:CPU、内存
一、Kueue 核心对象与工作流程
在开始实验之前,需要先理解 Kueue 中几个核心对象之间的关系:
Kubernetes Job
│
│ kueue.x-k8s.io/queue-name
▼
LocalQueue
│
▼
ClusterQueue
│
├── ResourceFlavor
├── CPU、内存、GPU 等资源配额
├── Cohort
└── Preemption 抢占策略
1.1 ResourceFlavor
ResourceFlavor 用来表示一种资源变体,用于区分具有不同硬件特征、采购方式或调度属性的资源。例如,在实际生产环境中,不同节点可能存在以下差异:
-
CPU 架构不同,例如 x86 和 ARM。
-
GPU 型号不同,例如 NVIDIA A100、T4。
-
节点类型不同,例如按量节点和抢占式节点。
-
节点具有不同的标签、污点和容忍配置。
此时可以通过 ResourceFlavor 将不同资源类型与相应节点关联起来。
如果集群中的资源没有进行类型区分,也可以创建一个不包含任何 spec 配置的空 ResourceFlavor。本文的 CPU 测试不需要区分节点类型,因此会使用空 ResourceFlavor。
需要注意,ResourceFlavor 本身不保存 CPU、内存等资源配额。实际配额定义在 ClusterQueue 中。
1.2 ClusterQueue
ClusterQueue 是集群级别的资源对象,用于管理一组可供 Workload 使用的资源配额。
ClusterQueue 主要负责定义:
-
哪些命名空间可以使用该 ClusterQueue。
-
管理哪些资源,例如 CPU、内存和 GPU。
-
每种 ResourceFlavor 对应多少资源配额。
-
Workload 使用什么排队策略。
-
是否可以从 Cohort 中借用配额。
-
是否允许 Workload 之间发生抢占。
ClusterQueue 中的配额按照 [ResourceFlavor, Resource] 组合分别管理,例如:
[demo-flavor, cpu]
[demo-flavor, memory]
1.3 LocalQueue
LocalQueue 是命名空间级别的资源对象。用户通常不会直接将 Job 提交到 ClusterQueue,而是通过 Job 上的标签选择当前命名空间中的 LocalQueue:
metadata:
labels:
kueue.x-k8s.io/queue-name: demo-queue
一个 LocalQueue 指向一个 ClusterQueue,ClusterQueue 再为 LocalQueue 中的 Workload 提供配额。一个 ClusterQueue 可以被多个 LocalQueue 引用。
1.4 Workload
Workload 是 Kueue 中真正的准入单元。创建一个受 Kueue 管理的 Kubernetes Job 后,Kueue 会自动为该 Job 创建对应的 Workload。Workload 中记录了:
-
Job 请求的资源总量。
-
Job 使用的 LocalQueue。
-
Workload 的优先级。
-
Kueue 为 Workload 选择的 ResourceFlavor。
-
为 Workload 分配配额的 ClusterQueue。
-
Workload 当前是否已保留配额。
-
Workload 当前是否已经完成准入。
Kueue 会根据 Workload 中各个 PodSet 的 Pod 数量和单个 Pod 的资源请求计算总资源需求。
因此,在排查 Kueue 问题时,不能只查看 Job 和 Pod,还需要重点查看对应的 Workload。
二、实验注意事项
Kueue 中配置的配额与 Kubernetes 节点的真实资源不是同一个概念。
例如,在 ClusterQueue 中配置:
nominalQuota: "4"
表示 Kueue 最多允许符合条件的 Workload 占用 4 CPU 配额,但这并不表示 Kubernetes 集群一定存在 4 个可用 CPU。完整的启动过程可以理解为:
Job 提交
↓
Kueue 创建 Workload
↓
Kueue 检查 ClusterQueue 配额
↓
Workload 获得准入
↓
Job 解除挂起
↓
Kubernetes 调度器调度 Pod
↓
Pod 在节点上运行
因此:
-
Workload 没有获得准入时,应优先检查 Kueue 配额和队列配置。
-
Workload 已经获得准入,但 Pod 仍然 Pending 时,应继续检查节点资源、污点、容忍、节点选择器和存储等 Kubernetes 调度条件。
三、安装 Kueue
3.1 安装 Kueue
Kueue v0.18.2 可以直接使用官方发布的 manifests.yaml 安装:
kubectl apply --server-side \
-f https://github.com/kubernetes-sigs/kueue/releases/download/v0.18.2/manifests.yaml
该命令会在集群中安装 Kueue 控制器、CRD、Webhook 和相应的 RBAC 资源。Kueue v0.18.2 官方安装文档要求 Kubernetes 版本不低于 1.29,本文使用的 Kubernetes v1.35.5 满足要求。
3.2 检查 Kueue 控制器
安装完成后,查看 kueue-system 命名空间中的 Pod:
kubectl -n kueue-system get pod
输出如下:
NAME READY STATUS RESTARTS AGE
kueue-controller-manager-7869bdd57b-7md6g 1/1 Running 0 18s
这表示 Kueue Controller Manager 已正常运行。
3.3 检查 Kueue CRD
执行:
kubectl get crd | grep -i kueue
输出如下:
admissionchecks.kueue.x-k8s.io
clusterqueues.kueue.x-k8s.io
cohorts.kueue.x-k8s.io
localqueues.kueue.x-k8s.io
multikueueclusters.kueue.x-k8s.io
multikueueconfigs.kueue.x-k8s.io
provisioningrequestconfigs.kueue.x-k8s.io
resourceflavors.kueue.x-k8s.io
topologies.kueue.x-k8s.io
workloadpriorityclasses.kueue.x-k8s.io
workloads.kueue.x-k8s.io
本文主要会使用以下 CRD:
| CRD | 作用 |
|---|---|
resourceflavors.kueue.x-k8s.io |
定义资源类型 |
clusterqueues.kueue.x-k8s.io |
定义集群级资源配额 |
localqueues.kueue.x-k8s.io |
为命名空间提供队列入口 |
workloads.kueue.x-k8s.io |
表示 Kueue 的准入单元 |
cohorts.kueue.x-k8s.io |
组织可以相互共享配额的 ClusterQueue |
workloadpriorityclasses.kueue.x-k8s.io |
定义 Workload 优先级 |
这些对象均属于 Kueue v1beta2 API。
四、基础实验:验证 Kueue 配额与排队功能
本章主要验证两个场景:
-
ClusterQueue 配额充足时,Job 能够正常获得准入并运行。
-
ClusterQueue 配额不足时,Job 会保持等待状态。
4.1 创建实验命名空间
创建 kueue-demo 命名空间:
kubectl create namespace kueue-demo
4.2 创建 ResourceFlavor
创建 demo-resourceflavor.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: demo-flavor
这段配置中:
-
apiVersion:使用 Kueue v1beta2 API。 -
kind:资源类型为ResourceFlavor。 -
metadata.name:ResourceFlavor 名称为demo-flavor。 -
没有配置
spec.nodeLabels,表示该 Flavor 不限定特定的节点类型。
对于资源类型相同、不需要区分节点硬件的环境,可以使用这种空 ResourceFlavor。
检查 ResourceFlavor:
kubectl get resourceflavor
输出如下:
NAME AGE
demo-flavor 6m20s
4.3 创建 ClusterQueue
创建 demo-clusterqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: demo-cluster-queue
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kueue-demo
queueingStrategy: BestEffortFIFO
resourceGroups:
- coveredResources:
- cpu
- memory
flavors:
- name: demo-flavor
resources:
- name: cpu
nominalQuota: "3"
- name: memory
nominalQuota: 2Gi
下面解释其中的主要字段。
namespaceSelector
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kueue-demo
namespaceSelector 用于限制哪些命名空间中的 Workload 可以通过 LocalQueue 使用该 ClusterQueue 的资源配额。kubernetes.io/metadata.name 是 Kubernetes 自动添加到 Namespace 上的不可变标签,其值就是命名空间名称。
因此,这段配置表示只有 kueue-demo 命名空间中的 Workload 可以使用 demo-cluster-queue。
queueingStrategy
queueingStrategy: BestEffortFIFO
queueingStrategy 用于控制 Workload 在 ClusterQueue 中的排队方式。
Kueue 支持:
-
StrictFIFO -
BestEffortFIFO
BestEffortFIFO 会按照优先级和创建时间排列 Workload,但较早进入队列且暂时无法满足配额的 Workload,不会阻塞后面能够使用当前剩余配额的 Workload。
BestEffortFIFO 也是默认排队策略。
coveredResources
coveredResources:
- cpu
- memory
表示这个 ResourceGroup 同时管理 CPU 和内存。CPU 和内存位于同一个 ResourceGroup 中,因此 Kueue 在为 Workload 分配 Flavor 时,会为这两个资源选择同一个 ResourceFlavor。
flavors
flavors:
- name: demo-flavor
这里引用前面创建的 demo-flavor。
如果引用的 ResourceFlavor 不存在,ClusterQueue 将无法进入正常的 Active 状态。
nominalQuota
resources:
- name: cpu
nominalQuota: "3"
- name: memory
nominalQuota: 2Gi
表示 demo-cluster-queue 在 demo-flavor 下拥有:
CPU 名义配额:3
内存名义配额:2Gi
nominalQuota 表示 ClusterQueue 在对应 [ResourceFlavor, Resource] 组合下拥有的名义配额。
检查 ClusterQueue:
kubectl get clusterqueue
输出如下:
NAME COHORT PENDING WORKLOADS
demo-cluster-queue 0
这里 COHORT 为空,表示该 ClusterQueue 当前没有加入任何 Cohort。
4.4 创建 LocalQueue
创建 demo-localqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: demo-queue
namespace: kueue-demo
spec:
clusterQueue: demo-cluster-queue
主要字段说明如下:
metadata:
name: demo-queue
namespace: kueue-demo
表示创建一个名为 demo-queue 的 LocalQueue,该 LocalQueue 属于 kueue-demo 命名空间。
spec:
clusterQueue: demo-cluster-queue
表示 demo-queue 使用 demo-cluster-queue 中的资源配额。
LocalQueue 是命名空间级对象,并且一个 LocalQueue 指向一个 ClusterQueue。
检查 LocalQueue:
kubectl -n kueue-demo get localqueue
输出如下:
NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS
demo-queue demo-cluster-queue 0 0
此时三个对象之间的关系如下:
ResourceFlavor:demo-flavor
↑
ClusterQueue:demo-cluster-queue
↑
LocalQueue:kueue-demo/demo-queue
4.5 创建第一个 Job
创建 job-1.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: job-1
namespace: kueue-demo
labels:
kueue.x-k8s.io/queue-name: demo-queue
spec:
parallelism: 3
completions: 3
template:
spec:
containers:
- name: worker
image: ubuntu:22.04
command:
- /bin/sh
args:
- -c
- sleep 300
resources:
requests:
cpu: "1"
memory: 200Mi
restartPolicy: Never
下面解释几个关键字段。
queue-name
labels:
kueue.x-k8s.io/queue-name: demo-queue
表示将这个 Job 提交到 kueue-demo 命名空间中的 demo-queue。
使用 Kueue 管理 Job 时,需要通过该标签指定 LocalQueue。Kueue 会自动管理 Job 的挂起和恢复状态,因此这里不需要手动配置 spec.suspend: true。
parallelism 和 completions
parallelism: 3
completions: 3
表示:
-
Job 总共需要完成 3 个 Pod。
-
最多可以同时运行 3 个 Pod。
每个 Pod 请求:
CPU:1
内存:200Mi
因此该 Job 的总资源请求为:
CPU:3 × 1 = 3
内存:3 × 200Mi = 600Mi
Kueue 会根据 PodSet 中的 Pod 数量乘以单个 Pod 的资源请求,计算 Workload 的总资源需求。
4.6 验证 Job 是否正常运行
查看 Job:
kubectl -n kueue-demo get job
输出如下:
NAME STATUS COMPLETIONS DURATION AGE
job-1 Running 0/3 8s 9s
查看对应的 Workload:
kubectl -n kueue-demo get workloads
输出如下:
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-job-1-4d593 demo-queue demo-cluster-queue True 33s
这里可以看到:
QUEUE:demo-queue
RESERVED IN:demo-cluster-queue
ADMITTED:True
表示:
-
Job 通过
demo-queue进入 Kueue。 -
Workload 的资源配额由
demo-cluster-queue提供。 -
Workload 已经完成准入。
4.7 查看 Workload 详细信息
执行:
kubectl -n kueue-demo describe workload job-job-1-4d593
在输出中可以看到:
Status:
Admission:
Cluster Queue: demo-cluster-queue
Pod Set Assignments:
Count: 3
Flavors:
Cpu: demo-flavor
Memory: demo-flavor
Name: main
Resource Usage:
Cpu: 3
Memory: 600Mi
这表示:
-
Workload 被
demo-cluster-queue准入。 -
CPU 和内存都使用
demo-flavor。 -
PodSet 中包含 3 个 Pod。
-
CPU 总请求为 3。
-
内存总请求为 600Mi。
继续查看 Conditions:
Conditions:
Message: Quota reserved in ClusterQueue demo-cluster-queue
Reason: QuotaReserved
Status: True
Type: QuotaReserved
Message: The workload is admitted
Reason: Admitted
Status: True
Type: Admitted
其中:
QuotaReserved=True
表示 Kueue 已经在 demo-cluster-queue 中为该 Workload 保留了资源配额。
Admitted=True
表示 Workload 已完成准入,对应 Job 可以解除挂起并开始运行。
官方排查文档也建议通过 Workload 中的 QuotaReserved 和 Admitted Condition 判断 Job 是否已经获得准入。
4.8 查看 ClusterQueue 配额使用情况
执行:
kubectl describe clusterqueue demo-cluster-queue
可以看到:
Status:
Admitted Workloads: 1
Flavors Reservation:
Name: demo-flavor
Resources:
Borrowed: 0
Name: cpu
Total: 3
Borrowed: 0
Name: memory
Total: 600Mi
Flavors Usage:
Name: demo-flavor
Resources:
Borrowed: 0
Name: cpu
Total: 3
Borrowed: 0
Name: memory
Total: 600Mi
Pending Workloads: 0
Reserving Workloads: 1
重点观察:
CPU Total:3
Memory Total:600Mi
Borrowed:0
第一个 Job 已经使用了全部 3 CPU 配额。由于当前 ClusterQueue 没有加入 Cohort,因此不会从其他 ClusterQueue 借用资源,Borrowed 为 0。
Flavors Reservation 表示已经被 Workload 保留的配额。 Flavors Usage 表示已经被完成准入的 Workload 实际占用的配额。 本文没有配置额外的 AdmissionCheck,因此 Workload 保留配额后很快完成准入,两组数值相同。
4.9 创建第二个 Job
创建 job-2.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: job-2
namespace: kueue-demo
labels:
kueue.x-k8s.io/queue-name: demo-queue
spec:
template:
spec:
containers:
- name: worker
image: ubuntu:22.04
command:
- /bin/sh
args:
- -c
- sleep 300
resources:
requests:
cpu: "2"
memory: 200Mi
restartPolicy: Never
该 Job 请求:
CPU:2
内存:200Mi
但是第一个 Job 已经使用了 ClusterQueue 中全部 3 CPU 配额,因此第二个 Job 无法获得配额。
4.10 验证配额不足时的状态
查看第二个 Job 对应的 Workload:
kubectl -n kueue-demo get workload
然后查看详细信息:
kubectl -n kueue-demo describe workload job-job-2-40da7
关键输出如下:
Conditions:
Message: couldn't assign flavors to pod set main:
insufficient unused quota for cpu in flavor demo-flavor,
2 more needed
Reason: Pending
Status: False
Type: QuotaReserved
Resource Requests:
Name: main
Resources:
Cpu: 2
Memory: 200Mi
这表示:
QuotaReserved=False
Reason=Pending
Kueue 已经尝试为该 Workload 分配配额,但是 demo-flavor 中没有可用的 CPU 配额,还缺少 2 CPU,因此 Workload 保持 Pending。此时 Job 没有运行,并不是 Kubernetes 调度器无法找到合适节点,而是 Kueue 在准入阶段就没有为 Workload 分配到足够配额。
当 Job 被 Kueue 挂起时,Kubernetes 不会为该 Job 创建新的 Pod;如果 Job 已经存在运行中的 Pod,挂起 Job 时这些 Pod 会被终止和删除。
五、Cohort 配额借用实验
在实际环境中,不同团队通常会拥有独立的 ClusterQueue。
如果每个团队只能使用自己的固定配额,就可能出现以下情况:
team-a:有大量空闲配额
team-b:自身配额不足,Job 无法运行
Cohort 可以将多个 ClusterQueue 组织在一起,使它们在满足限制条件的情况下共享未使用的配额。
5.1 创建命名空间
创建两个团队的命名空间:
kubectl create namespace team-a
kubectl create namespace team-b
5.2 创建 ResourceFlavor
创建 cohort-resourceflavor.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: cohort-flavor
由于本实验只测试 CPU 配额借用,不需要区分节点类型,因此同样使用空 ResourceFlavor。
检查创建结果:
kubectl get resourceflavor cohort-flavor
5.3 创建 Cohort
创建 team-ab-cohort.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: team-ab
检查 Cohort:
kubectl get cohort
Cohort 是集群级资源对象。多个 ClusterQueue 可以通过 spec.cohortName 加入同一个 Cohort,并在相同的资源和 ResourceFlavor 维度上共享未使用配额。
5.4 创建 team-a ClusterQueue
创建 team-a-clusterqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-a-cq
spec:
cohortName: team-ab
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
resourceGroups:
- coveredResources:
- cpu
flavors:
- name: cohort-flavor
resources:
- name: cpu
nominalQuota: "4"
borrowingLimit: "2"
lendingLimit: "2"
其中:
cohortName: team-ab
表示 team-a-cq 加入 team-ab Cohort。
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
表示只允许 team-a 命名空间中的 Workload 使用该 ClusterQueue。
配额配置如下:
nominalQuota:4 CPU
borrowingLimit:2 CPU
lendingLimit:2 CPU
字段含义如下:
-
nominalQuota:team-a-cq 自己拥有的名义配额。 -
borrowingLimit:team-a-cq 最多可以从 Cohort 中借入的配额。 -
lendingLimit:team-a-cq 最多允许其他 ClusterQueue 借走的空闲配额。
检查 ClusterQueue:
kubectl get clusterqueue team-a-cq
5.5 创建 team-b ClusterQueue
创建 team-b-clusterqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-b-cq
spec:
cohortName: team-ab
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-b
resourceGroups:
- coveredResources:
- cpu
flavors:
- name: cohort-flavor
resources:
- name: cpu
nominalQuota: "2"
borrowingLimit: "2"
lendingLimit: "2"
team-b-cq 的配置为:
nominalQuota:2 CPU
borrowingLimit:2 CPU
lendingLimit:2 CPU
两个 ClusterQueue 的关系如下:
Cohort:team-ab
│
├── team-a-cq
│ ├── nominalQuota:4 CPU
│ ├── borrowingLimit:2 CPU
│ └── lendingLimit:2 CPU
│
└── team-b-cq
├── nominalQuota:2 CPU
├── borrowingLimit:2 CPU
└── lendingLimit:2 CPU
同一个 ResourceFlavor 可以被多个 ClusterQueue 引用。
这里的 cohort-flavor 并不拥有 6 CPU 配额,而是:
team-a-cq 在 [cohort-flavor, cpu] 下拥有 4 CPU
team-b-cq 在 [cohort-flavor, cpu] 下拥有 2 CPU
配额始终属于具体的 ClusterQueue。检查两个 ClusterQueue:
kubectl get clusterqueue team-a-cq team-b-cq
5.6 创建 team-a LocalQueue
创建 team-a-localqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-a-queue
namespace: team-a
spec:
clusterQueue: team-a-cq
检查:
kubectl -n team-a get localqueue
team-a-queue 属于 team-a 命名空间,并使用 team-a-cq 中的资源配额。
5.7 创建 team-b LocalQueue
创建 team-b-localqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-b-queue
namespace: team-b
spec:
clusterQueue: team-b-cq
检查:
kubectl -n team-b get localqueue
team-b-queue 属于 team-b 命名空间,并使用 team-b-cq 中的资源配额。
5.8 创建需要借用配额的 Job
创建 team-b-job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: team-b-job
namespace: team-b
labels:
kueue.x-k8s.io/queue-name: team-b-queue
spec:
template:
spec:
containers:
- name: worker
image: ubuntu:22.04
command:
- /bin/sh
args:
- -c
- sleep 300
resources:
requests:
cpu: "3"
restartPolicy: Never
该 Job 请求 3 CPU,但 team-b-cq 自己只有 2 CPU 名义配额:
Job 请求:3 CPU
team-b-cq nominalQuota:2 CPU
缺少配额:1 CPU
因为:
-
team-a-cq和team-b-cq属于同一个 Cohort。 -
team-a-cq当前有空闲配额。 -
team-a-cq允许最多借出 2 CPU。 -
team-b-cq允许最多借入 2 CPU。
因此 team-b-cq 可以从 Cohort 中借用 1 CPU。
ClusterQueue 能够使用的最大配额可以理解为:
nominalQuota + borrowingLimit
但实际能否借到,还取决于 Cohort 中其他 ClusterQueue 是否存在可借出的空闲配额。
5.9 验证 Cohort 借用结果
查看 Pod:
kubectl -n team-b get pod
输出如下:
NAME READY STATUS RESTARTS AGE
team-b-job-cnzcm 1/1 Running 0 14s
Job 成功运行,说明 Workload 已经获得 3 CPU 配额。
查看 team-b-cq:
kubectl describe clusterqueue team-b-cq
关键输出如下:
Status:
Admitted Workloads: 1
Flavors Reservation:
Name: cohort-flavor
Resources:
Borrowed: 1
Name: cpu
Total: 3
Flavors Usage:
Name: cohort-flavor
Resources:
Borrowed: 1
Name: cpu
Total: 3
Pending Workloads: 0
Reserving Workloads: 1
其中:
Total:3
Borrowed:1
表示:
team-b-cq 当前使用总量:3 CPU
team-b-cq 自身名义配额:2 CPU
从 Cohort 借用的配额:1 CPU
Kueue v1beta2 API 中,total 表示当前使用的总配额,包括从 Cohort 借用的部分;borrowed 表示超过自身 nominalQuota 的使用量。
六、高优先级 Job 抢占低优先级 Job
前面的实验解决的是多个 ClusterQueue 之间如何共享空闲配额。本章继续验证另一个场景:
ClusterQueue 的配额已经全部被低优先级 Job 使用,
此时又提交了一个需要相同资源的高优先级 Job。
Kueue 的抢占机制可以驱逐一个或多个已经获得准入的 Workload,从而为另一个 Workload 释放配额。
6.1 创建实验命名空间
kubectl create namespace preemption-demo
6.2 创建 ResourceFlavor
创建 preemption-resourceflavor.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: preemption-flavor
检查:
kubectl get resourceflavor preemption-flavor
6.3 创建 ClusterQueue
创建 preemption-clusterqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: preemption-cq
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: preemption-demo
preemption:
withinClusterQueue: LowerPriority
resourceGroups:
- coveredResources:
- cpu
flavors:
- name: preemption-flavor
resources:
- name: cpu
nominalQuota: "2"
该 ClusterQueue 只管理 CPU,并配置了 2 CPU 名义配额:
nominalQuota: "2"
本实验最关键的配置是:
preemption:
withinClusterQueue: LowerPriority
withinClusterQueue 用于控制同一个 ClusterQueue 内部的抢占。LowerPriority 表示:
当一个待准入 Workload 因配额不足无法运行时,允许它抢占同一个 ClusterQueue 中优先级更低的已准入 Workload。
需要注意,其作用范围是 ClusterQueue,而不是 LocalQueue。即使两个 Job 来自不同的 LocalQueue,只要这些 LocalQueue 指向同一个 ClusterQueue,也可能受到该策略影响。
检查 ClusterQueue:
kubectl get clusterqueue preemption-cq
6.4 创建 LocalQueue
创建 preemption-localqueue.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: preemption-queue
namespace: preemption-demo
spec:
clusterQueue: preemption-cq
检查:
kubectl -n preemption-demo get localqueue
该 LocalQueue 使用 preemption-cq 提供的 2 CPU 配额。
6.5 创建 WorkloadPriorityClass
创建 workload-priority-class.yaml:
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: low-priority
value: 100
description: "低优先级任务"
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: high-priority
value: 1000
description: "高优先级任务"
检查:
kubectl get workloadpriorityclasses
输出如下:
NAME VALUE
high-priority 1000
low-priority 100
WorkloadPriorityClass 是集群级对象,可以被不同命名空间中的 Job 使用。
其中:
-
value表示 Workload 的优先级数值。 -
数值越大,优先级越高。
-
description用于说明该优先级的用途。
Workload 优先级主要用于:
-
ClusterQueue 内 Workload 的排序。
-
判断一个 Workload 是否可以抢占另一个 Workload。
-
Cohort 中需要借用配额的 Workload 排序。
WorkloadPriorityClass 与 Kubernetes 原生的 PriorityClass 不是同一个对象。WorkloadPriorityClass 只影响 Kueue 的排队和抢占,不会改变 Pod 在 Kubernetes 调度器中的优先级。
6.6 创建低优先级 Job
创建 low-priority-job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: low-job
namespace: preemption-demo
labels:
kueue.x-k8s.io/queue-name: preemption-queue
kueue.x-k8s.io/priority-class: low-priority
spec:
template:
spec:
containers:
- name: worker
image: ubuntu:22.04
command:
- /bin/sh
args:
- -c
- sleep 600
resources:
requests:
cpu: "2"
restartPolicy: Never
其中:
kueue.x-k8s.io/queue-name: preemption-queue
表示将 Job 提交到 preemption-queue。
kueue.x-k8s.io/priority-class: low-priority
表示该 Job 使用前面创建的 low-priority WorkloadPriorityClass。官方推荐通过这个标签为 Job 指定 WorkloadPriorityClass。
该 Job 请求 2 CPU,正好占满 preemption-cq 的全部配额。
检查 Job:
kubectl -n preemption-demo get job
输出如下:
NAME STATUS COMPLETIONS DURATION AGE
low-job Running 0/1 43s 43s
此时:
ClusterQueue 配额:2 CPU
low-job 使用:2 CPU
剩余配额:0 CPU
6.7 创建高优先级 Job
创建 high-priority-job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: high-job
namespace: preemption-demo
labels:
kueue.x-k8s.io/queue-name: preemption-queue
kueue.x-k8s.io/priority-class: high-priority
spec:
template:
spec:
containers:
- name: worker
image: ubuntu:22.04
command:
- /bin/sh
args:
- -c
- sleep 300
resources:
requests:
cpu: "2"
restartPolicy: Never
高优先级 Job 同样请求 2 CPU,但此时全部配额已经被 low-job 使用。
两个 Job 的优先级为:
low-job:100
high-job:1000
由于 high-job 的优先级更高,并且 ClusterQueue 配置了:
withinClusterQueue: LowerPriority
因此,Kueue 可以抢占低优先级的 low-job,释放 2 CPU 配额,再将配额分配给 high-job。
6.8 验证抢占结果
查看两个 Job:
kubectl -n preemption-demo get job
输出如下:
NAME STATUS COMPLETIONS DURATION AGE
high-job Running 0/1 4s 4s
low-job Suspended 0/1 56s
可以看到:
high-job:Running
low-job:Suspended
这表示:
-
low-job被 Kueue 抢占。low-job的 Workload 释放了资源配额。 -
low-job被重新设置为挂起状态。high-job获得 2 CPU 配额并开始运行。
6.9 查看两个 Workload 的状态
执行:
kubectl -n preemption-demo get workload
输出如下:
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-high-job-27ca1 preemption-queue preemption-cq True 43s
job-low-job-3b11d preemption-queue False 95s
高优先级 Workload:
RESERVED IN:preemption-cq
ADMITTED:True
表示 high-job 已经获得 ClusterQueue 配额并完成准入。低优先级 Workload:
RESERVED IN:空
ADMITTED:False
表示 low-job 已失去原来的配额,不再处于已准入状态。
七、总结
通过本文的三个实验,可以得到以下结论。
1. Kueue 负责 Job 级别的配额准入
Kueue 不替代 Kubernetes 调度器。Kueue 负责决定:
Job 是否应该等待
Job 是否可以获得配额
Job 是否可以解除挂起
Workload 是否需要被抢占
Kubernetes 调度器继续负责决定 Pod 最终运行在哪个节点上。
2. Job 通过 LocalQueue 使用 ClusterQueue 配额
基本关系为:
Job
↓
LocalQueue
↓
ClusterQueue
↓
ResourceFlavor 和资源配额
Job 通过:
kueue.x-k8s.io/queue-name
选择 LocalQueue。
3. Workload 是 Kueue 的准入单元
Kueue 会为 Job 创建对应的 Workload。
排查 Job 为什么没有运行时,应重点查看 Workload 中的:
QuotaReserved
Admitted
Evicted
Preempted
4. Cohort 可以实现 ClusterQueue 之间的配额共享
同一 Cohort 中的 ClusterQueue 可以在限制范围内借用其他 ClusterQueue 的空闲名义配额。
本次实验中:
team-b-cq 自身配额:2 CPU
team-b-job 请求:3 CPU
从 Cohort 借用:1 CPU
最终使用:3 CPU
5. 高优先级 Workload 可以抢占低优先级 Workload
配置:
preemption:
withinClusterQueue: LowerPriority
后,高优先级 Workload 可以抢占同一 ClusterQueue 中优先级更低的已准入 Workload。
被抢占的 Job 会重新进入挂起状态,其运行中的 Pod 会被删除,高优先级 Job 则获得释放出来的配额并开始运行。