← 목록으로

Kubernetes 官方出品:一个 Controller 搞定 Job 排队和资源配额 - 探索云原生

Kubernetes 官方出品:一个 Controller 搞定 Job 排队和资源配额 - 探索云原生

요약

多个团队共用一个 Kubernetes 集群,A 团队提交了一批训练任务,几十张 GPU 很快就被占满;B 团队新提交的 Job 只能一直 Pending。 因为,而是 Kubernetes 原生采用"先到先得"的调度方式,没有 Job 队列,也没有多租户配额管理。 Kueue 正是 Kuberne

본문

kueue-p1-intro.jpg

多个团队共用一个 Kubernetes 集群,A 团队提交了一批训练任务,几十张 GPU 很快就被占满;B 团队新提交的 Job 只能一直 Pending。
因为,而是 Kubernetes 原生采用"先到先得"的调度方式,没有 Job 队列,也没有多租户配额管理。

Kueue 正是 Kubernetes 官方为此提供的解决方案。它不替换 kube-scheduler,只负责 Job 的排队和准入,在此基础上实现资源配额管理和公平调度。

1. Kueue 简介

Kueue 是什么?

Kueue 是 Kubernetes SIGs 维护的官方 Job 级队列管理系统,负责决定 Job 何时准入(Admit)、何时被驱逐(Preempt),核心目标是管理资源配额和多租户公平调度。

和 Volcano 最大的区别:Kueue 不替换 kube-scheduler,它只管"排队和准入",调度还是交给原生调度器。

Volcano = 自定义调度器 + 队列管理 + 作业管理
Kueue = 队列管理 + 配额管理(调度交给 kube-scheduler)

Kueue 能做什么?

  • 多租户配额管理:通过 ClusterQueue 为不同团队划分资源配额

  • 公平调度:基于 Dominant Resource Shares(DRS)的公平共享算法,防止资源被单一团队长期独占

  • 队列排队:Job 提交后按优先级排队,配额不足时挂起等待

  • Cohort 借调:同一 Cohort 内的 ClusterQueue 可以借用彼此空闲配额,用完归还

  • 弹性配额:支持 nominalQuota(保底)+ borrowingLimit(借用上限)+ lendingLimit(出借上限)三层额度控制

  • 标准兼容:原生支持 K8s Job / JobSet / PyTorchJob / TFJob / RayJob 等,无需改 Job YAML

为什么需要 Kueue?

或者说 Kueue 解决了什么原生 K8s 解决不了的问题?

原生 Kubernetes 的资源管理是"先到先得",没有队列的概念:

场景 原生 Kubernetes Kueue 优势
配额管理 ResourceQuota 粗粒度控制 ClusterQueue 细粒度配额(按 GPU 型号/节点池划分) 精确到资源类型和 Flavor
多租户公平 先到先得,无法保障公平 Fair Sharing 基于 DRS 的公平分配 防止资源独占
Job 排队 Pending 后只能等 支持优先级队列、FIFO 策略 按优先级有序调度
跨团队借调 不支持 Cohort 机制,空闲配额自动借出、按需归还 提高集群利用率

举一个典型场景:公司有两个 AI 团队共享一个 GPU 集群,各有 50% 的 A100 配额。团队 A 没任务时,团队 B 可以借用全部 GPU;团队 A 提交任务后,Kueue 会通过 preemption 让团队 B 释放借用的资源,回到公平状态。

原生 K8s 做不到这一点。

Kueue vs Volcano 快速对比

维度 Kueue Volcano
定位 队列 + 配额管理(轻量) 批处理调度平台(重量)
架构 不替换调度器,旁路管理 自定义调度器,替换 kube-scheduler
归属 Kubernetes SIGs 官方 CNCF(华为发起)
部署复杂度 一个 Controller Scheduler + Controller + Admission 三个组件
Gang Scheduling 通过 All-or-Nothing with Ready Pods(超时机制) 原生 Gang Scheduling
Job API 标准 K8s Job(自动创建 Workload) 自定义 VolcanoJob(vcjob)

