Kubernetes DRA:GPU 调度从“数卡”走向“选卡”


Kubernetes DRA 从数卡到选卡封面图

Kubernetes DRA:GPU 调度从“数卡”走向“选卡”

Kubernetes 官方文档已经将 Dynamic Resource Allocation(DRA,动态资源分配)标记为 Kubernetes v1.35 Stable,并默认启用,核心 API 也稳定为 resource.k8s.io/v1

这件事对 AI 基础设施的意义,不是让申请 GPU 的 YAML 变短,而是让 Kubernetes 对异构设备的理解从:

这个节点还有几张卡?

进一步走向:

哪张卡符合任务要求?它在哪个节点可访问?分配前需要怎样准备?任务结束后如何释放?

传统 Device Plugin 并没有因此失效。对设备型号统一、只关心数量的集群,它仍然简单而成熟。DRA 真正补上的,是型号、显存、拓扑、共享和设备配置逐渐复杂之后,标准调度模型缺失的那一层。

Device Plugin 解决了“能用卡”,但主要表达数量

Kubernetes 管理 GPU,长期以来主要依赖厂商 Device Plugin。典型 Pod 会这样申请设备:

apiVersion: v1
kind: Pod
metadata:
  name: cuda-worker
spec:
  containers:
    - name: worker
      image: nvidia/cuda:12.8.0-base-ubuntu22.04
      command: ["nvidia-smi"]
      resources:
        limits:
          nvidia.com/gpu: 1

它表达得很清楚:给这个容器一张 NVIDIA GPU。

Device Plugin 在节点侧发现设备,通过 kubelet 报告 nvidia.com/gpu 这样的扩展资源,并在容器启动前完成设备文件、环境变量等注入。对于硬件同质、任务只关心卡数的资源池,这套机制已经足够稳定。

问题在于,扩展资源主要是一个整数计数模型。只写 nvidia.com/gpu: 1,很难在标准资源请求中直接表达:

  • 必须是 A100、H100,还是任意型号都可以;
  • 显存至少需要多少;
  • 两张卡是否需要处于合适的互联或 NUMA 拓扑;
  • 请求的是整卡、切分实例,还是可共享设备;
  • 首选高端卡,资源不足时能否退到另一种设备;
  • 分配后是否需要执行厂商特定的准备动作。

实践中当然可以组合节点标签、亲和性、污点、准入 Webhook、不同扩展资源名和厂商调度插件。但硬件类型越多,选择逻辑越容易散落在多个组件里。

整数 GPU 扩展资源只表达数量,未声明型号与显存

数量够,不等于设备合适。 DRA 要解决的正是从“资源计数”到“具体设备选择”的表达问题。

DRA 的核心:把设备变成可描述、可筛选的对象

DRA 不再只让工作负载提交一个设备计数,而是把设备需求抽象为声明,让调度器结合驱动发布的设备信息,同时决定具体设备和可访问它的节点。

理解 DRA,先抓住四个对象。

DeviceClass:平台提供什么类型的设备

DeviceClass 由集群管理员或设备驱动定义,表示一类可申请设备及其公共选择规则。

它有点像存储中的 StorageClass:应用通常不必知道某张物理卡位于哪台机器,只需要引用平台提供的类别,例如“训练型 GPU”或“高速网络设备”。

ResourceClaim:工作负载具体需要什么

ResourceClaim 是一份设备资源声明。它可以表达设备类别、数量和筛选条件;Pod 再引用这份声明。

如果多个 Pod 需要共享同一个已知 Claim,可以直接引用它;若每个 Pod 都需要独立但配置相似的设备,则可以用 ResourceClaimTemplate 让 Kubernetes 为 Pod 生成对应 Claim。

ResourceSlice:集群里实际有哪些设备

ResourceSlice 通常由 DRA 驱动创建和维护,用来发布设备清单、属性以及节点可访问范围。

应用开发者一般不会手工维护它。它是设备驱动告诉 Kubernetes“现在有哪些设备可供选择”的标准接口。

调度器:同时匹配设备与节点

调度器根据 Claim 的条件,在 ResourceSlice 发布的候选设备中完成匹配,并把 Pod 放到能够访问被分配设备的节点。

