编程 Kubernetes 1.36 深度拆解:当调度器学会「按拓扑思考」——DRA 全面收网、PodGroup 组调度与 User Namespace GA 的工程全貌

2026-07-17 03:42:13 +0800 CST views 5

Kubernetes 1.36 深度拆解:当调度器学会「按拓扑思考」——DRA 全面收网、PodGroup 组调度与 User Namespace GA 的工程全貌

2026 年的第一个大版本,代号 Haru,带着 70 项增强(18 项 Stable、25 项 Beta、25 项 Alpha)落地。
如果你去年还在用 Device Plugin 硬凑 GPU 调度、用 Operator 自己造 Gang Scheduling、用一堆 Webhook 做准入变更,那么 1.36 会让你意识到:Kubernetes 正在把「AI Infra 的工程债务」收编进内核。

2026 年 5 月,Kubernetes 1.36(Haru)正式发布。和往年「又一批 API 毕业」的平淡叙事不同,这一版有一个非常清晰的主线:为 AI/ML 工作负载补上调度器级别的原语。动态资源分配(DRA)从「能跑 Demo」跨入「生产可用」,组调度(PodGroup / Workload API)第一次有了官方身份,拓扑感知调度(TAS)让调度器终于理解 NUMA 和 PCIe 拓扑,而 User Namespaces 与 Mutating Admission Policies 双双 GA,把「安全默认」和「无 Webhook 准入」这两件运维老大难一次性解决。

这篇文章不从 Release Notes 搬运清单,而是从一线工程师的视角,把 1.36 最核心的几条主线拆开讲透:它们解决什么真实痛点、底层架构怎么流转、怎么落地代码、又能在生产里榨出多少性能。


一、背景:AI 工作负载把调度器逼到了墙角

要理解 1.36 为什么「非改不可」,得先回到过去三年 Kubernetes 在 AI 场景里的真实困境。

1.1 整数卡的诅咒

传统 Device Plugin(以 NVIDIA Device Plugin 为代表)把 GPU 注册成 nvidia.com/gpu: 1 这种扩展资源(Extended Resource)。它有两个致命限制:

  1. 只能是整数。你要在一张 80GB 的 A100 上跑 4 个小推理任务?对不起,resources.limits 里只能写 1,要么独占整卡,要么别用。多卡切分只能靠 MIG 这种硬件级隔离,或者 MPS / Time-Slicing 在 Device Plugin 层面「假装」有多张卡——本质是把一份设备拆成多个整数字段骗调度器,资源 accounting 一团糟。
  2. 调度器看不见设备属性。Device Plugin 只上报「我有几张卡」,不告诉调度器「这张卡是 A100 还是 T4、挂在哪个 NUMA 节点、走哪条 PCIe 通道」。于是 kube-scheduler 只能做「节点级」的二值判断,拓扑优化无从谈起。

1.2 大模型训练的 Gang 死锁

训练一个 64 卡的任务,需要 64 个 Pod 同时拿到卡才能开始。但默认调度器是逐 Pod 调度的:如果集群里有 60 张空闲卡,64 个 Pod 里先调度的 60 个占满资源,剩下 4 个永远 Pending,而那 60 个又因为等不齐同伴而空转——经典的 资源死锁 / 碎片

社区的解法是 Volcano、KubeBatch 这类批调度器,或者各大厂自研的 Coscheduling 插件。但它们都是「外围补丁」:自定义 CRD、侵入式修改调度器、和原生调度器抢资源。到 2026 年,Kubernetes 终于把这件事收编成一等公民。

1.3 容器里的 root 就是节点上的 root

默认情况下,容器里的 UID 0 映射到宿主机的 UID 0。一旦容器逃逸,攻击者的权限等同于节点 root。这在多租户、特别是 GPU 共享集群里是真实灾难。User Namespaces 这项机制能把容器内的 root 映射成宿主机上的非特权 UID 区间,但它在 Kubernetes 里打磨了多个版本才真正 GA。

1.36 就是冲着这三座大山来的。下面逐条拆。


二、核心概念:1.36 到底改了什么

2.1 DRA(Dynamic Resource Allocation):把 GPU 当 PVC 来用

DRA 的设计哲学一句话:把「持久卷动态供应」的模型泛化到任意硬件资源。就像你用 StorageClass + PersistentVolumeClaim 申领存储,DRA 让你用 DeviceClass + ResourceClaim 申领设备。

它和 Device Plugin 的本质区别:

维度Device Plugin(扩展资源)DRA
资源粒度整数张卡任意属性匹配的「设备集合」
调度可见性仅节点级数量设备级属性(型号、拓扑、健康度)
按需初始化不支持支持(claim 分配后由驱动初始化)
跨 Pod 共享靠 MIG/MPS hack原生支持 shareable
抽象方式硬编码 vendor.com/resource声明式 DeviceClass 选择器

DRA 引入三个核心对象:

  • ResourceSlice:由设备驱动(DRA driver)定期向 API Server 上报,描述「某节点上有哪些设备、各自有什么属性」。相当于设备世界的 Node 状态。
  • DeviceClass:集群管理员定义设备的「逻辑分类」,用 CEL 表达式描述「什么样的具体设备属于这一类」。应用运维只认 DeviceClass,不关心底层是 A100 还是 H100。
  • ResourceClaim:Pod 对设备的具体申领,引用某个 DeviceClass,由调度器在调度时绑定到具体的 ResourceSlice 设备。

1.36 中 DRA 的状态矩阵(这是重点)

子特性状态备注
扩展资源类型(Extended Resources)Beta支持把任意扩展资源纳入 DRA 管理
设备绑定条件(Device Binding Conditions)Beta,默认启用驱动可以给设备打健康/就绪条件,调度器据此门控
设备污点与容忍(Taints & Tolerations)Beta类比节点污点,可隔离故障/维护中设备
可消费容量(Consumable Capacity)GA,默认启用设备可暴露「容量池」,支持切片共享
分区设备(Partitionable Devices)Beta一张卡切成多份分给不同 claim
优先列表(Prioritized List)GA多候选设备时按优先级选择
管理员访问(Admin Access)GA控制谁能为特定设备建 claim
原生资源映射(Native Resource Mapping)Alpha把设备映射到 CPU/内存等原生资源
列表类型属性(List-typed Attributes)Alpha设备属性支持列表值

一句话总结 1.36 的 DRA:结构化参数(structured parameters)全面成熟,切片共享和拓扑感知从「能演示」变成「能上线」

2.2 PodGroup / Workload API:组调度有了官方身份

1.36 引入了 scheduling.k8s.io/v1alpha2 下的 WorkloadPodGroup API。它解决的就是 1.1 节说的 Gang Scheduling 问题:

  • Gang Scheduling(组调度):要么一组 Pod 全调度成功,要么全不调度,杜绝部分占用导致的死锁。
  • 工作负载感知抢占(Workload-aware Preemption):抢占时不再是「踢掉单个 Pod」,而是「踢掉整个 PodGroup」,保证被抢占方也是成组离开,不会留下半成品。
  • PodGroupPodsCount 插件:调度器优先选择 Pod 数多的 PodGroup,减少长尾等待。

注意:这个 API 目前是 Alpha,需要通过 Feature Gate 开启,生产上要谨慎。但它的意义在于——组调度终于从「社区方案各自为政」收敛成「API 标准化」的起点。

2.3 TAS(Topology Aware Scheduling):让调度器懂 NUMA

拓扑感知工作负载调度(Topology Aware Scheduling)在 1.36 处于 Alpha。它的目标是让 kube-scheduler 理解拓扑域(topology domain):NUMA 节点、PCIe Switch、机架、可用区。

在此之前,CPU Manager + Topology Manager 只能在单节点内尽量把 Pod 的 CPU 和内存对齐到同一 NUMA。但跨 Pod、跨设备的拓扑(比如「这两个 GPU 在同一个 PCIe Switch 下,NVLink 带宽更高」)调度器完全不知道。TAS 把拓扑信息建模成调度约束,配合 DRA 的设备属性,调度器就能把通信密集的训练任务放到高带宽互联的设备子集上。

2.4 User Namespaces GA:容器 root 不再是节点 root

1.36 中 User Namespaces 正式 GA。开启后(Pod 级 spec.hostUsers: false),容器内的 UID/GID 被映射到一个与宿主机隔离的非特权区间。即便容器逃逸,攻击者拿到的也是宿主机上的「nobody 级别」身份,无法直接动节点。这对多租户 GPU 集群是质变级的安全加固。

2.5 Mutating Admission Policies GA:用 CEL 取代 Webhook

可变准入策略(Mutating Admission Policies)在 1.36 达到 GA。它允许你用 CEL(Common Expression Language) 把「变更逻辑」写成原生 Kubernetes 对象,而不是维护一套独立的 MutatingWebhook 服务。

