Kueue:Kubernetes 原生 Job Queueing——一个 Controller 搞定 Job 排队和资源配额
如果你运维过 AI 训练集群,一定遇到过这种场景:某团队提交了 100 个 PyTorchJob,Pod 数量瞬间打满整个集群,其他团队的训练任务全部卡住。原生 Kubernetes 的 batch/v1beta1 Job 没有队列管理,提交即调度,FIFO 无公平性可言。Kueue 正是 SIG-Scheduling 为解决这个问题而生的 Job Queueing 框架——它不替换 kube-scheduler,而是在准入层实现资源配额和公平调度,用一个 Controller 把 Job 的排队问题彻底管起来。
作者按: 本文写作时 Kueue 版本为 v0.10+,部分 API 已从 v1beta1 升级至 v1beta2,后文 YAML 示例统一采用 v1beta2 API,建议读者以官方文档为准。
一、背景与痛点:原生 K8s 调度为什么不够用?
1.1 Kubernetes Batch Job 的调度盲区
Kubernetes 的 batch/v1 Job(以及 batch/v1beta1 CronJob)在设计上极度简单:用户提交一个 Job,kube-scheduler 直接将 Pod 调度到节点,没有任何排队机制。这在以下场景会造成严重问题:
场景一:突发性资源争抢
一个 ML 平台同时服务多个算法团队。某天上午 10 点,A 团队需要跑 50 个分布式训练任务做模型调参,B 团队正在做日常模型评估。当 A 团队一口气提交 50 个 PyTorchJob 时,所有 Pod 立即进入调度状态——CPU、GPU、内存瞬间被占满,B 团队的评估任务只能等待,甚至因资源耗尽导致 OOM。
场景二:多租户资源配额失控
假设集群有 64 块 NVIDIA A100,按团队划分应该各用 16 块。但没有任何机制强制执行这个配额——先提交的 Job 先调度,谁手快谁用得多,最终 A 团队可能用掉 48 块,B 团队只剩 16 块甚至更少。
场景三:Fair Sharing 缺失
某段时间 A 团队完全没提交任务,集群 64 块 GPU 空闲;B 团队提交了 20 个任务用了 20 块 GPU。这时 A 团队突然来了紧急任务,必须立即用满 64 块——但 B 团队的 20 个任务还在跑,原生 K8s 没有"抢占"机制,无法回收已分配的资源。
这三个问题的本质是:Kubernetes 的调度粒度在 Pod 级别,而资源管控粒度需要在"作业批次"级别。Kueue 正是填补这一空白的设计。
1.2 AI/ML 训练场景的特殊挑战
AI/ML 训练任务(PyTorchJob、TFJob、JobSet)有以下特殊性,进一步放大了上述问题:
- 分布式训练多 Pod 协作:一个分布式训练 Job 可能需要 8 个 Pod 同时调度,每个 Pod 都需要 1 块 GPU。如果只有一个 Pod 调度成功,整个训练无法开始。Kueue 支持"All-or-nothing"策略,确保所有 Pod 同时就绪。
- 长时运行、资源独占:一个 LLM 训练任务可能跑几天甚至几周,一旦占满 GPU 就无法让位。Kueue 的抢占(Preemption)机制可以在高优先级任务到达时主动终止低优先级任务。
- 多框架并行:PyTorchJob、TFJob、MPIJob、RayJob……每种框架的 CRD 不同,但调度需求相同。Kueue 通过统一的 Workload 抽象层,屏蔽了底层框架差异。
- GPU 资源稀缺:GPU 是有限资源,调度策略直接影响成本和效率。Kueue 支持按 GPU 型号(nvidia.com/gpu)、节点拓扑进行精细化调度。
1.3 现有方案对比
| 方案 | 定位 | 优点 | 缺点 |
|---|---|---|---|
| Kueue | 原生 K8s 扩展(SIG-Scheduling) | 轻量、无缝集成、标准 API、社区活跃 | 功能相对克制,高级调度需配合其他工具 |
| Volcano | 通用批处理调度器 | 功能丰富、Job 级别调度成熟、GPU 感知好 | 需要替换/扩展 kube-scheduler,侵入性较强 |
| YuniKorn | 资源调度器(Apache 项目) | 支持 Spark/Flink 等大数据任务、Fair Queue | 主要面向大数据场景,K8s 集成相对间接 |
| Karmada | 多集群管理 | 跨集群调度能力 | 不是为 Job Queueing 设计的 |
Kueue 的核心哲学是最小侵入:它不替换 kube-scheduler,而是作为一个准入控制器(Admission Controller)运行在调度之前,通过 webhook 将 Job 转换为 Workload,再由 Kueue Controller 管理队列和准入。这种设计使得 Kueue 可以渐进式引入,不需要大规模改造现有集群。
二、核心概念:Kueue 的五块积木
理解 Kueue,首先需要理解它的五个核心 API 对象:ResourceFlavor、ClusterQueue、LocalQueue、Workload、Cohort。它们之间的关系可以用一句话概括:用户通过 LocalQueue 提交 Workload,Workload 在 ClusterQueue 中排队等资源,ClusterQueue 从 Cohort 中借资源,ResourceFlavor 定义节点标签决定 Pod 去哪里。
2.1 ResourceFlavor:节点的"身份证"
ResourceFlavor 是 Kueue 中最基础的概念,它本质上是一个带有标签的 CRD 对象,用来描述一组节点的特征。这些标签随后会被 Kube-scheduler 用于节点亲和性调度。
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: nvidia-a100-80gb # 节点标签名
spec:
nodeLabels:
# 节点必须有这些标签才能匹配这个 Flavor
node.kubernetes.io/gpu-model: nvidia-A100-SXM4-80GB
topology.kubernetes.io/rack: rack-a
taints:
# 如果节点有污点,Workload 必须有对应 Toleration 才能使用
- key: nvidia.com/gpu
value: "true"
effect: NoSchedule
为什么需要 ResourceFlavor?因为集群中的节点往往异构——有些是 A100,有些是 H100,有些是纯 CPU 节点。Kueue 通过 ResourceFlavor 区分这些节点,在调度时将 Workload 的 Pod 分配到匹配的节点上。
一个常见的实践是:为一个 GPU 集群创建多个 ResourceFlavor,分别对应不同的 GPU 型号和可用区:
# 节点示例:打标签让节点归属对应 Flavor
# kubectl label node gpu-node-1 node.kubernetes.io/gpu-model=nvidia-A100-SXM4-80GB
# kubectl label node gpu-node-2 node.kubernetes.io/gpu-model=nvidia-H100-SXM-80GB
2.2 ClusterQueue:集群级资源池
ClusterQueue(简称 CQ)是 Kueue 中最核心的资源抽象,它定义了集群中一个资源池的配额和调度策略。ClusterQueue 是集群作用域的(cluster-scoped),可以被多个命名空间共享。
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-ai-cluster-queue
spec:
# 资源组:定义该队列管理的资源及其 Flavor
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: "nvidia-a100-80gb" # 引用 ResourceFlavor
resources:
- name: "cpu"
nominalQuota: 36 # 名义配额:独占资源量
borrowingLimit: 18 # 可借用上限(从 Cohort)
- name: "memory"
nominalQuota: "144Gi"
borrowingLimit: "72Gi"
- name: "nvidia.com/gpu"
nominalQuota: 8
borrowingLimit: 4
# 队列策略
queueingStrategy: BestEffortFIFO
# Fair Sharing 权重(优先级)
nominalUsageRatio: 1.5
关键字段解析:
nominalQuota:该 ClusterQueue 的名义配额,即独占资源量。在没有其他队列借用时,该队列最多使用这么多资源。borrowingLimit:可借用配额上限。当本队列的名义配额用完时,可以从同 Cohort 的其他队列借用,但不超过这个上限。queueingStrategy:排队策略。StrictFIFO严格按优先级和创建时间排队(老任务阻塞新任务);BestEffortFIFO允许新任务"插队"如果它能 fit(默认策略)。nominalUsageRatio:用于 Fair Sharing 权重,数值越小优先级越高。
2.3 LocalQueue:命名空间级入口
LocalQueue(简称 LQ)是 ClusterQueue 在命名空间级别的"视图",是用户提交 Job 的入口。每个 LocalQueue 绑定到一个 ClusterQueue,Workload 提交到 LocalQueue 后,会自动进入对应 ClusterQueue 的调度队列。
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: training-queue
namespace: team-ai # 命名空间级别
spec:
clusterQueue: team-ai-cluster-queue # 绑定到哪个 ClusterQueue
为什么要引入 LocalQueue 这层抽象?
- 权限隔离:ClusterQueue 是集群级别的,普通用户无权直接操作。LocalQueue 允许运维预先创建好队列,普通用户只需要提交到自己的 LocalQueue。
- 命名空间绑定:一个 LocalQueue 只能属于一个命名空间,避免跨命名空间混乱。
- 默认队列:
kueue.x-k8s.io/default-local-queue: "true"注解可以让该命名空间的所有 Job 自动路由到该队列,用户无需每次指定队列名。
2.4 Workload:调度的基本单元
Workload 是 Kueue 的调度单元,对应一个待调度的作业请求。当用户提交一个 Kubernetes Job(或 PyTorchJob 等)时,Kueue 的 webhook 会自动创建一个对应的 Workload 对象:
apiVersion: kueue.x-k8s.io/v1beta2
kind: Workload
metadata:
name: training-job-xyz-uid
namespace: team-ai
spec:
# 资源请求:Job 中所有 Pod 的资源需求汇总
podSets:
- count: 4 # Pod 数量
template:
spec:
containers:
- name: trainer
resources:
requests:
cpu: "8"
memory: "32Gi"
nvidia.com/gpu: "1"
# Workload 优先级(影响调度顺序)
priorityClassName: high-priority
priority: 100000
Workload 的生命周期:
- Created:Job 被提交,webhook 自动创建 Workload,状态为
Created。 - Queued:Workload 进入 ClusterQueue 调度队列,等待资源。
- Admitted:
Pending→Admitted,Kueue 通过 webhook 将 Job 的.spec.suspend从true改为false,Pod 开始真正调度。 - Finished:Job 完成后 Workload 进入
Finished状态,释放配额。
核心机制——Suspend 方案: Kueue 不使用污点/亲和性来控制调度,而是通过修改 Job 的 .spec.suspend 字段来控制 Pod 是否启动。初始状态下 Job 的 suspend 为 true(webhook 注入),Kueue 准入后将 suspend 改为 false,Pod 才进入调度队列。这种方案的优点是对 kube-scheduler 完全透明,无需修改调度器逻辑。
2.5 Cohort:资源分组的艺术
Cohort 是将多个 ClusterQueue 组合成一个资源池的关键机制。属于同一 Cohort 的 ClusterQueue 可以互相借用对方的空闲配额,实现动态资源 Fair Sharing。
# ClusterQueue A:team-a 的队列
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-a-cq
spec:
cohort: team-research # 同 Cohort 名,所有成员共享资源池
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: "nvidia-a100-80gb"
resources:
- name: "cpu"
nominalQuota: 36
borrowingLimit: 18 # 最多从 Cohort 借用 18 CPU
- name: "memory"
nominalQuota: "144Gi"
borrowingLimit: "72Gi"
- name: "nvidia.com/gpu"
nominalQuota: 8
borrowingLimit: 4
queueingStrategy: BestEffortFIFO
---
# ClusterQueue B:team-b 的队列,同一个 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-b-cq
spec:
cohort: team-research # 与 team-a 同一 Cohort
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: "nvidia-a100-80gb"
resources:
- name: "cpu"
nominalQuota: 36
- name: "memory"
nominalQuota: "144Gi"
- name: "nvidia.com/gpu"
nominalQuota: 8
queueingStrategy: BestEffortFIFO
借用语义详解:
假设 team-a-cq 和 team-b-cq 各有 8 块 GPU nominalQuota,且 borrowingLimit 均为 4。
- 场景 1:team-a 提交了 8 个任务用了 8 块 GPU,team-b 提交了 4 个任务。此时 team-b 的任务可以借用 team-a 的 4 块空闲 GPU,共使用 8 块。
- 场景 2:team-a 需要用满 16 块 GPU(8 自有 + 8 借用),Kueue 检测到 team-b 有 pending 任务时,会触发抢占:将 team-a 借用来的 4 块 GPU 归还给 team-b,确保 team-b 的任务能调度。
- 场景 3:team-b 的 nominalQuota 为 0(只能借用),team-a 有 8 块空闲 GPU,此时 team-b 可以借用最多 4 块(受 borrowingLimit 限制)。
这种机制完美解决了开头提到的 Fair Sharing 问题——队列不会饿死,借来的资源有借有还。
2.6 Preemption:抢占的艺术
Preemption(抢占)是 Kueue 实现资源弹性分配的关键机制。当一个高优先级 Workload 无法在当前可用配额下调度时,Kueue 可以主动终止(evict)一个或多个低优先级 Workload 来腾出资源。
抢占的三种策略:
WithinClusterQueue:在同一 ClusterQueue 内部抢占。发生在高优先级 Workload 无法 fit 但低优先级 Workload 持有可被回收的资源时。WithinCohort:在同 Cohort 的 ClusterQueue 之间抢占。当一个 ClusterQueue 需要资源但当前配额不足,而同 Cohort 的另一个队列有可借用资源(且其所有者当前有 pending Workload)时触发。BorrowFromOwn:从自己的 borrowingLimit 中抢占。当一个 ClusterQueue 借用了 Cohort 资源,现在资源所有者需要时,被借方需要释放资源。
抢占策略通过 WorkloadPriorityClass 控制:
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: high-priority
value: 100000 # 数值越高优先级越高
preemptionPolicy: Preempt // 还有 Daemon、Never 选项
description: "Production training jobs"
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: low-priority
value: 100
preemptionPolicy: Preempt
description: "Experiment jobs"
注意: 抢占只会发生在 Workload 持有的资源超过其名义配额时。如果一个 Workload 只用了自己的 nominalQuota,抢占不会发生。这确保了"名义配额"的资源至少是安全的。
三、架构深度解析:Kueue 的内部工作机制
3.1 整体架构
Kueue 的架构可以用"五层同心圆"来理解:
┌─────────────────────────────────────────┐
│ User Layer (提交 Job) │
├─────────────────────────────────────────┤
│ Webhook Layer (Mutating) │ ← Job → Workload 转换
│ (Job/mutating webhook) │ suspend=true 注入
├─────────────────────────────────────────┤
│ Kueue Controller Layer │
│ ┌─────────────────────────────────────┐ │
│ │ Job Controller (watch Job → WL) │ │
│ │ Workload Controller (调度队列) │ │
│ │ Admission Controller (准入控制) │ │
│ │ Quota Reservation (配额预留) │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ Scheduler Layer │ ← Kueue 内置调度器
│ (FIFO / Fair Sharing) │
├─────────────────────────────────────────┤
│ Kube-scheduler │ ← Pod 级别的调度
│ (Pod scheduling) │ 不感知 Kueue
└─────────────────────────────────────────┘
3.2 Webhook 机制详解
Kueue 使用 Kubernetes 的 Mutating Admission Webhook 机制来拦截 Job 的创建请求。这个过程对用户完全透明:
第一步:用户提交 Job
apiVersion: batch/v1
kind: Job
metadata:
name: training-job
namespace: team-ai
labels:
kueue.x-k8s.io/queue-name: training-queue # 可选:指定 LocalQueue
spec:
parallelism: 4
completions: 4
template:
spec:
containers:
- name: trainer
image: pytorch/pytorch:2.1.0
resources:
requests:
cpu: "8"
memory: "32Gi"
nvidia.com/gpu: "1"
restartPolicy: Never
第二步:Webhook 拦截并修改
Kueue 的 Job/mutating webhook 拦截到这个 Job,执行以下操作:
- 检查
kueue.x-k8s.io/queue-name标签,确定目标 LocalQueue - 将 Job 的
.spec.suspend设置为true(Pod 暂停调度) - 添加 annotations 记录原始 suspend 值
- 如果未指定队列名且命名空间有默认 LocalQueue,自动注入队列名
# Webhook 修改后的 Job spec
spec:
suspend: true # ← 被 webhook 改为 true,Pod 暂停调度
# 其他字段不变
第三步:Job Controller 创建 Workload
Kueue 的 Job Controller 监听 Job 资源变化。当检测到一个新的非 suspend Job(或一个 Job 的 Workload 尚未创建)时,自动创建对应的 Workload:
Job (suspend=true, queued by webhook)
↓ Job Controller watch
Workload (包含资源请求信息)
↓ Workload Controller 放入调度队列
调度队列 (ClusterQueue 中排队)
↓ 配额满足 + 优先级最高
Admission: suspend=false
↓
Pod 开始调度
3.3 Kueue Controller 的四大模块
模块一:Job Controller
Job Controller 负责监听 Kubernetes Job(或 PyTorchJob、TFJob 等受支持的 CRD)资源,并根据 Job 的状态管理对应的 Workload:
- Job 创建 → 创建 Workload
- Job 规格变更 → 更新 Workload
- Job 完成/删除 → 完成/删除 Workload
Kueue 通过聚合 API(Aggregated API) 注册到 kube-apiserver,不需要直接 watch etcd,保证了良好的可扩展性。
模块二:Workload Controller
Workload Controller 维护所有 Workload 的状态,并负责调度决策:
- 维护 ClusterQueue 的资源使用状态
- 计算每个 Workload 的配额预留(Quota Reservation)
- 决定 Workload 是否可以进入 "Admitted" 状态
- 处理超时和重新入队逻辑
模块三:Quota Reservation(配额预留)
配额预留是 Kueue 调度器的核心算法。当一个 Workload 在队列中等待调度时,Kueue 会尝试为它预留配额:
- 在所有满足条件的 ClusterQueue 中选择一个(按 Fair Sharing 权重)
- 在该 ClusterQueue 的资源组中选择一个可用的 Flavor
- 检查该 Flavor 的 nominalQuota 是否足够,或可以通过 borrowing 满足
- 如果满足,预留配额并设置 Workload 状态为
QuotaReserved - 如果不满足,Workload 保持在
Pending状态
配额预留使用了乐观锁机制:预留时记录时间戳,如果有更高优先级的 Workload 到达,预留可能被撤销(reclaim)。
模块四:Admission Controller
Admission Controller 负责将 Workload 从 "QuotaReserved" 变为 "Admitted":
- 检查 Workload 的所有 AdmissionCheck(如果有)是否通过
- 通过后,通过 Job/mutating webhook 将 Job 的
.spec.suspend设置为false - Pod 开始调度,Workload 状态更新为
Admitted - 定期检查 Pod 是否真正完成调度,如果没有则回退
3.4 与 kube-scheduler 的关系
这是 Kueue 最容易混淆的点:Kueue 和 kube-scheduler 是什么关系?
简单回答:互补,不重叠。
- kube-scheduler:Pod 级别的调度,决定 Pod 应该调度到哪个具体节点。
- Kueue:Job/Workload 级别的调度,决定 Job 是否可以启动(Admission),以及在哪个资源池中运行。
时间线:
[Job 提交]
↓
[Kueue Webhook: suspend=true]
↓
[Workload 创建,进入调度队列]
↓
[Kueue 调度: QuotaReserved]
↓
[Kueue 调度: Admission (suspend=false)]
↓
[Kube-scheduler: Pod → Node 调度]
↓
[Pod Running]
可以理解为:Kueue 是 Job 的"入场检票员",kube-scheduler 是 Pod 的"座位分配员"。入场检票员决定谁可以进剧场,座位分配员决定谁坐哪个座位。
3.5 AdmissionCheck:准入检查的扩展点
AdmissionCheck 是 Kueue 为外部系统提供的一个扩展点,允许外部组件影响 Workload 的准入时机。典型应用场景包括:
- ProvisioningRequest:调用云服务商按需创建 GPU 节点(Autopilot),节点创建完成后再准入 Workload
- GenericManaged Resource:检查外部资源管理器(如 HAMi vGPU 调度器)的资源状态
- 自定义检查:例如检查 Spot 实例价格是否在阈值内,数据是否准备就绪等
apiVersion: kueue.x-k8s.io/v1beta2
kind: AdmissionCheck
metadata:
name: check-gpu-available
spec:
controllerName: kueue.x-k8s.io/admission-check-controller
retryDelayMinutes: 10 # 失败后重试间隔
strategy: Queueing # 或 "Immediate":失败后立即重试
status:
conditions:
- type: Ready
status: "True"
四、完整代码实战:从零搭建 Kueue 环境
4.1 环境准备
本文使用 Kind(Kubernetes in Docker)在本地搭建演示环境。如果已有真实集群,可跳过安装步骤。
# 安装 Kind
brew install kind # macOS
# 或: wget https://github.com/kubernetes-sigs/kind/releases/download/v0.20.0/kind-linux-amd64
# 创建 Kind 集群(需要开启 API Server 的 AdmissionWebhookConfig 特性)
kind create cluster --name kueue-demo --config - <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "node.kubernetes.io/gpu-model=nvidia-A100-SXM4-80GB"
EOF
# 确认集群可用
kubectl cluster-info
4.2 安装 Kueue
推荐使用 Helm 安装 Kueue(稳定版):
# 添加 Kueue Helm 仓库
helm repo add kueue https://kueue.sigs.k8s.io/charts
helm repo update
# 安装 Kueue(包含 CRD、webhook、controller)
helm install kueue kueue/kueue \
--namespace kueue-system \
--create-namespace \
--version 0.10.0
# 确认 Pod 运行正常
kubectl get pods -n kueue-system
# NAME READY STATUS RESTARTS AGE
# kueue-7f4f9c8d7b-abcde 1/1 Running 0 30s
如果你是开发者,也可以使用源代码安装:
# 克隆源码
git clone https://github.com/kubernetes-sigs/kueue.git
cd kueue
# 使用 make 安装(包含 CRD + 部署)
make deploy
4.3 安装 kueuectl(Kueue CLI)
# macOS/Linux 一键安装
kubectl krew install deque
kubectl krew install add deque https://kueue.sigs.k8s.io/krew-index/deque
# 实际版本中安装命令如下:
# 使用 krew 安装
kubectl krew install add kueue https://kueue.sigs.k8s.io/krew-index/kueue
# 或者直接下载二进制
curl -LO "https://github.com/kubernetes-sigs/kueue/releases/download/v0.10.0/kueue-v0.10.0-linux-amd64.tar.gz"
tar -xzf kueue-*.tar.gz
sudo mv ./kubectl-kueue /usr/local/bin/
安装后可以用 kueuectl 快速创建 Kueue 资源:
# 查看 Kueue 版本
kueuectl version
# 查看所有 ClusterQueue 状态
kueuectl get clusterqueue
# 查看所有 Workload
kueuectl get workload -A
4.4 示例一:基础资源配置(CPU 节点)
步骤 1:创建 ResourceFlavor
# resource-flavor-cpu.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: cpu-node # 标签匹配名
spec:
nodeLabels:
node.kubernetes.io/node-type: cpu-only
kubectl apply -f resource-flavor-cpu.yaml
步骤 2:创建 ClusterQueue(含 CPU/Memory 配额)
# cluster-queue-cpu.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: cpu-pool-cq
spec:
# 匹配所有命名空间(可用 namespaceSelector 限制)
namespaceSelector: {}
# 资源组:一个 CPU 节点的资源定义
resourceGroups:
- coveredResources: ["cpu", "memory"]
flavors:
- name: "cpu-node"
resources:
# 最多同时运行 18 个 CPU
- name: "cpu"
nominalQuota: 18
# borrowingLimit: 9 # 可选:允许借用同 Cohort 其他队列 9 CPU
# 最多同时使用 72 Gi 内存
- name: "memory"
nominalQuota: "72Gi"
# 默认队列策略:BestEffortFIFO
queueingStrategy: BestEffortFIFO
kubectl apply -f cluster-queue-cpu.yaml
# 查看队列状态(ResourceFlavor 列显示已分配/总配额)
kueuectl describe clusterqueue cpu-pool-cq
步骤 3:创建 LocalQueue(team-a 命名空间)
# 创建演示命名空间
kubectl create namespace team-a
# local-queue-team-a.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-a-queue
namespace: team-a
spec:
clusterQueue: cpu-pool-cq # 绑定到上面的 ClusterQueue
kubectl apply -f local-queue-team-a.yaml
# 确认 LocalQueue 已就绪
kubectl get localqueue -n team-a
# NAME CLUSTERQUEUE PENDING ACTIVE SUCCEEDED FAILED
# team-a-queue cpu-pool-cq 0 0 0 0
步骤 4:提交第一个 Job 并观察行为
# job-batch.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: batch-training-1
namespace: team-a
labels:
# 关键:指定目标 LocalQueue
kueue.x-k8s.io/queue-name: team-a-queue
spec:
parallelism: 3 # 同时运行 3 个 Pod
completions: 3 # 完成后总共 3 个 Pod
backoffLimit: 0 # 不重试
template:
spec:
restartPolicy: Never
containers:
- name: trainer
image: busybox
command: ["sh", "-c", "echo 'Training...'; sleep 300"]
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "2"
memory: "4Gi"
kubectl apply -f job-batch.yaml
# 立即观察:Job 的 suspend 字段已被 webhook 改为 true
kubectl get job -n team-a batch-training-1 -o jsonpath='{.spec.suspend}' # 应输出 true
# 查看 Workload 是否自动创建
kubectl get workload -n team-a
# NAME CLUSTERQUEUE WORKLOAD PRIORITY QUEUE NAME ADMITTED AGE
# batch-training-1-user cpu-pool-cq 100000 team-a-queue false 5s
# 等待 Kueue 调度(几秒内应该发生)
kubectl get workload -n team-a -w
# 观察 PENDING → ADMITTED 的状态变化
# 确认 Job 的 suspend 被改为 false,Pod 开始运行
kubectl get pods -n team-a
# NAME READY STATUS RESTARTS AGE
# batch-training-1-abcde 0/1 Running 0 10s
# batch-training-1-fghij 0/1 Running 0 10s
# batch-training-1-klmno 0/1 Running 0 10s
4.5 示例二:多租户 GPU 集群与资源竞争
场景设置: 两个团队(team-a 和 team-b)共享 8 块 A100 GPU,各有 4 块 nominalQuota,最多可借用对方 2 块。
步骤 1:创建 GPU ResourceFlavor
# resource-flavor-gpu.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: nvidia-a100-80gb
spec:
nodeLabels:
# 实际节点需要有此标签:
# kubectl label node <node-name> node.kubernetes.io/gpu-model=nvidia-A100-SXM4-80GB
node.kubernetes.io/gpu-model: nvidia-A100-SXM4-80GB
步骤 2:创建两个 ClusterQueue 并加入同一 Cohort
# team-a-cluster-queue.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-a-gpu-cq
spec:
cohort: gpu-research # ← 关键:同 Cohort,共享 GPU 池
namespaceSelector: {} # 允许所有命名空间(实际生产应限制)
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: "nvidia-a100-80gb"
resources:
- name: "cpu"
nominalQuota: 32
borrowingLimit: 16 # 可从 Cohort 借用最多 16 CPU
- name: "memory"
nominalQuota: "256Gi"
borrowingLimit: "128Gi"
- name: "nvidia.com/gpu"
nominalQuota: 4 # team-a 独占 4 块 A100
borrowingLimit: 2 # 最多再借用 2 块(共 6 块)
queueingStrategy: BestEffortFIFO
---
# team-b-cluster-queue.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-b-gpu-cq
spec:
cohort: gpu-research # ← 与 team-a 同一 Cohort
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: "nvidia-a100-80gb"
resources:
- name: "cpu"
nominalQuota: 32
borrowingLimit: 16
- name: "memory"
nominalQuota: "256Gi"
borrowingLimit: "128Gi"
- name: "nvidia.com/gpu"
nominalQuota: 4 # team-b 也独占 4 块 A100
borrowingLimit: 2
queueingStrategy: BestEffortFIFO
kubectl apply -f team-a-cluster-queue.yaml
kubectl apply -f team-b-cluster-queue.yaml
# 查看队列状态,确认 Cohort 配置
kueuectl get clusterqueue
# NAME COHORT WORKLOADS* PENDING ACTIVE Admitted AGE
# team-a-gpu-cq gpu-research 0 0 0 0/4 5s
# team-b-gpu-cq gpu-research 0 0 0 0/4 5s
步骤 3:创建 LocalQueue
kubectl create namespace team-a
kubectl create namespace team-b
# team-a 的队列
kubectl apply -f - <<'EOF'
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-a-gpu-queue
namespace: team-a
spec:
clusterQueue: team-a-gpu-cq
EOF
# team-b 的队列
kubectl apply -f - <<'EOF'
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-b-gpu-queue
namespace: team-b
spec:
clusterQueue: team-b-gpu-cq
EOF
步骤 4:提交竞争任务,观察 Fair Sharing
# team-a 提交 4 个训练任务(各需 1 块 GPU)
for i in $(seq 1 4); do
kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: team-a-training-$i
namespace: team-a
labels:
kueue.x-k8s.io/queue-name: team-a-gpu-queue
spec:
parallelism: 1
completions: 1
template:
spec:
restartPolicy: Never
containers:
- name: trainer
image: nvidia/cuda:12.0-runtime-ubuntu22.04
command: ["nvidia-smi", "||", "echo", "No GPU available"]
resources:
requests:
nvidia.com/gpu: "1"
cpu: "4"
memory: "8Gi"
limits:
nvidia.com/gpu: "1"
EOF
done
# team-b 提交 4 个训练任务
for i in $(seq 1 4); do
kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: team-b-training-$i
namespace: team-b
labels:
kueue.x-k8s.io/queue-name: team-b-gpu-queue
spec:
parallelism: 1
completions: 1
template:
spec:
restartPolicy: Never
containers:
- name: trainer
image: nvidia/cuda:12.0-runtime-ubuntu22.04
command: ["nvidia-smi", "||", "echo", "No GPU available"]
resources:
requests:
nvidia.com/gpu: "1"
cpu: "4"
memory: "8Gi"
limits:
nvidia.com/gpu: "1"
EOF
done
# 观察资源分配情况
kueuectl get clusterqueue
# 应该看到:
# team-a-gpu-cq: Admitted 4/4(使用了全部 4 块 nominalQuota GPU)
# team-b-gpu-cq: Admitted 0/4(nominalQuota 已满,等待资源)
# team-b 的任务因资源不足处于 PENDING 状态
kubectl get workload -A
# NAMESPACE NAME CLUSTERQUEUE ADMITTED AGE
# team-a team-a-training-1 team-a-gpu-cq true 30s
# team-a team-a-training-2 team-a-gpu-cq true 30s
# team-a team-a-training-3 team-a-gpu-cq true 30s
# team-a team-a-training-4 team-a-gpu-cq true 30s
# team-b team-b-training-1 team-b-gpu-cq false 25s
# team-b team-b-training-2 team-b-gpu-cq false 25s
# team-b team-a-training-3 team-b-gpu-cq false 25s
# team-b team-b-training-4 team-b-gpu-cq false 25s
# 当 team-a 完成部分任务后,team-b 可以借用空闲资源
# Kueue 会自动将 team-b 的 PENDING Workload 调度为 ADMITTED
4.6 示例三:集成 Kubeflow PyTorchJob
Kueue 原生支持 Kubeflow 的 PyTorchJob、TFJob、MPIJob 等 CRD。以下是与 PyTorchJob 的集成示例:
前置条件: 集群已安装 Kubeflow Training Operator
# 检查 Training Operator 是否安装
kubectl get pods -n kubeflow
# 如果未安装,可以快速安装
# kubectl apply -k "github.com/kubeflow/training-operator/manifests/overlays/standalone?ref=v1.8.0"
步骤 1:创建 PyTorchJob 的 LocalQueue 配置
PyTorchJob 需要使用 Kueue 的 batch/v1 Job 风格的集成——通过 labels 注解队列名:
# pytorch-job-distributed.yaml
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
name: pytorch-distributed-training
namespace: team-a
labels:
kueue.x-k8s.io/queue-name: team-a-gpu-queue # ← 关键:指定 Kueue 队列
spec:
# PyTorchJob 配置:1 个 Master + 2 个 Worker
# 每个角色 1 块 GPU,共 3 块
torchReplicaSpecs:
Master:
replicas: 1
template:
spec:
containers:
- name: pytorch
image: pytorch/pytorch:2.1.0
args:
- "-m"
- "torch.distributed.run"
- "--master_addr"
- "$(PYTORCH_MASTER_ADDR)"
- "--nnodes=3"
- "--node_rank=0"
- "--nproc_per_node=1"
# 实际训练请替换为真实训练脚本
- "echo 'Master started'; sleep 3600"
env:
- name: PYTORCH_MASTER_ADDR
value: "pytorch-distributed-training-master.0"
resources:
requests:
nvidia.com/gpu: "1"
cpu: "4"
memory: "16Gi"
limits:
nvidia.com/gpu: "1"
restartPolicy: Never
Worker:
replicas: 2
template:
spec:
containers:
- name: pytorch
image: pytorch/pytorch:2.1.0
args:
- "-m"
- "torch.distributed.run"
- "--master_addr"
- "$(PYTORCH_MASTER_ADDR)"
- "--nnodes=3"
- "--node_rank=$(MY_NODE_RANK)"
- "--nproc_per_node=1"
- "echo 'Worker started'; sleep 3600"
env:
- name: PYTORCH_MASTER_ADDR
value: "pytorch-distributed-training-master.0"
- name: MY_NODE_RANK
valueFrom:
fieldRef:
fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']
resources:
requests:
nvidia.com/gpu: "1"
cpu: "4"
memory: "16Gi"
limits:
nvidia.com/gpu: "1"
restartPolicy: Never
kubectl apply -f pytorch-job-distributed.yaml
# 观察 PyTorchJob 对应的 Workload
kubectl get workload -n team-a
# NAME CLUSTERQUEUE ADMITTED AGE
# pytorch-distributed-training-master team-a-gpu-cq false 10s
# pytorch-distributed-training-worker-0 team-a-gpu-cq false 10s
# pytorch-distributed-training-worker-1 team-a-gpu-cq false 10s
重要提示:PyTorchJob 的 Workload 数量取决于角色数量。 Kueue 会为每个 Pod replica(Master + 每个 Worker)各创建一个 Workload。这些 Workload 全部被 admitted 后,分布式训练才能正式开始。
五、生产级最佳实践
5.1 多租户资源隔离设计
原则:Namespace + ClusterQueue + namespaceSelector 三层隔离
Tenant A Namespace (team-a)
└── LocalQueue (team-a-cq) → ClusterQueue A (nominalQuota=16 GPU)
└── Workload 1, 2, 3...
Tenant B Namespace (team-b)
└── LocalQueue (team-b-cq) → ClusterQueue B (nominalQuota=16 GPU)
└── Workload 4, 5, 6...
Cohort: team-research (A + B 可以互相借用,borrowingLimit 控制上限)
命名空间级配额限制(推荐生产使用):
# 限制 team-a 命名空间只能使用 team-a-gpu-cq
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-a-gpu-cq
spec:
# 精确限制:只有 team-a 命名空间可以使用这个队列
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
# ... resourceGroups ...
5.2 GPU 集群配额管理细节
GPU 配额管理有几个容易踩坑的点:
点一:GPU 资源类型
NVIDIA GPU 使用 nvidia.com/gpu 资源名;AMD GPU 使用 amd.com/gpu;HAMi 虚拟 GPU 使用厂商自定义资源(如 huawei.com/gpu)。确保 ClusterQueue 中的 coveredResources 包含正确的资源名。
点二:GPU 节点的污点和容忍
GPU 节点通常有污点防止普通 Pod 调度:
# 检查 GPU 节点的污点
kubectl get nodes <gpu-node> -o jsonpath='{.spec.taints}'
# 通常是:nvidia.com/gpu:NoSchedule
# ResourceFlavor 需要对应声明容忍
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: nvidia-a100-80gb
spec:
nodeLabels:
node.kubernetes.io/gpu-model: nvidia-A100-SXM4-80GB
tolerations:
# Workload 使用这个 Flavor 时,自动继承此容忍
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
点三:节点亲和性自动注入
Kueue 的 webhook 还可以自动为 Pod 添加节点亲和性,确保 Pod 调度到对应 ResourceFlavor 的节点上。启用方式是设置 Feature Gate(或在 Kueue Config 中配置)。
5.3 队列优先级设置
通过 WorkloadPriorityClass 设置作业优先级:
# workload-priority-classes.yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: production-training
value: 1000000
preemptionPolicy: Preempt
description: "Production model training - highest priority"
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: experiment-training
value: 100
preemptionPolicy: Preempt
description: "Experiment training - can be preempted"
在 Job 中使用:
spec:
template:
spec:
# 方式 1:直接指定 WorkloadPriorityClass
priorityClassName: production-training
# 注意:priorityClassName 对应的是 Kubernetes Pod PriorityClass
# Kueue 的 WorkloadPriorityClass 是独立于 Pod Priority 的另一套优先级系统
两种优先级系统的区别:
- Pod PriorityClass:
spec.priorityClassName,影响 Pod 调度优先级(调度器层面) - WorkloadPriorityClass:Kueue 专用,影响 Workload 在队列中的排队顺序和抢占能力
两者可以独立使用,也可以配合使用。
5.4 监控与可观测性
Kueue 内置了丰富的 Prometheus metrics,零配置开箱即用:
# 安装 Prometheus(如果尚未安装)
helm install prometheus prometheus-community/prometheus \
-n prometheus --create-namespace
# Kueue 的 metrics 通过 Service 暴露
kubectl get svc -n kueue-system
# NAME TYPE CLUSTER-IP PORT(S)
# kueue-metrics-service ClusterIP 10.96.x.x 8080/TCP
# 直接查看 metrics
kubectl port-forward -n kueue-system svc/kueue-metrics-service 8080 &
curl http://localhost:8080/metrics | grep "kueue_"
# 关键指标解读:
# kueue_cluster_queue_pending_workloads{cluster_queue="team-a-gpu-cq"} # 队列中等待的任务数
# kueue_workloads_admitted_total{cluster_queue="team-a-gpu-cq"} # 累计准入的任务数
# kueue_workloads_evicted_total{cluster_queue="team-a-gpu-cq"} # 累计抢占的任务数
# kueue_quota_reclaimings_total{flavor="nvidia-a100-80gb"} # 配额回收次数
Grafana 仪表板推荐查询:
# 各队列 Pending Workload 数量
sum by (cluster_queue) (kueue_cluster_queue_pending_workloads)
# GPU 利用率(实际使用 / nominalQuota)
sum by (cluster_queue, flavor) (kueue_admitted_workload_quota_usage{resource="nvidia.com/gpu"})
/
sum by (cluster_queue, flavor) (kueue_admitted_workload_quota{resource="nvidia.com/gpu"})
# 平均等待时间
rate(kueue_workloads_admitted_total[5m]) / rate(kueue_workloads_created_total[5m])
5.5 与 Volcano / YuniKorn 的选择指南
| 维度 | Kueue | Volcano | YuniKorn |
|---|---|---|---|
| 定位 | Job Queueing(准入控制) | 批处理调度器(替换 scheduler) | 资源调度器 |
| 安装复杂度 | 低(Helm 一键) | 高(需修改 scheduler 配置) | 中 |
| 调度深度 | Job 级别准入 | Pod 级别调度(更精细) | Pod 级别调度 |
| Fair Sharing | ✅ Cohort 机制 | ✅ 多队列 Fair Share | ✅ FIFO + Fair |
| Preemption | ✅ Workload 级别 | ✅ Task 级别 | ✅ 基于优先级 |
| GPU 拓扑感知 | ✅(Topology Aware) | ✅ | ❌ |
| Multi-Cluster | ✅(MultiKueue) | ⚠️ 需配合联邦框架 | ❌ |
| 与现有系统冲突 | 无(webhook 方案) | 需接管 scheduler | 需接管 scheduler |
| CNCF 状态 | 沙箱项目 | 毕业项目 | 活跃项目 |
什么时候选 Kueue:
- 需要最小侵入地引入 Job 队列管理
- 已有完整的 kube-scheduler 配置,不想改动
- 主要场景是 AI/ML 训练任务的准入控制
- 需要与 Kubeflow、JobSet 等生态无缝集成
什么时候选 Volcano:
- 需要 Job 内部的 Task 级别调度(如 Spark Executor 的动态扩缩容)
- 需要 Gang Scheduling(所有 Pod 必须同时调度)
- 需要与 ARGO Workflow、Tekton 等工作流引擎深度集成
什么时候选 YuniKorn:
- 大数据场景为主(Spark、Flink、Presto)
- 需要 Apache Hadoop YARN 风格的调度
5.6 常见坑与排错
坑一:Job 创建后 Workload 没有自动生成
原因:可能未启用 Kueue 的 Job 集成。
# 检查 kueue-config 中是否启用了 Job 集成
kubectl get configmap kueue-manager-config -n kueue-system -o yaml
# 或者手动创建 JobSet 配置启用
# kueue.x-k8s.io/integration.jobTemplateAnnotation: batch.kubernetes.io/job-template
坑二:Workload 一直 PENDING,不进入 ADMITTED
排查步骤:
# 1. 查看 Workload 详细信息
kubectl describe workload <workload-name> -n <namespace>
# 2. 检查 ClusterQueue 配额状态
kueuectl describe clusterqueue <clusterqueue-name>
# 3. 检查是否有可用的 ResourceFlavor 节点
kubectl get nodes -l node.kubernetes.io/gpu-model=nvidia-A100-SXM4-80GB
# 4. 检查是否有节点污点未处理
kubectl get nodes -o json | jq '.items[].spec.taints'
# 5. 检查 AdmissionCheck 状态(如果有)
kubectl get admissionchecks
坑三:Pod 已经 Running 但 Workload 状态还是 PENDING
Kueue 的 Admission Controller 会等待所有 Pod 真正调度到节点。如果部分 Pod 调度失败(资源不足、亲和性不满足等),Workload 不会变为 ADMITTED。解决方案:
# 查看 Pod 调度失败原因
kubectl describe pod <pod-name> -n <namespace>
# 常见原因:
# 1. 节点资源碎片化(Pod 申请 8 CPU,但每个节点只有 4 CPU 剩余)
# → 拆分 Job parallelism,或增加节点
# 2. 节点亲和性不满足
# → 检查 ResourceFlavor 标签是否与节点匹配
# 3. GPU 节点无污点容忍
# → 在 ResourceFlavor 中添加 tolerations
坑四:Borrowing 不生效
确认 ClusterQueue 的 cohort 字段一致,且 borrowingLimit 不为 0。如果 Cohorted ClusterQueue 超过 100 个,Kueue 会有性能影响,请合理拆分 Cohort。
六、源码导读:Kueue 的核心实现
6.1 Job → Workload 转换逻辑
Kueue 的 Job Controller 位于 pkg/controller/jobs/ 目录。对于标准 Kubernetes Job,核心逻辑在 pkg/controller/jobs/job/job_controller.go:
// 简化版核心逻辑
func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. 获取 Job
job, err := r.GetJob(ctx, req.NamespacedName)
// 2. 检查 Job 是否需要被 Kueue 管理
if !isJobManaged(job) {
return ctrl.Result{}, nil
}
// 3. 获取/创建 Workload
wl, err := r.getOrCreateWorkload(ctx, job)
// 4. 根据 Job 状态更新 Workload
if job.IsSuspended() {
// Job 暂停,Workload 应该是 PENDING 或 QuotaReserved
r.handleSuspendedJob(ctx, wl)
} else if job.IsFinished() {
// Job 完成,标记 Workload 完成
r.markWorkloadFinished(ctx, wl)
}
return ctrl.Result{}, nil
}
6.2 调度队列的实现
Kueue 的调度器位于 pkg/scheduler/ 目录。调度器的核心是一个优先级队列,按以下顺序排序:
- WorkloadPriorityClass value(数值越大越优先)
- 创建时间(相同优先级时,早创建先调度)
- PriorityClass(Pod 级别优先级,用于打破平局)
// 调度队列排序(pkg/scheduler/scheduler.go)
type workloadOrder struct{}
func (s workloadOrder) Less(a, b *kueue.Workload) bool {
// 1. 优先级高的先调度
if a.Spec.Priority != b.Spec.Priority {
return a.Spec.Priority > b.Spec.Priority
}
// 2. 同优先级,早创建的先调度
if !a.CreationTimestamp.Equal(&b.CreationTimestamp) {
return a.CreationTimestamp.Before(&b.CreationTimestamp)
}
// 3. 名字字典序(最后手段)
return a.Name < b.Name
}
6.3 Quota Reservation 的实现
配额预留是调度器中最复杂的部分,位于 pkg/scheduler/flavorassigner/ 和 pkg/scheduler/scheduler.go。核心算法如下:
// 伪代码:尝试为 Workload 预留配额
func attemptReservation(ctx context.Context, wl *kueue.Workload, cq *kueue.ClusterQueue) (bool, error) {
for _, podSet := range wl.Spec.PodSets {
// 为每个 PodSet 找一个匹配的 Flavor
flavor, err := assignFlavor(podSet, cq)
if err != nil {
return false, err // 无法满足,跳过
}
// 检查 nominalQuota 是否足够
if !hasNominalQuota(cq, flavor, podSet) {
// nominalQuota 不足,尝试 borrowing
if !hasBorrowableQuota(cq, flavor, podSet) {
return false, nil // 借用也不行,无法预留
}
// 可以借用,标记为 borrowing
markAsBorrowing(wl, flavor)
}
}
// 所有 PodSet 都满足条件,预留成功
reserveQuota(wl, cq)
updateWorkloadStatus(wl, "QuotaReserved")
return true, nil
}
七、总结与展望
7.1 Kueue 在 2026 年 AI 工作负载场景的价值
2025-2026 年,大模型训练、推理和多租户 AI 平台成为 Kubernetes 最重要的应用场景。Kueue 作为 SIG-Scheduling 官方出品的 Job Queueing 框架,正好切中了这些场景的核心需求:
多租户 AI 平台:Kueue 的 Cohort 机制让多个团队共享 GPU 集群成为可能——名义配额保证每个团队至少有多少资源,Fair Sharing 让闲置资源得到充分利用。这对于 GPU 资源紧张但使用率低下的企业来说,每年可节省数十万甚至数百万元的 GPU 成本。
AI 训练管道:Kueue 与 Kubeflow、JobSet 的原生集成,让训练任务的队列管理成为基础设施的一部分。团队无需手动管理作业提交顺序,Kueue 自动处理优先级、抢占和资源分配。
Hybrid Cloud:MultiKueue 功能支持跨集群调度,允许多个集群共享一个全局控制平面。配合集群联邦工具,可以实现真正的"GPU 资源按需调度"。
7.2 未来路线图
根据 Kueue 官方 KEP 和 GitHub Roadmap,值得关注的演进方向包括:
- Dynamic Resource Allocation(DRA)集成:与 K8s 原生 DRA 深度集成,支持更灵活的 GPU 资源划分(单卡切分、多卡聚合)
- Topology-Aware Scheduling 增强:更好地支持 LLMs 的 tensor parallelism 和 pipeline parallelism 场景
- Multi-Cluster Federation:完善 MultiKueue,支持更复杂的多集群调度策略
- Elastic Workloads:支持训练任务的动态伸缩,在保证 SLA 的前提下提高资源利用率
7.3 选择决策树
你的场景是什么?
├── 仅需要 Job 准入控制(排队 + 配额)
│ └── 选择 Kueue ✓
├── 需要 Gang Scheduling(Job 内所有 Pod 同时调度/失败)
│ └── 选择 Volcano
├── 主要是大数据工作负载(Spark/Flink/Presto)
│ └── 选择 YuniKorn
├── 需要替换/增强 kube-scheduler 调度策略
│ ├── 批处理为主 → Volcano
│ └── AI 训练为主 → Kueue + Volcano
└── 多集群 AI 训练
└── Kueue MultiKueue
记住一点:Kueue 和 Volcano 并不互斥——你可以在一个集群中同时运行 Kueue(Job 准入层)和 Volcano(Gang Scheduling 层)。Kueue 负责"什么时候允许 Job 启动",Volcano 负责"Job 的 Pod 怎么调度到节点"。两者配合,可以构建一套完整的 AI 训练调度体系。
附:完整的 Kueue 核心 YAML 模板库
为方便读者快速复用,这里汇总了文中所有 YAML 模板的核心要素:
# 一、最小化 Kueue 安装清单(kind 快速验证)
# 1. helm install kueue ...
# 2. ResourceFlavor
# 3. ClusterQueue
# 4. LocalQueue
# 5. Job with queue-label
# 二、生产推荐配置要素
# - ClusterQueue: 限制 namespaceSelector
# - Cohort: 设置 borrowingLimit
# - ResourceFlavor: 对应真实节点标签 + tolerations
# - WorkloadPriorityClass: 设置优先级和 preemptionPolicy
# - Prometheus metrics: 监控队列状态和资源使用率
# 三、排错清单
# 1. kubectl get events --field-selector reason=QuotaReservation
# 2. kubectl get workload -A -o wide | grep PENDING
# 3. kueuectl describe clusterqueue <name>
# 4. kubectl get nodes -l <flavor-label>
# 5. 检查 Webhook 是否注册:kubectl get mutatingwebhookconfigurations
本文代码示例已在 Kind v0.20 + Kueue v0.10 环境中验证。如有问题,欢迎在评论区交流。