2.2 GPU 资源建模:Device Plugin、CDI 与 Extended Resource


文档摘要

2.2 GPU 资源建模:Device Plugin、CDI 与 Extended Resource Kubernetes 并不天生认识 GPU。要让它知道「这台节点上有 8 张 A100」、让 Pod 能声明「我要 1 张 GPU」,需要一整套发现、上报、调度、隔离的机制——这就是本节要讲清的三件套:Device Plugin、CDI、Extended Resource。 2.2.1 问题:如何让 K8s 认识一块 GPU K8s 原生管理的资源有两类:可压缩资源(CPU、设备带宽) 和 不可压缩资源(内存、磁盘)。这些都是「连续可分」的——0.5 核 CPU 是合法的申请。但 GPU 是离散设备:要么整张给一个 Pod,要么不给,没有「0.

2.2 GPU 资源建模:Device Plugin、CDI 与 Extended Resource

Kubernetes 并不天生认识 GPU。要让它知道「这台节点上有 8 张 A100」、让 Pod 能声明「我要 1 张 GPU」,需要一整套发现、上报、调度、隔离的机制——这就是本节要讲清的三件套:Device Plugin、CDI、Extended Resource。

2.2.1 问题:如何让 K8s 认识一块 GPU

K8s 原生管理的资源有两类:可压缩资源(CPU、设备带宽)不可压缩资源(内存、磁盘)。这些都是「连续可分」的——0.5 核 CPU 是合法的申请。但 GPU 是离散设备:要么整张给一个 Pod,要么不给,没有「0.5 张 GPU」的概念(除非用 MIG/MPS 切分,见第 3 章)。

更关键的是,K8s 调度器默认只认识上述两类内置资源,并不知道节点上挂着什么 PCIe 设备。要让 GPU 进入 K8s 的视野,需要回答三个问题:

  1. 发现:这台节点上有几张 GPU?什么型号?
  2. 上报:怎么把这些信息告诉 Kubelet 和调度器?
  3. 分配:Pod 申请 GPU 时,怎么把具体的设备文件挂载进容器?

这三步对应三个机制:Device Plugin(发现+上报)、Extended Resource(资源计量)、CDI(标准化的设备注入)。

2.2.2 Device Plugin:GPU 进入 K8s 的第一道门

Device Plugin(设备插件) 是 K8s 提供的扩展机制,允许第三方厂商把各种硬件设备(GPU、FPGA、SR-IOV 网卡、InfiniBand HCA 等)接入 K8s。NVIDIA 提供的 k8s-device-plugin 是 AI 场景最常用的实现。

Device Plugin 的工作流程是一个清晰的注册-分配回路:

这个流程的关键步骤:

  1. 注册:Device Plugin 以 DaemonSet 形式跑在每个 GPU 节点上,启动时通过 gRPC 向 Kubelet 注册,声明自己管理的资源名(如 nvidia.com/gpu)。
  2. ListAndWatch:Device Plugin 周期性向 Kubelet 上报当前可用的设备列表(如 ["GPU-abc", "GPU-def"])。
  3. 调度:Pod 在 spec 中声明 nvidia.com/gpu: 1,调度器根据节点上报的可用数量过滤节点。
  4. Allocate:Pod 被调度到某节点后,Kubelet 调用 Device Plugin 的 Allocate 方法,Device Plugin 返回要挂载的设备文件路径与驱动库。
  5. 注入:Kubelet 把 /dev/nvidia0、CUDA 运行时库等挂载进容器,容器内进程即可使用 GPU。

💡 判读:Device Plugin 的本质是一个「设备经纪人」——它在 K8s 与硬件之间充当翻译,让 K8s 不需要为每种硬件写专门的调度逻辑,只要遵守 Device Plugin 接口,任何设备都能接入。

2.2.3 Extended Resource:资源计量与配额