这与“先选一个还有 GPU 计数的节点,再由节点侧决定具体设备”不同:设备选择和节点选择被纳入同一个调度决策。

DRA 根据 ResourceClaim 与 ResourceSlice 联合选择 GPU 和可访问节点

一个简化的 DRA 例子

下面只展示核心关系。假设集群已经安装 gpu.example.com DRA 驱动,并且该驱动会发布 GPU 的型号属性。示例中的驱动名、属性名和型号都是占位值,实际配置必须以厂商驱动文档为准。

平台先定义一个设备类别:

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: training-gpu
spec:
  selectors:
    - cel:
        expression: >-
          device.driver == 'gpu.example.com' &&
          device.attributes['gpu.example.com'].type == 'gpu'

工作负载再创建一份声明,请求一张指定型号的训练卡:

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: model-training-gpu
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: training-gpu
          count: 1
          selectors:
            - cel:
                expression: >-
                  device.attributes['gpu.example.com'].model == 'ExampleGPU-X100'
---
apiVersion: v1
kind: Pod
metadata:
  name: model-training
spec:
  resourceClaims:
    - name: accelerator
      resourceClaimName: model-training-gpu
  containers:
    - name: trainer
      image: registry.example.com/ml/trainer:1.0
      resources:
        claims:
          - name: accelerator

这不是一份可以直接复制到任意 GPU 集群的部署文件。DRA 只规定统一框架,具体设备属性及准备方式仍由驱动定义。

真正的变化也不是“用更多 YAML 替换 nvidia.com/gpu: 1”,而是工作负载现在提交了一份带条件的设备声明。调度器要找到的不再是任意一张可用卡,而是满足声明的具体设备。

一次 DRA 分配如何发生

完整过程可以压缩成五步:

  1. 发现设备:节点上的 DRA 驱动发现 GPU 或其他硬件;
  2. 发布信息:驱动通过 ResourceSlice 发布设备属性与节点可访问范围;
  3. 提交声明:工作负载创建 ResourceClaim,或通过 ResourceClaimTemplate 生成 Claim;
  4. 联合调度:调度器匹配具体设备,并选择能够访问它的节点;
  5. 准备与回收:Pod 到达节点后,节点侧 DRA 驱动准备设备;使用结束后取消准备,并按 Claim 生命周期释放分配。

设备准备可能涉及设备配置、挂载、运行时参数注入等动作。Kubernetes 负责协调声明、调度和生命周期,驱动负责理解具体硬件。

DRA 在目标节点执行 Prepare 与 Unprepare 的设备生命周期

需要注意:Unprepare 取消的是节点侧准备状态,不等于删除 DeviceClassResourceSlice;资源何时完全释放,还取决于 Claim 的生命周期和使用关系。

DRA 对 GPU 调度真正改变了什么

从“有一张卡”变成“有一张合适的卡”

混合 GPU 集群中,不同代际、显存和性能特征的设备可能共存。如果全部暴露成同一种扩展资源,调度结果也许满足数量,却不一定满足任务的容量和性能要求。

DRA 允许驱动发布结构化属性,允许 Claim 提交筛选条件,为精细选卡提供了标准入口。

让设备与节点进入同一个决策

多卡训练不仅需要卡数,还可能关心 GPU 之间、GPU 与 CPU 之间的拓扑。DRA 为“选择具体设备,并把 Pod 放到能够访问它的节点”提供了统一基础。

但能表达多复杂的拓扑,仍取决于 Kubernetes 对应能力和厂商驱动实现,不能因为 API 稳定就假定所有拓扑问题已经解决。

为共享、切分和可配置设备提供更合理的抽象

GPU 未必始终是一张物理卡对应一个工作负载。切分实例、共享容量和分配前配置,都比整数计数复杂。

DRA 将“申请什么”与“驱动如何准备”分开,使这些能力不必全部压缩进固定扩展资源名称。厂商可以表达自己的设备模型,Kubernetes 则提供声明、调度和生命周期框架。

支持按优先级声明备选设备

DRA 的设备请求可以提供按顺序尝试的候选方案。例如首选某种高性能设备,无法分配时再申请另一类设备或不同数量的设备。