更多参考:Kueue 官方文档、Volcano 系列文章

2. 核心概念

在部署之前,先理解 Kueue 的 5 个核心对象:

┌─────────────────────────────────────────────────────┐
│ Cohort │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ClusterQueue A │ │ ClusterQueue B │ │
│ │ (nominal: 5GPU) │ │ (nominal: 5GPU) │ │
│ │ borrowLimit: 5 │ │ borrowLimit: 5 │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ ResourceFlavor: A100 / T4 / CPU │
└─────────────────────────────────────────────────────┘
 │ │
 LocalQueue A LocalQueue B ← Namespaced
 (team-a) (team-b)
 │ │
 Workload 1 Workload 2 ← 用户的 Job
  • ResourceFlavor:Flavor,代表不同类型的资源(如 A100 vs T4、Spot vs On-Demand),可以绑定 nodeLabels / taints

  • ClusterQueue:集群级队列,定义资源配额(nominalQuota / borrowingLimit / lendingLimit),是配额管理的核心

  • LocalQueue:命名空间级队列,用户直接和它打交道,指向一个 ClusterQueue

  • Cohort:ClusterQueue 的组,同组内可以互相借调空闲配额

  • Workload:Kueue 的调度单元,Kueue 会为每个 Job 自动创建对应的 Workload,用户一般不需要手动创建

调度流程

一个 Job 从提交到运行,经过 Kueue 的完整流程:

用户提交 Job (带 kueue.x-k8s.io/queue-name 标签)
 ↓
Kueue 自动创建 Workload 对象
 ↓
Workload 进入 LocalQueue → 找到对应的 ClusterQueue
 ↓
检查 ClusterQueue 是否有足够配额?(允许从 Cohort 借调)
 ├── 没有 → 询问 Cluster Autoscaler 能否扩容? → 等待扩容
 ↓ (有配额 或 扩容完成)
检查 ClusterQueue 是否配置了 AdmissionCheck?
 ├── 有 → 等待所有 Check 通过 → 最终准入
 └── 没有 → 直接最终准入
 ↓
Kueue 注入 nodeAffinity/tolerations (锁定算力)
 ↓
Job Controller 创建 Pod
 ↓
Kube Scheduler 分配 Pod 到具体节点
 ↓
节点资源不足? → 触发 Cluster Autoscaler 真正扩容节点 (Provision)

kueue-workflow.svg

关键点:Kueue 只负责"准入"决策,Pod 真正调度到哪个节点还是 kube-scheduler 决定。

3. Kueue 部署

Kueue 部署非常轻量,只需要一个 Controller。

版本兼容性

Kueue 要求 Kubernetes 1.29+,本文使用 Kubernetes v1.36.1 部署最新的 Kueue v0.18.1。

安装

官方提供了两种安装方式,推荐 Helm:

方式一:kubectl 直接安装

kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.18.1/manifests.yaml

方式二:Helm 安装(推荐)

helm install kueue oci://registry.k8s.io/kueue/charts/kueue \
 --version=0.18.1 \
 --namespace kueue-system \
 --create-namespace \
 --wait --timeout 300s

无法访问 registry.k8s.io 可以从 GitHub 下载 chart 包安装:

helm install kueue https://github.com/kubernetes-sigs/kueue/releases/download/v0.18.1/kueue-0.18.1.tgz \
 --namespace kueue-system \
 --create-namespace \
 --wait --timeout 300s

卸载

helm uninstall kueue --namespace kueue-system

验证

kubectl -n kueue-system get po
NAME READY STATUS RESTARTS AGE
kueue-controller-manager-665fc6d58-whxcz 1/1 Running 0 60m

一个 Pod 就搞定了,这就是 Kueue 轻量的地方。

4. 快速上手:一个完整的 Demo

接下来我们跑一个最小化 Demo:创建 ResourceFlavor → ClusterQueue → LocalQueue → 提交 Job,完整走一遍 Kueue 的工作流程。

4.1 创建基础资源