Device Plugin 注册的资源(如 nvidia.com/gpu)属于 Extended Resource(扩展资源)。这是 K8s 用来管理「整数计量的扩展资源」的机制,有几个特性:

  • 整数计量:扩展资源只能申请整数(1、2、3 张),不能申请 0.5 张。这正契合 GPU 的离散性。
  • 节点级容量:扩展资源的容量是节点级的,由 Device Plugin 上报,调度器按节点过滤。
  • 不可超售:与 CPU/内存不同,扩展资源默认不可超售(overcommit),有多少设备就答应多少请求。
资源类型 计量 是否可超售 典型例子
CPU millicores(毫核) cpu: 500m
内存 字节 否(但不强制) memory: 4Gi
Extended Resource 整数 nvidia.com/gpu: 1

Extended Resource 的设计哲学是「专门给离散且不可超售的硬件资源用」,因此非常适合 GPU、FPGA、专用加速卡。

⚠️ 注意:原生 Extended Resource 只支持「整张分配」。要实现「一个 Pod 用半张 GPU」或「多个 Pod 共享一张 GPU」,需要配合 MIG、MPS 或时间分片机制(详见第 3 章)。这是 GPU 调度从「独占」走向「共享」的关键演进。

2.2.4 Device Plugin 的局限与 CDI 的登场

Device Plugin 机制虽然解决了 GPU 接入问题,但有三个长期痛点:

  1. 设备注入逻辑分散:Device Plugin 的 Allocate 方法返回的是「要挂载的设备文件 + 环境变量」,但具体怎么挂载(哪些库、哪些环境变量、什么权限)由各 Device Plugin 自己写,逻辑分散且不一致。
  2. 容器运行时耦合:不同容器运行时(containerd、CRI-O、Docker)对设备挂载的处理略有差异,Device Plugin 要适配多个运行时。
  3. 复杂设备(MIG、SR-IOV)支持差:MIG 切分后的子设备、SR-IOV 虚拟网卡等复杂设备,Device Plugin 的注入逻辑难以统一表达。

为了解决这些问题,CNCF 的 Container Device Interface(CDI,容器设备接口) 应运而生。CDI 的核心思想是:把设备注入标准化为一份 JSON 描述文件,让 Device Plugin 只负责生成 CDI 文件,容器运行时只负责解析 CDI 文件,二者彻底解耦。

CDI 的工作方式

CDI 把设备描述抽象成一份「设备规格」JSON,包含:

  • 设备名:如 nvidia.com/gpu=GPU-abc
  • 挂载点:要挂载的设备文件(/dev/nvidia0)、驱动库。
  • 环境变量:要注入的环境变量(CUDA_VISIBLE_DEVICES)。
  • 权限:设备文件的访问权限。

容器运行时(如 containerd 1.7+)原生支持 CDI,Pod 声明要某个 CDI 设备,运行时直接按 JSON 描述挂载,Device Plugin 不再需要关心运行时细节。

维度 Device Plugin(旧) CDI(新)
注入逻辑 分散在各 Plugin 统一 JSON 描述
运行时适配 各自适配 运行时统一解析
复杂设备支持 强(MIG、SR-IOV)
标准化程度 厂商自定义 CNCF 标准
落地状态 主流(多数集群) 新部署首选

💡 判读:CDI 不是 Device Plugin 的替代者,而是它的「注入层升级」。Device Plugin 仍然负责设备发现与上报,只是把「怎么注入容器」这件事交给标准化的 CDI 文件。新建 AI 集群推荐直接用支持 CDI 的 NVIDIA Device Plugin 版本。

2.2.5 三种机制的关系:一个堆叠的层级

