AI/ML 训练和批处理任务有一个共同诉求:要么所有 Pod 同时跑起来,要么谁也别跑。Kubernetes 一直按 Pod 逐个调度,遇到这种"全有或全无"的场景就容易卡死——3 个 Pod 占了资源,第 4 个没位置,前 3 个白占着等,别人也用不上。v1.35 引入了 Workload API 和初步的 gang scheduling,但把运行状态塞进了 Workload 对象里,耦合重、扩展难。v1.36 做了一次架构拆分:Workload 只做静态模板,PodGroup 独立管运行状态,调度器有了专门的 PodGroup 调度周期,一口气把整组 Pod 的放置算完再原子提交。同时拓扑感知调度、工作负载感知抢占、DRA ResourceClaim 与 PodGroup 的打通也首次亮相。下面逐层拆解。
模板与运行态分离:Workload + PodGroup
v1.35 的 Workload 对象把 PodGroup 的 spec 和 status 都嵌在一起。一个 1000 Pod 的训练任务,每次状态更新都要改同一个对象,性能和扩展性都受限。v1.36 把两者拆开:
- Workload(
scheduling.k8s.io/v1alpha2):纯模板,定义 PodGroup 的 spec 模板,调度器不需要 watch 它。 - PodGroup(
scheduling.k8s.io/v1alpha2):运行态对象,持有实际调度策略和状态,按 replica 分片更新 status。
调度器只读 PodGroup 就够了,逻辑更干净。v1alpha1 整个被替换,不再兼容。
一个最小示例——定义一个要求 4 Pod 同时启动的 gang 调度 Workload 模板:
apiVersion: scheduling.k8s.io/v1alpha2
kind: Workload
metadata:
name: training-job-workload
namespace: some-ns
spec:
podGroupTemplates:
- name: workers
schedulingPolicy:
gang:
minCount: 4
控制器根据模板 stamp 出运行态 PodGroup:
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: training-job-workers-pg
namespace: some-ns
spec:
podGroupTemplateRef:
workload:
workloadName: training-job-workload
podGroupTemplateName: workers
schedulingPolicy:
gang:
minCount: 4
status:
conditions:
- type: PodGroupScheduled
status: "True"
lastTransitionTime: "2026-04-03T00:00:00Z"
Pod 通过新的 schedulingGroup 字段(替代了旧的 workloadRef)挂到 PodGroup:
apiVersion: v1
kind: Pod
metadata:
name: worker-0
namespace: some-ns
spec:
schedulingGroup:
podGroupName: training-job-workers-pg
containers:
- name: trainer
image: registry.example/trainer:latest
这套拆分让大规模工作负载的状态更新不再打在同一个 Workload 对象上,分片写入 PodGroup status,性能和可扩展性都更好。
PodGroup 调度周期:原子决策,不再逐 Pod 碰运气
旧流程是调度器从队列里逐个 pop Pod,逐个找节点、逐个 reserve。如果 4 个 Pod 属于同一个 gang,第 1 个 reserve 了节点 A 的资源,第 2 个 reserve 了节点 B,到第 4 个发现资源不够——前 3 个已经占着位置,谁也跑不了,谁也释放不了。
v1.36 的 PodGroup 调度周期改了这件事:
- 调度器从队列 pop 出一个 PodGroup 成员时,把同组所有排队 Pod 一并取出,按确定性顺序排序。
- 取一次集群状态快照,避免中间被别人改了。
- 用 PodGroup 调度算法(仍走标准的 filter + score 流程)为整组找放置方案。
- 原子提交:满足 minCount → 可调度 Pod 一起进入 binding;不满足 → 全组返回队列,等资源释放后重试。
关键细节:已经绑定节点的 Pod 不会被撤销或驱逐,即使后续周期里组没凑齐。新加入的 Pod 会在调度时考虑已就位成员,但不会把已就位成员踢掉。
当前限制
- 同质 PodGroup(所有 Pod 资源需求相同、无亲和性/反亲和性/拓扑约束)——算法能找到解就一定会找到。
- 异质 PodGroup 或有 Pod 间依赖的组——不保证一定能找到解,即使解看起来"显而易见"。
- 组内亲和性依赖——由于确定性处理顺序,可能无论集群状态如何都找不到放置。
这些限制意味着第一版更适合"同质 gang"场景,复杂拓扑依赖的异质组还需要后续版本改进。
拓扑感知调度:把 Pod 按机架拢在一起
分布式训练对网络延迟敏感。Pod 随机散布在不同机架,AllReduce 的梯度同步会被跨机架带宽卡住。拓扑感知调度允许在 PodGroup 上直接声明拓扑约束:
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: topology-aware-workers-pg
spec:
schedulingPolicy:
gang:
minCount: 4
schedulingConstraints:
topology:
- key: topology.kubernetes.io/rack
调度器的工作流程:
- 生成候选放置——通过新的
PlacementGenerate扩展点,按拓扑约束筛选出节点子集组合。 - 评估可行性——确认整组 PodGroup 能在某个放置里全部放下。
- 评分选优——通过新的
PlacementScore扩展点,对可行放置按资源利用效率打分,选最优。
当前版本不支持为满足拓扑约束而触发抢占,后续版本会和工作负载感知抢占整合。多级拓扑、软约束偏好、与 DRA 的深度联动也在规划中。
工作负载感知抢占:整组抢、整组让
默认抢占是逐 Pod 评估每个节点上的受害者。对 PodGroup 来说这不够——可能需要在 3 个节点上各腾一点空间,才能把整组放进去。
v1.36 的工作负载感知抢占把 PodGroup 当一个抢占单位,跨全集群搜索受害者组合,可以同时从多个节点抢占 Pod,腾出足够空间后再整组放入。
两个新概念直接加在 PodGroup API 上:
- PodGroup priority——覆盖组内各 Pod 的单独优先级,抢占评估时用组优先级。
- PodGroup disruptionMode——控制组内 Pod 是否可以被独立抢占,还是必须"全有或全无"地一起被抢占。
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: victim-pg
spec:
priorityClassName: high-priority
priority: 1000
disruptionMode: PodGroup
disruptionMode: PodGroup 意味着抢占评估时,要么整组一起被驱逐,要么谁也不动。这防止了"只抢了组里一半 Pod,剩下的半残运行"的情况。目前这两个字段只在工作负载感知抢占中被尊重,后续会扩展到默认抢占等场景。
DRA ResourceClaim 与 PodGroup 打通
DRA(Dynamic Resource Allocation)从 v1.34 GA 后,Pod 可以通过 ResourceClaim 或 ResourceClaimTemplate 请求 GPU/TPU/NIC 等设备。ResourceClaimTemplate 会为每个 Pod 生成一个独立 ResourceClaim,但大规模工作负载里"某些 Pod 共享某些设备"的场景,之前只能手动管理。
v1.36 让 PodGroup 成为 ResourceClaimTemplate 的复制单元——一个 PodGroup 只生成一个 ResourceClaim,组内所有 Pod 共享它:
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: training-job-workers-pg
spec:
resourceClaims:
- name: pg-claim
resourceClaimTemplateName: my-claim-template
---
apiVersion: v1
kind: Pod
metadata:
name: topology-aware-workers-pg-pod-1
spec:
schedulingGroup:
podGroupName: training-job-workers-pg
resourceClaims:
- name: pg-claim
resourceClaimTemplateName: my-claim-template
---
apiVersion: v1
kind: Pod
metadata:
name: topology-aware-workers-pg-pod-2
spec:
schedulingGroup:
podGroupName: training-job-workers-pg
resourceClaims:
- name: pg-claim
resourceClaimTemplateName: my-claim-template
Pod 的 resourceClaimTemplateName 匹配 PodGroup 的声明时,Pod 直接引用 PodGroup 生成的那个 ResourceClaim,不再为 Pod 单独生成。
另一个关键改进:ResourceClaim 的 status.reservedFor 之前只能列单个 Pod(上限 256 条目)。现在一条 PodGroup 引用就能代表远超 256 个 Pod,高基数设备共享不再是瓶颈。
Job 控制器原生集成:不用手写 Workload 了
v1.36 的 Job 控制器可以自动为符合条件的 Job 创建 Workload 和 PodGroup,Pod 自动带上 schedulingGroup,不再需要额外工具或手动接线。
启用 WorkloadWithJob feature gate 后,Job 控制器会:
- 为符合条件的 Job 创建 Workload + PodGroup。
- 在每个 Pod 上设置
.spec.schedulingGroup。 - 设置 Job 为 owner,Job 删除时自动垃圾回收。
触发条件(必须全部满足):
spec.parallelism > 1spec.completionMode = Indexedspec.completions == spec.parallelism- Pod 模板上
schedulingGroup未被设置
这些条件描述的是"形状固定、每个 Pod 有稳定身份、gang 大小在准入时已知"的 Job。不符合条件的 Job 按原来的逐 Pod 调度,行为不变。如果你已经在 Pod 模板上设了 schedulingGroup(比如有更上层控制器在管),Job 控制器不会覆盖,安全地与外部批处理系统共存。
一个符合条件的最小 Job 示例:
apiVersion: batch/v1
kind: Job
metadata:
name: training-job
namespace: job-ns
spec:
completionMode: Indexed
parallelism: 4
completions: 4
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: registry.example/trainer:latest
Job 控制器会自动创建 Workload 和 PodGroup,4 个 Pod 通过 PodGroup 调度周期一起放入——凑齐 4 个才绑定,凑不齐就全组等。
如何在测试集群里试用
所有功能在 v1.36 都是 Alpha,需要手动开启。以下是一份完整的 feature gate 配置清单:
前置条件——在 kube-apiserver 和 kube-scheduler 上启用:
# kube-apiserver 启动参数
--feature-gates=GenericWorkload=true
# 确保 scheduling.k8s.io/v1alpha2 API group 已启用
# kube-scheduler 启动参数
--feature-gates=GenericWorkload=true
各子功能——在对应组件上额外启用:
| 功能 | Feature Gate | 启用位置 |
|---|---|---|
| Gang 调度 | GangScheduling |
kube-scheduler |
| 拓扑感知调度 | TopologyAwareWorkloadScheduling |
kube-scheduler |
| 工作负载感知抢占 | WorkloadAwarePreemption |
kube-scheduler(需同时开 GangScheduling) |
| DRA ResourceClaim 支持 | DRAWorkloadResourceClaims |
kube-apiserver、kube-controller-manager、kube-scheduler、kubelet |
| Job 控制器集成 | WorkloadWithJob |
kube-apiserver、kube-controller-manager |
一个快速验证 gang 调度的完整流程——假设你已经有一个 v1.36 测试集群并开启了上述 gate:
# 1. 创建一个 4 Pod gang Workload
kubectl apply -f - <<EOF
apiVersion: scheduling.k8s.io/v1alpha2
kind: Workload
metadata:
name: gang-demo-workload
namespace: default
spec:
podGroupTemplates:
- name: workers
schedulingPolicy:
gang:
minCount: 4
EOF
# 2. 手动创建对应 PodGroup(模拟控制器行为)
kubectl apply -f - <<EOF
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: gang-demo-workers-pg
namespace: default
spec:
podGroupTemplateRef:
workload:
workloadName: gang-demo-workload
podGroupTemplateName: workers
schedulingPolicy:
gang:
minCount: 4
EOF
# 3. 创建 4 个 Pod,全部指向同一个 PodGroup
for i in 0 1 2 3; do
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: gang-demo-worker-$i
namespace: default
spec:
schedulingGroup:
podGroupName: gang-demo-workers-pg
containers:
- name: pause
image: registry.k8s.io/pause:3.9
EOF
done
# 4. 观察:如果集群能同时放 4 个 Pod,它们会一起进入 Running;
# 如果资源不够,4 个都会 Pending,不会出现"2 个 Running、2 个 Pending"的半残状态
kubectl get pods -w
如果想用 Job 控制器自动集成,更简单——只要 Job 符合条件,开 WorkloadWithJob gate 后直接提交 Job 即可,控制器会自动创建 Workload 和 PodGroup。
采用建议与注意事项
- 先在测试集群验证——Alpha API 随版本可能变动,v1alpha2 替换了 v1alpha1,后续 Beta 阶段可能再调整字段。不要直接在生产环境开启。
- 优先用 Job 控制器集成——如果你的工作负载恰好是
Indexed + parallelism == completions的固定形状 Job,这是最省力的路径,不需要手写 Workload 和 PodGroup。 - 同质 gang 是当前最稳妥的场景——异质 PodGroup 和有 Pod 间亲和性依赖的组,算法不保证找到解。初期建议把 gang 调度用在"所有 Pod 规格相同、无复杂亲和性"的训练或批处理任务上。
- 拓扑感知调度暂不支持抢占——如果机架里资源不够,不会为了满足拓扑约束去驱逐别人。需要确保目标拓扑域内有足够空闲资源。
- DRA + PodGroup 是大规模 GPU 共享的关键路径——如果你在跑多 Pod 共享同一 GPU 的训练任务,这个组合能大幅减少 ResourceClaim 数量和 reservedFor 条目压力。
- disruptionMode: PodGroup 对保护高优先级任务有用——设置后,抢占评估要么整组驱逐要么不动,避免训练任务被部分抢占后进入半残状态。但当前只在工作负载感知抢占中生效,默认抢占还不认这个字段。
v1.37 的路线图已经在推进:Workload 和 PodGroup API 升 Beta、minCount 可变性(弹性 Job)、多级 Workload 层级(支持 JobSet / LWS 等复杂结构)、拓扑感知调度和工作负载感知抢占升 Beta、统一控制器集成 API。如果你在跑分布式训练或大规模批处理,现在可以在测试集群里试水,把反馈送到 SIG Scheduling(Slack #workload-aware-scheduling 或每周例会),帮助这些功能在 Beta 阶段更贴合真实场景。