Kueue 使用三种资源来管理作业排队:

  • ResourceFlavor:描述作业可用的硬件配置
  • ClusterQueue:定义资源池的配额(CPU、内存等)
  • LocalQueue:用户提交作业的命名空间级队列,映射到 ClusterQueue
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
 name: "default-flavor"
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
 name: "cluster-queue"
spec:
 namespaceSelector: {} # 允许所有命名空间
 resourceGroups:
 - coveredResources: ["cpu", "memory"]
 flavors:
 - name: "default-flavor"
 resources:
 - name: "cpu"
 nominalQuota: 9
 - name: "memory"
 nominalQuota: 9Gi
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
 namespace: "default"
 name: "user-queue"
spec:
 clusterQueue: "cluster-queue"
kubectl apply -f queue.yaml

4.2 验证队列状态

# ClusterQueue 是否激活
kubectl get clusterqueue cluster-queue -o wide

# LocalQueue 是否就绪
kubectl get localqueue user-queue -n default
root@lixd-dev-4:~# kubectl get clusterqueue cluster-queue -o wide
NAME COHORT STRATEGY PENDING WORKLOADS ADMITTED WORKLOADS
cluster-queue BestEffortFIFO 0 0

root@lixd-dev-4:~# kubectl get localqueue user-queue -n default
NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS
user-queue cluster-queue 0 0

队列创建成功,当前没有 Workload。

4.3 提交 Job

注意看,这里用的就是标准的 K8s Job,只需要加一个 label 就能接入 Kueue:

apiVersion: batch/v1
kind: Job
metadata:
 name: sample-job
 namespace: default
 labels:
 kueue.x-k8s.io/queue-name: user-queue # 接入 Kueue 的关键
spec:
 parallelism: 3
 completions: 3
 template:
 spec:
 containers:
 - name: dummy-job
 image: registry.k8s.io/e2e-test-images/agnhost:2.53
 command: ["/bin/sh"]
 args: ["-c", "sleep 30"]
 resources:
 requests:
 cpu: "1"
 memory: "200Mi"
 restartPolicy: Never
kubectl apply -f job.yaml

4.4 验证

# 查看 Kueue 自动创建的 Workload
kubectl get workloads -n default

# 查看 Pod
kubectl get pods -n default
root@lixd-dev-4:~# kubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-sample-job-555d8 user-queue cluster-queue True 9s

root@lixd-dev-4:~# kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
sample-job-j6xdp 1/1 Running 0 14s
sample-job-k4mtm 1/1 Running 0 14s
sample-job-nvjkz 1/1 Running 0 14s

可以看到 Kueue 自动为 Job 创建了对应的 Workload,Workload 状态为 Admitted(已准入),3 个 Pod 正常运行。

4.5 等待任务完成

# 等待任务完成
kubectl get jobs -n default -w
root@lixd-dev-4:~# kubectl get jobs -n default -w
NAME STATUS COMPLETIONS DURATION AGE
sample-job Complete 3/3 34s 44s

Job 完成后,Workload 也会被标记为 Finished:

kubectl get workloads -n default
root@lixd-dev-4:~# kubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-sample-job-555d8 user-queue cluster-queue True True 49s

到这里,我们完整走了一遍 Kueue 的工作流程:创建队列 → 提交 Job → Kueue 自动准入 → Pod 运行 → 任务完成。

整个过程中,我们用的是标准的 K8s Job,唯一的变化就是加了一个 kueue.x-k8s.io/queue-name 标签。这就是 Kueue "不替换调度器、旁路管理"设计哲学的体现。

5. 小结

Kueue 是 Kubernetes SIGs 官方出品的 Job 队列与配额管理系统,它的核心设计哲学是"只管排队和准入,不动调度器",这让它非常轻量且对现有集群侵入性极小。

本文介绍了 Kueue 的背景、核心概念和部署方式,并通过一个最小化 Demo 走完了"创建队列 → 提交 Job → 自动准入 → 任务完成"的完整流程。

下一篇我们将深入解析 Kueue 的五大核心对象(ResourceFlavor / ClusterQueue / LocalQueue / Cohort / Workload),搞清楚它们各自的职责和配置细节。

← 목록으로