Device Plugin、Extended Resource、CDI 三者不是替代关系,而是堆叠的层级,各管一段:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 360" font-family="sans-serif" font-size="13"> <!-- 标题 --> <text x="360" y="24" text-anchor="middle" font-size="15" font-weight="bold">GPU 资源建模三层关系</text> <!-- 三层堆叠 --> <rect x="60" y="50" width="600" height="70" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/> <text x="360" y="78" text-anchor="middle" font-weight="bold" fill="#9d174d">Device Plugin:发现 + 上报</text> <text x="360" y="100" text-anchor="middle" font-size="12" fill="#831843">gRPC 注册到 Kubelet,ListAndWatch 上报设备,Allocate 返回注入规格</text> <rect x="60" y="140" width="600" height="70" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/> <text x="360" y="168" text-anchor="middle" font-weight="bold" fill="#854d0e">Extended Resource:资源计量</text> <text x="360" y="190" text-anchor="middle" font-size="12" fill="#713f12">整数计量、不可超售、调度器按节点过滤(nvidia.com/gpu: 1)</text> <rect x="60" y="230" width="600" height="70" rx="8" fill="#dbeafe,stroke:#2563eb" stroke="#2563eb" stroke-width="1.5"/> <text x="360" y="258" text-anchor="middle" font-weight="bold" fill="#1e40af">CDI:标准化注入</text> <text x="360" y="280" text-anchor="middle" font-size="12" fill="#1e3a8a">JSON 设备规格,容器运行时统一解析,支持 MIG/SR-IOV</text> <!-- 关系箭头 --> <text x="40" y="320" font-size="12" fill="#475569">三者各管一段:Plugin 发现→Resource 计量→CDI 注入</text> </svg>

把三层串起来理解:

  • Device Plugin 回答「这台节点有什么设备」。
  • Extended Resource 回答「这些设备怎么被 Pod 申请」。
  • CDI 回答「被申请的设备怎么注入容器」。

2.2.6 资源声明与调度示例(伪声明)

一个 Pod 申请 GPU 的声明(伪代码示意,非完整 YAML):

spec: containers: - name: trainer resources: limits: nvidia.com/gpu: 1 # 申请 1 张 GPU cpu: 8 memory: 64Gi

调度器的工作就是:扫描集群中所有节点的 nvidia.com/gpu 可用数量,找到剩余 >= 1 的节点,结合其他约束(亲和、污点)打分,选一个节点落地。落到节点后,Kubelet 调用 Device Plugin + CDI 把 GPU 注入容器,容器内 nvidia-smi 即可看到分配的卡。

2.2.7 局限与下一步:从独占到共享

Device Plugin + Extended Resource + CDI 这套机制解决了「GPU 进入 K8s」的问题,但它的资源模型是整张独占的——一张 GPU 一次只能给一个 Pod。这导致 1.1 节讲的「GPU 孤岛、利用率低」问题依然存在。

要实现「一张 GPU 给多个 Pod 共享」,需要在此基础上引入 GPU 切分技术(MIG、MPS、时分复用),这就是第 3 章的开篇主题。可以把第 2 章和第 3 章理解为:

  • 第 2 章(本章):GPU 的 K8s 化——让 K8s 认识、调度、独占分配 GPU。
  • 第 3 章:GPU 的精细化——让一张 GPU 被切分、共享、按拓扑优化。

💡 判读:理解了「Device Plugin 解决接入、Extended Resource 解决计量、CDI 解决注入」这三层关系,后续学共享 GPU(MIG/MPS)与拓扑感知调度时,就能明白它们都是在「Device Plugin 之上」叠加的新能力,而非另起炉灶。

本节小结

  • K8s 不天生认识 GPU,要让它管理 GPU,需要回答三个问题:发现、上报、分配。
  • Device Plugin 是 GPU 进入 K8s 的第一道门,通过 gRPC 向 Kubelet 注册并周期上报设备列表。
  • Extended Resource 是 K8s 管理离散、不可超售扩展资源的机制,GPU 计量单位是整数张。
  • CDI 把设备注入标准化为 JSON 描述,解耦 Device Plugin 与容器运行时,是新建集群的首选。
  • 三者是堆叠层级而非替代:Plugin 发现 → Resource 计量 → CDI 注入,各管一段。
  • 本章这套机制只支持「整张独占」,要实现 GPU 共享需引入 MIG/MPS(第 3 章)。

下一节《2.3 GPU/CPU 混合调度与异构资源管理》将讲清一个多型号 GPU 集群如何被组织、调度与隔离。


发布者: 作者: 灏天文库 转发
评论区 (0)
U