很多人刚接触 Kueue 时最大的困惑,不是 YAML 怎么写,而是看着一堆 CRD:ResourceFlavor、ClusterQueue、LocalQueue、Cohort、Workload,不知道它们之间到底是什么关系。本文不会逐个照着 API 文档介绍字段,而是把这五个对象放到同一条资源准入链路中,一次讲清楚它们各自负责什么、为什么要存在,以及它们之间如何协作。
上一篇文章中分析了 Kueue 的完整工作流:
涉及到多个核心对象,下面逐个拆开讲。
1. ResourceFlavor:资源长什么样
Kueue 要管资源,第一步得知道集群里有哪些"种类"的资源。集群里的 CPU、GPU 通常不是同构的:
- 价格和可用性 :竞价型 vs 按需型虚拟机
- 架构 :x86 vs ARM CPU
- 品牌和型号 :Nvidia A100 vs T4 GPU
ResourceFlavor 就是干这个的------给资源分个类,贴个标签,后面 ClusterQueue 按这个分类来管配额。
Kueue 在 Admission 阶段,会根据 ClusterQueue 中配置的 Flavor 顺序、Quota 是否满足、Flavor 是否匹配 Pod 等因素,选择一个可用的 ResourceFlavor。
空 Flavor
如果集群资源是同构的,或者不需要为不同资源规格分别管理配额,那直接创建一个不包含任何标签或污点的空 ResourceFlavor 即可:
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: default-flavor
这类 ResourceFlavor 不承担节点筛选或污点控制的职责,仅作为统一的资源抽象存在,方便后续扩展不同资源类型。
只分类不加污点
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: "gpu"
spec:
nodeLabels:
instance-type: gpu
新增参数:
spec.nodeLabels:通过 label 关联到对应的节点。具体实现上则是通过将 flavor 中 nodeLabels 部分自动注入到 Pod Spec 中的 nodeSelector,从而达到只将 Pod 调度到该 flavor 关联的节点上。
这类 Flavor 实现了根据 label 将节点进行分类,算是名副其实。管理员先给节点打上对应 label 即可实现分类:
bash
root@lixd-dev-4:~# kubectl label node lixd-dev-4 instance-type=gpu
node/lixd-dev-4 labeled
但也只是简单做了分类,不是该 Flavor 中的 Job 也能手动调度到这些 GPU 节点。比较推荐的做法是给节点再打上污点,这样就不是随便能调度上去了:
bash
kubectl taint node xxx nvidia.com/gpu=true:NoSchedule
节点打上污点后,Flavor 中的 Job 也不能调度了。Kueue 提供了两种模式来解决这个问题:
- 自动容忍模式 :将污点容忍信息写到 ResourceFlavor,Kueue 自动给使用该 Flavor 的 Pod 加上容忍
- 手动容忍模式 :将污点信息写到 ResourceFlavor,Kueue 做匹配拦截,只允许带了对应容忍的 Pod 使用该 Flavor
带污点
自动污点容忍调度
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: "spot"
spec:
nodeLabels:
instance-type: spot
tolerations:
- key: "spot-taint" # 节点上已有污点的 key
operator: "Exists"
effect: "NoSchedule" # 支持 NoSchedule 和 NoExecute
新增参数:
spec.tolerations:声明该 flavor 关联节点上的污点容忍信息。Kueue 准入时自动注入到 Pod 的.spec.template.spec.tolerations,确保 Pod 能容忍节点污点从而正常调度。
手动污点容忍
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: "spot"
spec:
nodeLabels:
instance-type: spot
nodeTaints:
- effect: NoSchedule # 只支持 NoSchedule 和 NoExecute,PreferNoSchedule 会被忽略
key: spot
value: "true"
新增参数:
spec.nodeTaints:在 flavor 上定义准入门槛。Kueue 不会自动注入容忍度,Pod 必须自己带对应 toleration 才能通过准入、拿到配额。
用户提交 Job 时必须自己带上 toleration,否则 Kueue 不批配额:
yaml
spec:
template:
spec:
tolerations:
- key: spot
operator: Exists
effect: NoSchedule
注意 :
spec.nodeTaints通常应与节点上的真实污点保持一致。否则可能出现 Pod 过了 Kueue 准入、拿到配额,但 K8s 调度器发现节点有真污点而 Pod 没对应 toleration,最终调度失败------白白占了配额。本质就是把调度层面的拦截提前到 Kueue 准入层面。
两种模式怎么选
自动模式(spec.tolerations) |
手动模式(spec.nodeTaints) |
|
|---|---|---|
| 谁给 Pod 加 toleration | Kueue 自动注入 | 用户自己写 |
| 用户需要关心节点污点吗 | 不需要,提交 Job 就行 | 必须自己写 toleration |
| 用在哪 | 生产默认,对用户最友好 | 保护昂贵资源,强制用户显式声明 |
选型建议:优先用自动模式;只有当你需要强制用户显式声明才能使用某种昂贵资源时,才用手动模式。
一句话记住:ResourceFlavor 定义资源的属性(标签、污点、容忍等),真正决定资源配额和公平性的仍然是 ClusterQueue。
2. ClusterQueue:谁是配额的真正管家
如果 ResourceFlavor 是给资源分类,那真正决定谁能用、用多少的是谁?答案就是 ClusterQueue。它才是 Kueue 配额管理的核心,定义了使用上限和公平共享规则。
基础用法
一个基础的 ClusterQueue 示例如下:
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: "cluster-queue"
spec:
namespaceSelector: {} # 匹配所有命名空间。
resourceGroups:
- coveredResources: ["cpu", "memory", "pods"]
flavors:
- name: "default-flavor"
resources:
- name: "cpu"
nominalQuota: 9
- name: "memory"
nominalQuota: 36Gi
- name: "pods"
nominalQuota: 5
字段解析:
spec.namespaceSelector:指定该 ClusterQueue 可以在哪些 namespace 被使用。当前 ClusterQueue 可以被任意 Namespace 使用。resourceGroups:资源组,定义 ClusterQueue 的资源额度,可以有多个,每个组都是独立的。coveredResources:管哪些资源,同一个 group 里面的资源必须在同一个 flavor 里面分配。flavors:可以有多个,分配是按顺序尝试。name:使用名称引用前面创建的 ResourceFlavor。resources:在这个 flavor 下,每种资源的配额上限。nominalQuota:保底配额,表示该 ClusterQueue 的名义配额,优先保障自身使用;未使用的部分可以按照 borrowing/lending 规则被其他 Queue 借用。borrowingLimit:你最多能从 Cohort 里借入多少,所以你最多能用 nominalQuota + borrowingLimit。lendingLimit:你最多允许别人从你这借走多少(当你空闲时),需要时可通过预抢占收回。
以 cpu 为例,基于上面三个配额字段,你可以使用的范围是 nominalQuota ~ (nominalQuota + borrowingLimit)。
高级用法
在基础配置之上,ClusterQueue 还支持以下进阶字段,按用途分组:
调度顺序:排队策略
yaml
spec:
queueingStrategy: BestEffortFIFO
BestEffortFIFO(默认) :前面的 Job 拿不到配额,后面的能插队先跑,资源利用率高。StrictFIFO:前面的 Job 拿不到配额,后面的必须等,适合有顺序依赖的 Pipeline。
资源共享:加入 Cohort
yaml
spec:
cohortName: research-cohort
cohortName:关联到一个 Cohort,同一个 Cohort 里的 ClusterQueue 可以互相共享资源。
抢占相关
当 ClusterQueue 或其 Cohort 中没有足够的配额时,新进入的 Workload 可以触发预抢占,挤掉低优先级的 Workload。涉及两个配置项:
预抢占策略
yaml
spec:
preemption:
reclaimWithinCohort: Any # 可抢占 Cohort 中超过名义配额的 Workload
borrowWithinCohort:
policy: LowerPriority # 借用时只抢占低优先级(不能与 Fair Sharing 一同使用)
maxPriorityThreshold: 100 # 且仅抢占优先级 ≤ 100 的 Workload
withinClusterQueue: LowerPriority # 同队列内,低优先级让路给高优先级
withinClusterQueue:同队列内,当待处理 Workload 不适合配额时,是否可抢占同队列中的活动 Workload。Never(默认)不抢占;LowerPriority仅抢占低优先级;LowerOrNewerEqualPriority抢占低优先级或同优先级的。reclaimWithinCohort:是否可抢占 Cohort 中使用了超过其名义配额的 Workload。Never(默认)不抢占;LowerPriority仅抢占低优先级;Any可抢占任意优先级。borrowWithinCohort:当需要借用配额时是否触发抢占。Never(默认)不触发;LowerPriority仅抢占 Cohort 中低优先级的 Workload(需同时启用reclaimWithinCohort)。注意:只能配置经典抢占,不能与 Fair Sharing 一同使用。
优先抢占还是借用
当 ClusterQueue 有多个 flavor 时,Kueue 按顺序尝试。当当前 flavor 配额用完时,你可以影响 Kueue 的行为:
whenCanBorrow:能从 Cohort 借的时候。MayStopSearch(默认):借了就停,不再试下一个;TryNextFlavor:不借,先试下一个 flavor。whenCanPreempt:能抢占低优先级 Job 的时候。TryNextFlavor(默认):先试下一个 flavor;MayStopSearch:不试了,直接抢占。
yaml
spec:
flavorFungibility:
whenCanBorrow: TryNextFlavor
whenCanPreempt: MayStopSearch
运维控制:停止策略
控制队列的运行状态,ClusterQueue 和 LocalQueue 都支持。
None(默认) :正常运行,新 Job 正常准入,已运行的不受影响。Hold:停止新的准入,已准入的不受影响,适合维护、配额调整等临时场景。HoldAndDrain:停止新的准入 + 触发已准入工作负载的驱逐,适合紧急情况需要立刻清场。
维护完恢复为 None 或直接删掉这个字段即可。这是运维操作,不是常态配置。
yaml
spec:
stopPolicy: Hold
把上面的都放在一起
一个包含所有字段的完整 ClusterQueue:
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: ai-team-cq
spec:
# 1. 加入 Cohort,允许和其他 ClusterQueue 共享资源
cohortName: research-cohort
# 2. 排队策略
queueingStrategy: BestEffortFIFO # 默认。StrictFIFO 会阻塞
# 3. 命名空间选择器(谁能用这个 ClusterQueue)
namespaceSelector:
matchLabels:
team: ai
# 4. 抢占策略
preemption:
reclaimWithinCohort: Any # 可抢占 Cohort 中超过名义配额的 Workload
borrowWithinCohort:
policy: LowerPriority # 借用时只抢占低优先级(不能与 Fair Sharing 一同使用)
maxPriorityThreshold: 100 # 且仅抢占优先级 ≤ 100 的 Workload
withinClusterQueue: LowerPriority # 同队列内,低优先级让路给高优先级
# 5. 配置是优先抢占还是借用
flavorFungibility:
whenCanBorrow: TryNextFlavor
whenCanPreempt: MayStopSearch
# 6. 停止策略
# stopPolicy: Hold
# 7. 资源组定义
resourceGroups:
- coveredResources: ["cpu", "memory"] # 第一组:计算资源
flavors:
- name: default-flavor
resources:
- name: cpu
nominalQuota: 16 # 保底配额
borrowingLimit: 8 # 最多能从 Cohort 借多少
lendingLimit: 8 # 最多允许别人借多少
- name: memory
nominalQuota: 64Gi
borrowingLimit: 32Gi
lendingLimit: 32Gi
- coveredResources: ["nvidia.com/gpu"] # 第二组:GPU 资源
flavors:
- name: a100
resources:
- name: nvidia.com/gpu
nominalQuota: 4
borrowingLimit: 2
- name: t4
resources:
- name: nvidia.com/gpu
nominalQuota: 8
一句话记住:ClusterQueue 才是真正管配额和公平性的地方。 第一次看会和 LocalQueue 搞混,其实记住一句就够了------ClusterQueue 管资源,LocalQueue 管用户。
3. LocalQueue:用户怎么进队列
ClusterQueue 是集群级别的,但用户不能直接往里塞 Job。中间还需要一层 LocalQueue------它是一个命名空间对象,指向一个 ClusterQueue,作为用户提交 Job 的入口。
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
namespace: team-a
name: team-a-queue
spec:
clusterQueue: cluster-queue
注意:
kubectl get queues是kubectl get localqueues的别名,更方便日常使用。
LocalQueue vs ClusterQueue
| 维度 | LocalQueue | ClusterQueue |
|---|---|---|
| 作用域 | 命名空间 | 集群 |
| 谁创建 | 团队自己 | 批处理管理员 |
| 职责 | 将同一租户的工作负载分组,指向一个 ClusterQueue | 管理资源池的配额和公平共享规则 |
| 一对一? | 多个 LocalQueue 可以指向同一个 ClusterQueue | --- |
text
命名空间 team-a:
LocalQueue: training-queue ──┐
LocalQueue: inference-queue ──┼──→ ClusterQueue: ai-team-cq
命名空间 team-b: │
LocalQueue: batch-queue ──────┘
一句话记住:LocalQueue 就是 Namespace 访问 ClusterQueue 的入口。 它不管配额,只管"我这个 namespace 的 Job 往哪个 ClusterQueue 送"。
4. Cohort:队列之间怎么借资源
如果每个 ClusterQueue 只能用自己的保底配额,那 team-a 闲着 3 核、team-b 想多跑 1 核也借不到,资源就浪费了。Cohort 就是解决这个的------可以理解成一个"资源共享联盟",加进同一个 Cohort 的 ClusterQueue 可以互相借配额。
第一次看 Cohort 我也没太理解。后来发现,它本身还能定义 resourceGroups(共享配额池),这些资源是管理员额外划拨给整个联盟的公共池,而不是把各个 ClusterQueue 的 nominalQuota 自动汇总得到的。
基本用法
yaml
# 创建 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: hello-cohort
spec:
resourceGroups:
- coveredResources: ["cpu"]
flavors:
- name: default-flavor
resources:
- name: cpu
nominalQuota: 12 # Cohort 级别的共享配额(额外资源)
---
# ClusterQueue A 加入 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-a-cq
spec:
cohortName: hello-cohort
resourceGroups:
- coveredResources: ["cpu"]
flavors:
- name: default-flavor
resources:
- name: cpu
nominalQuota: 4
borrowingLimit: 4 # 最多借 4 核
lendingLimit: 2 # 最多出借 2 核
---
# ClusterQueue B 也加入同一个 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-b-cq
spec:
cohortName: hello-cohort
resourceGroups:
- coveredResources: ["cpu"]
flavors:
- name: default-flavor
resources:
- name: cpu
nominalQuota: 4
borrowingLimit: 4
lendingLimit: 2
上面的配置意味着:
- 两个队列各有 4 核保底,Cohort 自己配置的一组共享配额 12 核。(总计可用 20 核)
- 空闲时可以互相借用
- 如果 ClusterQueue 想从 Cohort 借用资源,它必须 为该资源和 Flavor 定义
nominalQuota(即使值为 0)
分层 Cohort(Hierarchical Cohorts)
Cohort 可以组织成树形结构(CohortTree),适合大型组织:
yaml
# 根 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: root-cohort
---
# AI 部门 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: ai-dept
spec:
parentName: root-cohort # 父节点
fairSharing:
weight: "0.75"
---
# 大数据部门 Cohort
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: bigdata-dept
spec:
parentName: root-cohort # 父节点
fairSharing:
weight: "0.25"
text
root-cohort(总资源池)
├── ai-dept(权重 0.75 → 趋向使用 75% 公共资源)
│ ├── team-a-cq
│ └── team-b-cq
└── bigdata-dept(权重 0.25 → 趋向使用 25% 公共资源)
├── spark-cq
└── flink-cq
同一个 CohortTree 中的 ClusterQueue 可以使用其中的资源,遵循为 Cohort 和 ClusterQueue 指定的借用和借出限制。
一句话记住:Cohort 让多个 ClusterQueue 可以互相借资源。
5. Workload:Kueue 真正在调度什么
前面四个都是"配置",那 Kueue 真正调度、准入的对象是什么?是 Workload------一个要运行至完成的应用,由一个或多个 Pod 组成。Kueue 的 Admission、Quota Accounting、Preemption 全是围绕它展开的。
通常你不需要手动创建 Workload,Kueue 会为每个 Job 自动创建。但理解它的结构有助于排查问题。
关键字段
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: Workload
metadata:
name: sample-job
namespace: team-a
spec:
active: true # 设为 false 可停止 Workload(已运行的会被驱逐且不重新排队)
queueName: team-a-queue # 指向 LocalQueue
podSets: # Pod 组定义(Kueue 从 Job 自动提取)
- name: main
count: 3 # Pod 数量
template:
spec:
containers:
- name: container
image: registry.k8s.io/e2e-test-images/agnhost:latest
resources:
requests:
cpu: "1"
memory: 200Mi
maximumExecutionTimeSeconds: 3600 # Workload 处于 Admitted 状态超过此秒数后自动停用
优先级
Workload 的优先级影响准入顺序,有两种设置方式:
- Pod 优先级 :对于
batch/v1.Job,Kueue 根据 Job 的 Pod 模板的 Pod 优先级设置 Workload 的优先级 - WorkloadPriorityClass :独立管理工作负载的排队和抢占优先级,与 Pod 调度优先级分离
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
name: high-priority
value: 1000
description: "高优先级训练任务"
使用方式:在 Job 的 label 中指定:
yaml
metadata:
labels:
kueue.x-k8s.io/priority-class: high-priority
资源请求计算
Kueue 将 Workload 的总资源使用量计算为每个 podSet 资源请求的总和:
podSet 的资源使用量 = Pod 规格的资源请求 × count
Kueue 会根据 Limit Ranges 和 Runtime Class Overhead 调整资源使用量。
一句话记住:Workload 才是 Kueue 真正调度和准入的对象,前面四个对象都是给它配规则的。
6. Demo:串联五大核心对象
理论讲完了,下面用一个 demo 把五大核心对象串起来,验证配额管控和 Cohort 借用能力。环境是一个单节点 K8s 集群(8 核 CPU)+ Kueue v0.18.1。
text
Cohort(team-ab)
├── team-a-cq(保底 4 核,最多借到 6 核)
│ └── team-a-queue
└── team-b-cq(保底 2 核,最多借到 5 核)
└── team-b-queue
Step 1:给节点打 label 和 taint
bash
# 打 label,标记节点类型
kubectl label node lixd-dev-4 node-type=cpu
# 加 taint,防止没有容忍的 Pod 调度上来
kubectl taint node lixd-dev-4 dedicated=team:NoSchedule
Step 2:创建 ResourceFlavor(tolerations 自动模式)
yaml
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: cpu-flavor
spec:
nodeLabels:
node-type: cpu # 匹配节点
tolerations: # Kueue 自动注入 Pod,容忍节点污点
- key: dedicated
operator: Exists
effect: NoSchedule
Step 3:创建 Cohort + 两个 ClusterQueue + 两个 LocalQueue
yaml
# Cohort:让两个队列共享配额
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
name: team-ab
---
# team-a:保底 4 核,最多借到 6 核
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-a-cq
spec:
cohortName: team-ab
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "pods"]
flavors:
- name: cpu-flavor
resources:
- name: cpu
nominalQuota: 4
borrowingLimit: 2
- name: pods
nominalQuota: 3
---
# team-b:保底 2 核,最多借到 5 核
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: team-b-cq
spec:
cohortName: team-ab
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "pods"]
flavors:
- name: cpu-flavor
resources:
- name: cpu
nominalQuota: 2
borrowingLimit: 3
- name: pods
nominalQuota: 3
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
namespace: default
name: team-a-queue
spec:
clusterQueue: team-a-cq
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
namespace: default
name: team-b-queue
spec:
clusterQueue: team-b-cq
Step 4:提交 Job A(team-a,3 核,保底内)
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: job-a
labels:
kueue.x-k8s.io/queue-name: team-a-queue
spec:
template:
spec:
containers:
- name: sleep
image: busybox
command: ['sleep', '30']
resources:
requests:
cpu: '3'
restartPolicy: Never
30 秒后 Job A 跑完,验证 Kueue 自动注入了 toleration 和 nodeSelector:
bash
$ kubectl get pod job-a-<pod-suffix> -o jsonpath='{.spec.tolerations[0]}'
{"effect":"NoSchedule","key":"dedicated","operator":"Exists"} # ← 自动注入的
$ kubectl get pod job-a-<pod-suffix> -o jsonpath='{.spec.nodeSelector}'
{"node-type":"cpu"} # ← 自动注入的
Step 5:提交 Job B(team-b,3 核,需要借用)
team-b 保底只有 2 核,要 3 核需要从 team-a 借 1 核空闲配额(此时 Job A 还在跑,用了 3 核,team-a 空闲 1 核)。
此时提交 JobB:
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: job-b
labels:
kueue.x-k8s.io/queue-name: team-b-queue
spec:
template:
spec:
containers:
- name: sleep
image: busybox
command: ['sleep', '30']
resources:
requests:
cpu: '3'
restartPolicy: Never
借用过程:team-a 保底 4 核用了 3 核,空闲 1 核 → team-b 保底 2 核不够(要 3 核)→ 从 team-a 借了 1 核 → 准入成功。
30 秒后 Job B 跑完,team-b 的 3 核配额归还。等 Job A 也跑完后,整个 Cohort 就空了:team-a 保底 4 核全空闲,team-b 保底 2 核全空闲,总共 6 核可借。
Step 6:提交 Job D(team-b,5 核,测试 borrowingLimit 上限)
Job A、Job B 都已跑完,配额全部归还。现在 team-b 最多能用保底 2 + 借 3 = 5 核(team-a 空闲 4 核,足够借)。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: job-d
labels:
kueue.x-k8s.io/queue-name: team-b-queue
spec:
template:
spec:
containers:
- name: sleep
image: busybox
command: ['sleep', '120']
resources:
requests:
cpu: '5'
restartPolicy: Never
结果:
bash
$ kubectl get workloads
NAME QUEUE ADMITTED
job-job-d-fd3f3 team-b-queue True # 保底2+借3=5,刚好到上限,准入
Step 7:提交 Job E(team-b 再多 1 核,超过 borrowingLimit)
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: job-e
labels:
kueue.x-k8s.io/queue-name: team-b-queue
spec:
template:
spec:
containers:
- name: sleep
image: busybox
command: ['sleep', '120']
resources:
requests:
cpu: '1'
restartPolicy: Never
结果:
bash
$ kubectl get workloads
NAME QUEUE ADMITTED
job-job-d-fd3f3 team-b-queue True # 5核已用满
job-job-e-918d8 team-b-queue False # 再多 1 核,Pending
$ kubectl get workload job-job-e-918d8 -o jsonpath='{.status.conditions[0].message}'
insufficient unused quota for cpu in flavor cpu-flavor, 1 more needed
# team-b 最多 5 核,Job D 已用 5 核,没有余量了
小结
| Job | 团队 | 请求 CPU | 保底 | 借用 | 结果 |
|---|---|---|---|---|---|
| Job A | team-a | 3 核 | 4 核 | 0 | ✅ 准入 |
| Job B | team-b | 3 核 | 2 核 | 借 1 核 | ✅ 准入 |
| Job D | team-b | 5 核 | 2 核 | 借 3 核 | ✅ 准入(到 borrowingLimit 上限) |
| Job E | team-b | 1 核 | 已满 | 无法借 | ❌ Pending |
5 个核心对象在这里全部串联起来了。
7. 小结
Kueue 的核心,其实不是"调度算法",而是围绕 Workload 的准入与资源分配模型。
五个对象分别扮演了不同角色,但可以用一句话快速串起来:
- ResourceFlavor :定义"资源长什么样"(节点/污点/标签)
- ClusterQueue :定义"资源怎么分、给谁多少"
- LocalQueue :定义"哪个命名空间用哪个资源池"
- Cohort :定义"多个队列之间如何共享资源"
- Workload :真正被调度和准入的执行单元
第一次接触 Kueue 时,很容易把这些对象看成彼此独立的 CRD,但实际上它们共同组成了一条完整的资源准入链路:
bash
Job → LocalQueue → ClusterQueue → ResourceFlavor → Admission → Workload
理解了这条链路,再去看 Borrowing、Fair Sharing、Preemption 等高级能力,就会发现它们都只是围绕这套模型做扩展,而不是新的概念。