收益非常实在:

  • 不用再为 Webhook 搞高可用部署、TLS 证书、超时和失败策略;
  • 变更逻辑随集群走,审计、版本管理都变成 GitOps 的一部分;
  • 性能更好,少了一次网络往返。

2.6 调度器 PreBind 插件并行化

一个容易被忽略但影响面不小的变化:调度器框架现在支持并行执行 PreBind 插件。自定义调度器插件如果要在 PreBind 阶段做重操作(比如调用外部系统进行设备初始化),需要在 PreBindPreFlight 里返回 PreBindPreFlightResult 并设置 AllowParallel: true,否则保持串行。这对 DRA 这种「分配后要初始化设备」的场景很关键——多个 claim 的初始化可以并行,缩短调度尾延迟。


三、架构分析:DRA 与调度器如何协同

理解 DRA 不能只看对象定义,得看控制流。一次 DRA 设备分配从声明到 Pod 运行,经历如下关键路径:

  1. 上报(Reconcile Loop):DRA 驱动(独立于 kubelet 运行的 controller)周期性扫描节点上的设备,生成/更新 ResourceSlice 对象写入 API Server。每个 Slice 描述该节点可用的具体设备及其属性(型号、拓扑、健康条件)。
  2. 声明(User Action):用户创建 DeviceClass(管理员预置)和 ResourceClaim(或在 Pod 里用 ResourceClaimTemplate 自动生成)。
  3. 调度(Scheduling)kube-scheduler 里的 NodeResource 插件读取 claim 的需求,结合 ResourceSlice 判断「哪些节点能满足」。这里 DRA 和原生资源调度最大的不同是——候选节点必须在调度时就确定具体设备,而不是等 Pod 落到节点再分配。
  4. 分配(Allocation):调度器选好节点 + 设备组合后,把分配结果写回 ResourceClaimstatus.allocation。这一步会经过设备绑定条件污点容忍的校验:如果驱动给某设备打了 Pending/Unhealthy 条件,或打了不可容忍的污点,分配直接失败。
  5. 初始化(PreBind / Bind):进入 PreBind 阶段,驱动收到分配事件,对设备进行按需初始化(比如为这个 claim 创建 MIG 实例、挂载特定的固件配置)。1.36 的 PreBind 并行化让多个 claim 同时初始化。
  6. 运行(Run):kubelet 看到 Pod 引用的 claim 已分配,把设备注入容器(通过 /dev、环境变量、或 driver 特定的挂载),Pod 启动。

关键点:DRA 把「设备选择」从 kubelet 的「节点本地决策」上提到了「调度器的全局决策」。这正是它能做拓扑感知、能做切片共享、能做组调度的根本原因——因为调度器在手握全局视图时做决策,而 Device Plugin 只能在节点本地数数。

TAS 与 DRA 的配合:TAS 的拓扑约束作为调度器的附加打分/过滤维度,作用在 DRA 选出的候选设备上。例如 claim 要求「2 张 GPU 必须在同一 NUMA 且通过 NVLink 互联」,DRA 提供设备属性,TAS 提供拓扑域约束,二者在调度器里合流,最终选出满足拓扑的最优子集。


四、代码实战

下面给出可直接落地(或与 1.36 对齐)的清单。注意标注 Alpha 的部分需开启对应 Feature Gate。

4.1 DeviceClass + ResourceClaim + Pod 引用

最基础的 DRA 用法——声明一个「NVIDIA A100」设备类,Pod 申领并使用:

# 1) 设备类:管理员预置,用 CEL 描述「什么样的具体设备算这一类」
apiVersion: resource.k8s.io/v1beta1
kind: DeviceClass
metadata:
  name: gpu.nvidia.com
spec:
  selectors:
  - cel:
      expression: >
        device.driver == "nvidia.com" &&
        device.attributes["nvidia.com/model"] == "A100"
---
# 2) 资源申领:用户申领一个属于 gpu.nvidia.com 类的设备
apiVersion: resource.k8s.io/v1beta1
kind: ResourceClaim
metadata:
  name: gpu-claim
spec:
  device:
    class: gpu.nvidia.com
---
# 3) Pod 引用该 claim
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-gpu
spec:
  containers:
  - name: trainer
    image: nvcr.io/nvidia/pytorch:24.07-py3
    command: ["python", "train.py"]
    resources:
      claims:
      - name: gpu-claim
  resourceClaims:
  - name: gpu-claim
    resourceClaimName: gpu-claim

注意 containers[].resources.claims 和顶层 resourceClaims 的对应关系:前者把 claim「绑定」到具体容器,后者声明 Pod 级引用。