这让“首选方案 + 可接受的降级方案”有机会进入标准声明,而不必全部由外部控制器改写 Pod。具体可用字段仍应以目标 Kubernetes 版本与驱动支持为准。

Stable、默认启用,不等于升级后自动生效

Kubernetes v1.35 中 DRA 进入 Stable,意味着核心 API 获得稳定性承诺,集群组件默认具备对应框架能力。

不意味着

  • 升级控制平面后,GPU 会自动改用 DRA 发布;
  • 现有 nvidia.com/gpu 请求会自动转换为 ResourceClaim;
  • 所有 GPU 厂商、设备型号和驱动版本都已支持 DRA;
  • Device Plugin 会立即退出;
  • 型号、显存、拓扑和共享能力在不同驱动中完全一致;
  • 原有监控、配额、计费和故障排查体系无需调整。

“默认启用”描述的是 Kubernetes 框架能力。DRA 真正落地,仍需要匹配的厂商驱动、节点组件、资源类别、权限、策略和可观测性。

DRA 不会立刻取代 Device Plugin

Device Plugin 生态成熟、模型简单。对于同型号 GPU 集群,以及只需要整数设备的工作负载,继续使用扩展资源完全合理。

DRA 更适合这些需求已经明确的场景:

  • 一个集群存在多种 GPU 型号或显存规格;
  • 任务需要按设备属性选择硬件;
  • 多卡任务对互联或 NUMA 拓扑敏感;
  • 平台要管理切分、共享或可配置设备;
  • 希望把散落在标签、Webhook 和自定义控制器中的选卡逻辑迁移到标准 API;
  • 需要用统一方式管理 GPU、FPGA、网卡等不同设备。

更现实的演进路径是两套机制并存:简单工作负载继续使用 Device Plugin,需要精细设备选择的工作负载逐步采用 DRA。如何并存、是否会重复管理同一设备,必须遵循厂商部署说明。

生产落地前,至少检查这些问题

版本与驱动

  • 控制平面、kubelet、容器运行时是否满足目标驱动要求;
  • 是否使用稳定的 resource.k8s.io/v1 API;
  • 厂商 DRA 驱动支持哪些 GPU、固件和内核模块;
  • 驱动发布哪些属性,它们的名称、类型和语义是什么;
  • DRA 与传统 Device Plugin 能否并存,如何避免重复管理。

调度与生命周期

  • 单卡、多卡、混合型号节点能否得到预期分配;
  • 设备不足时,Pod 的 Pending 原因是否清晰;
  • CPU、内存、存储、节点亲和性与设备 Claim 同时存在时是否正确调度;
  • Pod 删除、Job 重试、节点排空或驱动重启后,设备能否正确释放;
  • ResourceClaim 的创建者和清理责任是否明确。

治理与可观测性

  • DeviceClass 的命名、用途和维护者是否明确;
  • 用户应直接创建 Claim,还是主要使用平台模板;
  • RBAC、准入策略、命名空间隔离和配额如何设计;
  • 能否从 Pod 追踪到 Claim、节点和具体设备;
  • 是否监控 ResourceSlice 更新、声明长期 Pending、设备准备失败和驱动异常;
  • 原有容量报表、利用率、成本核算和审计是否需要调整。

最后的判断

DRA 稳定化的价值,不在于让 GPU Pod 的配置更短。它真正解决的是:当设备不再同质、不可分割、只需计数时,Kubernetes 需要一种标准方式描述设备、提出需求,并让调度器同时选择设备和节点。

如果集群只有少量同型号 GPU,任务只关心卡数,Device Plugin 依然简单有效。

如果硬件异构程度越来越高,任务开始关心型号、显存、拓扑、切分或共享,DRA 就提供了比扩展资源整数更合适的基础模型。

所以,Kubernetes v1.35 中 DRA 进入 Stable 是一个重要节点,但不是迁移完成的标志。接下来真正决定生产价值的,是厂商驱动成熟度、调度验证、运维适配和平台治理。

GPU 调度正在从“数卡”走向“选卡”,但走完这一步,不能只升级 Kubernetes。

参考资料


文章作者: Onefly
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Onefly !
评论
  目录