4.2 切片共享:用 Consumable Capacity 把一张卡分给多人

1.36 中 Consumable Capacity(GA) + Partitionable Devices(Beta) 让单卡切片共享成为原生能力。示意:

# 设备类声明「可消费容量」的建模方式(示意,驱动侧需配合上报)
apiVersion: resource.k8s.io/v1beta1
kind: DeviceClass
metadata:
  name: gpu-slice.nvidia.com
spec:
  selectors:
  - cel:
      expression: device.driver == "nvidia.com"
  # 可消费容量:该设备类暴露一个「显存容量池」,单位 GB
  consumableCapacity:
  - name: "memory"
    attributes:
      "nvidia.com/memory": "gb"

应用申领时只要 quantity: 20gb(示意字段,具体随驱动实现),调度器就能在同一张物理卡上把 20GB 显存分给 A、把 40GB 分给 B,互不冲突。这在中小推理场景能直接把 GPU 成本打下来——过去只能靠 MIG 做粗粒度切分,现在细到容量维度。

4.3 PodGroup / Workload API(Alpha)

开启 Feature Gate PodGroup 后,声明一个需要成组调度的训练任务:

apiVersion: scheduling.k8s.io/v1alpha2
kind: Workload
metadata:
  name: llm-train-64
spec:
  # 整组要么全调度,要么全不调度(Gang)
  minMatch: 64
---
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: llm-train-64-group
spec:
  workloadName: llm-train-64
  # 该组共 64 个 Pod
  totalPods: 64

配合 Deployment/Job 的 podGroup 注解或字段,调度器在调度这 64 个 Pod 时会整体决策:凑不齐 64 个候选节点,就一个都不落,彻底消灭部分占用死锁。

4.4 User Namespaces:一行配置隔离 root

apiVersion: v1
kind: Pod
metadata:
  name: userns-demo
spec:
  hostUsers: false   # 关键:开启 User Namespace 隔离
  containers:
  - name: ctr
    image: registry.k8s.io/pause:3.9

hostUsers: false 即可让容器内 root 映射为宿主机非特权 UID。多租户集群强烈建议默认开启(可通过 Mutating Admission Policy 强制注入,见下节)。

4.5 Mutating Admission Policies:用 CEL 强制注入安全默认值(GA)

把「所有 Pod 默认开启 User Namespace 隔离」写成原生策略,告别 Webhook:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: default-userns
spec:
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["pods"]
  # 用 CEL 在准入时把 hostUsers 设为 false(示意结构)
  mutations:
  - patchType: WithParameters
    value: '[{"op":"add","path":"/spec/hostUsers","value":false}]'
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: default-userns-binding
spec:
  policyName: default-userns
  matchResources:
    namespaceSelector:
      matchLabels:
        multi-tenant: "true"

这段清单的价值在于:准入变更逻辑变成集群里的普通对象,可以用 GitOps 管理、可以 kubectl 直接审计,不再依赖一个随时可能超时的外部 Webhook 服务。

4.6 运维排查:看清分配结果

# 查看 claim 的分配状态
kubectl get resourceclaim gpu-claim -o yaml

# 查看节点上报了哪些设备
kubectl get resourceslice -o wide

# 看 Pod 实际拿到了什么设备
kubectl get pod pod-with-gpu -o jsonpath='{.spec.containers[*].resources.claims}'

# 拓扑/设备属性排查
kubectl describe resourceslice <slice-name>

五、性能优化与生产实践

讲了概念,落到「能榨出多少性能」才是工程师关心的。

5.1 GPU 共享与碎片治理

过去中小推理 Pod 独占整卡,GPU 利用率经常个位数。用 1.36 的 Consumable Capacity + 分区设备,按显存/算力维度切片,单卡可同时服务多个推理实例。但要警惕碎片:如果 claim 形状太碎,调度器找不到连续容量。实践建议:

  • 把切片粒度标准化(如 10GB / 20GB / 40GB 三档),减少组合爆炸;
  • 配合 设备污点 把「即将维护」的卡标记出来,让调度器自然排空;
  • 优先列表(Prioritized List, GA) 让调度器优先选「剩余容量最大」或「同 NUMA」的设备,提升后续紧凑度。

5.2 NUMA 拓扑对齐,砍掉跨片延迟

大模型训练里,GPU 跨 PCIe Switch、跨 NUMA 的通信延迟差可达数倍。TAS(Alpha)配合 DRA 的设备拓扑属性,可以把通信密集的 Pod 组调度到高带宽互联子集上。即便 TAS 还没上生产,也可以在 DeviceClass 的 CEL 里直接约束:

device.driver == "nvidia.com" &&
device.attributes["nvidia.com/nvlink-group"] == "group-0"

把「NVLink 组」建模成设备属性,用 DRA 选择器天然实现拓扑亲和。这是 1.36 之前 Device Plugin 完全做不到的。

5.3 Gang Scheduling 消灭死锁

64 卡训练如果不做组调度,按 1.1 节的死锁模型,集群稍微一忙就会卡死。PodGroup/Workload API 让「要么全有、要么全无」成为调度器的内建语义,配合工作负载感知抢占——被抢占时整组让出,避免留下「半组残党」继续占着资源却不干活。

5.4 绑定条件做健康门控

1.36 的 Device Binding Conditions(Beta 默认开) 是稳定性利器:驱动可以给设备打 Ready/Unhealthy 条件,调度器自动跳过不健康的设备。过去设备故障只能靠 Device Plugin 把数量报成 0,响应慢且容易漏报;现在条件模型更细,结合污点容忍可以做「软隔离」——先打污点不让新任务进来,等排空再修。

5.5 迁移注意事项(踩坑预警)

升级到 1.36 前务必核对:

  • PreBind 并行化:自定义调度器插件若用到 PreBind,需实现 PreBindPreFlight 并返回 AllowParallel: true 才能并行,否则退化为串行,可能拉长调度延迟。
  • 监控指标重命名volume_operation_total_errorsvolume_operation_errors_totaletcd_bookmark_countsetcd_bookmark_total。有自定义 Grafana/告警的团队要同步改。
  • Kubeadm 移除 FlexVolume:内置 FlexVolume 支持已删,依赖它的集群需自定义 KCM 镜像并手动挂载插件目录,或迁移到 CSI。
  • Alpha 特性生产谨慎:PodGroup/Workload API 与 TAS 仍是 Alpha,Feature Gate 默认关,上生产前建议先在隔离环境验证。

六、总结与展望

把 1.36 的几条主线串起来看,会发现 Kubernetes 的战略意图非常清晰:它不再只想做「容器的编排器」,而是要成为「异构算力的操作系统」

  • DRA 全面收网:GPU/FPGA/DPU 等加速卡从「整数扩展资源」升级为「可被声明式申领、可按属性匹配、可按容量切片、可拓扑感知」的一等资源。这是 AI Infra 工程化的基石。
  • 组调度原语化:PodGroup/Workload API 把社区各自为战的 Gang Scheduling 收敛为官方 API,虽然还在 Alpha,但方向已定。
  • 安全默认强化:User Namespaces 与 Mutating Admission Policies 双双 GA,把「多租户隔离」和「无 Webhook 准入」从最佳实践变成开箱能力。

对一线工程师的建议很务实:

  1. 立刻评估 DRA 替代 Device Plugin:尤其有多卡共享、拓扑亲和、按需初始化需求的团队,1.36 的 DRA 已经值得试点。
  2. 用 Mutating Admission Policies 清掉历史 Webhook:能省一套高可用服务就省一套,运维负担和故障面都降一截。
  3. 默认开启 User Namespaces:多租户集群的安全底线,配置成本几乎为零。
  4. 对 Alpha 特性保持「观察+小步试点」:PodGroup 和 TAS 值得在训练集群、推理网关等场景做影子验证,但别急着 All-in。

Kubernetes 1.36「Haru」不是功能堆砌的又一个版本,而是把 AI 时代最痛的调度与安全债务,一次性收编进内核的里程碑。当调度器学会按拓扑思考,当 GPU 像 PVC 一样被优雅申领,云原生才算真正接住了大模型时代的算力洪流。


参考资料:Kubernetes 1.36 官方 Release Notes、Kubernetes 官方 DRA 文档(resource.k8s.io/v1beta1)、CNCF 调度器增强 KEP。文中 Alpha 特性请以上游最新文档为准。

推荐文章

PHP解决XSS攻击
2024-11-19 02:17:37 +0800 CST
Vue3中的组件通信方式有哪些?
2024-11-17 04:17:57 +0800 CST
支付页面html收银台
2025-03-06 14:59:20 +0800 CST
防止 macOS 生成 .DS_Store 文件
2024-11-19 07:39:27 +0800 CST
mysql int bigint 自增索引范围
2024-11-18 07:29:12 +0800 CST
Vue3中如何处理组件间的动画?
2024-11-17 04:54:49 +0800 CST
程序员茄子在线接单