Container Device Interface (CDI) 技术介绍
原文地址:https://github.com/ForceInjection/kubernetes-hands-on-course/blob/master/Advanced-Topics/CDI/cdi_intro.md
相关文章:
Nvidia K8s Device Plugin 原理解析和源码分析
NVIDIA Container Toolkit 原理分析与代码深度解析
Kubernetes Dynamic Resource Allocation 权威指南
1. 容器设备访问现状与挑战
1.1 传统容器设备访问的挑战
在传统的容器化环境中,容器运行时对硬件设备的访问主要依赖于简单的设备节点暴露机制。然而,随着现代硬件设备复杂性的不断增长,这种传统方式面临着诸多挑战:
1.1.1 设备隔离与访问控制挑战
以 NVIDIA GPU 为例,传统容器化 GPU 计算面临严重的设备隔离和访问控制问题:
# 传统 Docker 方式:需要手动处理复杂的设备文件挂载和权限控制
docker run -it \
--device=/dev/nvidia0 \
--device=/dev/nvidia-uvm \
--device=/dev/nvidia-uvm-tools \
--device=/dev/nvidiactl \
--volume=/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1 \
--volume=/usr/lib/x86_64-linux-gnu/libcuda.so.1:/usr/lib/x86_64-linux-gnu/libcuda.so.1 \
--volume=/usr/lib/x86_64-linux-gnu/libcudart.so.11.0:/usr/lib/x86_64-linux-gnu/libcudart.so.11.0 \
--env NVIDIA_VISIBLE_DEVICES=0 \
--env NVIDIA_DRIVER_CAPABILITIES=compute,utility \
ubuntu:20.04主要挑战:
• 设备文件安全挂载:需要确保容器只能访问分配的 GPU设备(如/dev/nvidia0),防止恶意容器访问宿主机的其他GPU资源• 权限精确控制:通过 Linux 权限机制( chmod、chown)精确控制对GPU设备文件的访问权限• 多容器资源隔离:通过 cgroups等机制确保每个容器只能访问其分配的GPU资源,防止资源冲突
1.1.2 驱动程序依赖与兼容性挑战
驱动程序版本兼容性问题:GPU 应用需要特定版本的驱动程序和运行时库,这些组件通常安装在宿主机上,容器化时面临复杂的依赖管理:
# 不同 CUDA 版本的兼容性问题
# 宿主机:CUDA 11.8,容器应用:需要 CUDA 12.0
docker run --gpus all \
-e CUDA_VERSION=12.0 \
nvidia/cuda:12.0-runtime-ubuntu20.04 \
# 可能因驱动版本不匹配导致运行失败运行时差异导致的兼容性问题:
不同容器运行时对同一设备的处理方式存在显著差异:
# Kubernetes Pod 规范(使用 containerd + device plugin)
apiVersion: v1
kind: Pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda:11.0-base
resources:
limits:
nvidia.com/gpu: 1 # 该资源键需由 NVIDIA Device Plugin 注册,Kubernetes 本身并未内置 GPU 资源类型。# Podman 方式(需要 CDI 或 nvidia-container-toolkit)
podman run --security-opt=label=disable \
--hooks-dir=/usr/share/containers/oci/hooks.d/ \
--device nvidia.com/gpu=0 \
nvidia/cuda:11.0-base主要挑战:
• 驱动版本兼容性管理:确保不同容器运行的应用程序与宿主机驱动程序版本兼容,避免版本冲突 • 运行时库动态链接:容器内应用程序需要动态链接宿主机上的运行时库(如 libcuda.so、libnvidia-ml.so)• 多 CUDA 版本共存:在同一宿主机上支持多个不同版本的 CUDA 应用程序
1.1.3 资源管理与调度挑战
GPU 资源分配与调度问题:
多个容器之间的 GPU 资源分配和调度需要精确的管理机制,传统方式缺乏标准化的设备生命周期管理:
// 传统方式:容器运行时需要手动处理 GPU 资源管理
// 注意:以下是伪代码,仅用于说明传统方式的局限性,不可直接运行
// 实际环境中,GPU 内存分配主要依赖于 GPU 厂商提供的工具或 Kubernetes 等上层调度机制
func manageGPUResource(containerID string, memoryMB int) error {
// 伪代码:展示传统方式缺乏统一接口来进行 GPU 内存配额管理
// 在实际环境中,无法通过简单命令直接控制 GPU 内存分配
// 这里仅为说明传统方式在 GPU 内存管理上的局限性
cmd := exec.Command("docker", "run", "--gpus", "1",
"nvidia/cuda:11.0-base", "nvidia-smi")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to allocate GPU resource: %v", err)
}
// 资源回收问题:容器退出时可能无法可靠地回收资源
deferfunc() {
cmd := exec.Command("docker", "stop", containerID)
cmd.Run() // 依赖容器停止时自动释放资源,可能存在资源泄漏问题
}()
return nil
}
// 缺乏公平调度机制,容器间可能出现资源竞争
func scheduleGPUResources(containers []Container) error {
// 简单的先到先得调度,缺乏公平性保证
for _, container := range containers {
if isGPUAvailable() {
assignGPU(container.ID, 0) // 硬编码 GPU 0
} else {
return fmt.Errorf("no GPU available for container %s", container.ID)
}
}
return nil
}
// 设备冲突检测需要手动实现,容易出错
func checkDeviceConflict(deviceID string, containers []string) error {
for _, container := range containers {
if isUsingDevice(container, deviceID) {
return fmt.Errorf("device %s is already in use by container %s", deviceID, container)
}
}
return nil
}主要挑战:
• GPU 内存分配与回收:每个容器只能访问其分配的 GPU内存,需要防止资源冲突和内存泄漏• 计算资源公平调度:确保所有容器都能获得公平的 GPU资源分配,避免资源浪费• GPU 利用率监控:实时监控 GPU利用率,动态调整资源分配以提高资源利用率
1.1.4 性能开销挑战
容器化引入的性能损失:
传统容器化方式可能引入显著的性能开销,特别是在 GPU 计算场景下:
// 传统方式:通过多层抽象访问 GPU,增加延迟
func accessGPUTraditional() {
// 容器运行时 -> OCI Runtime -> runc -> 系统调用 -> GPU 驱动
// 每一层都可能引入额外的延迟和开销
// 内存拷贝开销:CPU <-> GPU 数据传输
data := make([]float32, 1024*1024) // 4MB 数据
// 传统方式需要多次内存拷贝
hostBuffer := allocateHostMemory(len(data))
copy(hostBuffer, data) // 第一次拷贝:用户空间 -> 主机内存
deviceBuffer := allocateGPUMemory(len(data))
cudaMemcpy(deviceBuffer, hostBuffer) // 第二次拷贝:主机内存 -> GPU 内存
// 系统调用开销:频繁的内核态/用户态切换
for i := 0; i < 1000; i++ {
syscall.Syscall(SYS_IOCTL, gpuFd, cmd, uintptr(unsafe.Pointer(&args)))
}
}主要挑战:
容器抽象层开销:
• 容器运行时调用链:容器运行时 -> OCI Runtime -> runc -> 系统调用 -> GPU 驱动的多层调用链 • 系统调用转换:容器内系统调用需要转换为主机系统调用,增加延迟 • 运行时隔离机制:容器安全隔离机制引入的额外检查和验证
GPU 硬件层次固有限制:
• PCIe 总线带宽限制:GPU 与 CPU 之间的数据传输受 PCIe 总线带宽限制 • 内存拷贝开销:数据在 CPU和GPU之间的必要拷贝操作• 零拷贝机制缺失:缺乏直接访问 GPU 内存的零拷贝机制
1.1.5 供应商生态碎片化挑战
厂商锁定与维护成本:
每个硬件供应商需要为不同运行时维护专门的集成代码,导致严重的生态碎片化:
// NVIDIA Container Toolkit 的发展历程体现了这一挑战:
// nvidia-docker v1 (2016) -> nvidia-docker v2 (2018) -> NVIDIA Container Toolkit (2019-至今)
// 每个版本都需要重新适配不同的容器运行时
// 传统方式:每个供应商为每个运行时单独开发
type NVIDIADeviceManager struct {
dockerImpl *DockerNVIDIADevice // nvidia-docker2
containerdImpl *ContainerdNVIDIADevice // nvidia-container-runtime
podmanImpl *PodmanNVIDIADevice // nvidia-podman-plugin
crioImpl *CRIONVIDIADevice // nvidia-crio-hook
}
// Intel 设备的类似实现
type IntelDeviceManager struct {
dockerImpl *DockerIntelDevice
podmanImpl *PodmanIntelDevice
containerdImpl *ContainerdIntelDevice
k8sImpl *K8sIntelDevicePlugin // 需要额外的 Device Plugin
}
func (m *IntelDeviceManager) ConfigureDevice(runtime string, deviceID string) error {
switch runtime {
case "docker":
return m.dockerImpl.Configure(deviceID)
case "podman":
return m.podmanImpl.Configure(deviceID)
case "containerd":
return m.containerdImpl.Configure(deviceID)
case "kubernetes":
return m.k8sImpl.Configure(deviceID) // 完全不同的实现方式
default:
return fmt.Errorf("unsupported runtime: %s", runtime)
}
}
// AMD GPU 的又一套实现
type AMDDeviceManager struct {
rocmDockerImpl *ROCmDockerDevice
rocmContainerdImpl *ROCmContainerdDevice
// 每个供应商都需要重复开发相似的功能
}主要挑战:
• 开发成本高:每个硬件供应商需要为每个容器运行时单独开发和维护集成代码 • 用户体验差:不同供应商和运行时的使用方式差异巨大,学习成本高 • 生态碎片化:缺乏统一标准,导致容器生态系统的碎片化
1.1.6 传统方式问题总结
基于上述分析,传统容器设备访问面临的主要挑战可以总结如下:
| 设备隔离与访问控制 | /dev/nvidia0 等多个设备节点 | ||
chmod/chown 权限控制 | |||
cgroups 隔离 GPU 资源访问 | |||
| 驱动程序依赖与兼容性 | |||
libcuda.solibnvidia-ml.so 动态链接 | |||
| 资源管理与调度 | |||
| 性能开销 | |||
| 供应商生态碎片化 | |||
1.2 现有解决方案的局限性
为了解决上述挑战,业界出现了多种解决方案,但都存在一定的局限性:
1.2.1 供应商特定解决方案
NVIDIA Container Toolkit:
• 专门针对 NVIDIA GPU 设计,无法支持其他厂商设备 • 需要为每个容器运行时单独适配
详细请参考:NVIDIA Container Toolkit 原理分析与代码深度解析
Intel GPU Device Plugin:
• 仅支持 Kubernetes 环境 • 与其他容器运行时兼容性差 • 缺乏统一的配置标准
1.2.2 Kubernetes Device Plugin 机制
详细请参考:Nvidia K8s Device Plugin 原理解析和源码分析
优势:
• 提供了相对标准化的设备管理接口 • 支持资源调度和分配
局限性:
• 仅限于 Kubernetes 环境,无法在其他容器运行时中使用 • 开发复杂度高,需要实现 gRPC 接口 • 缺乏设备生命周期的精细化管理
1.3 行业标准化需求
基于上述分析,容器设备访问领域迫切需要一个标准化解决方案,这正是 Container Device Interface (CDI) 诞生的背景和意义。
2. CDI 解决方案概述
2.1 CDI 的解决方案
Container Device Interface (CDI) 是一个用于容器运行时的标准化规范,专门设计用来解决传统容器设备访问面临的挑战。以 NVIDIA Container Toolkit 的 CDI 支持 为例,CDI 通过以下方式有效解决了上述问题:
统一设备描述标准:
• 通过 JSON/YAML 格式的设备规范文件,标准化描述 GPU 等复杂硬件设备 • 消除了不同供应商和运行时之间的配置差异 • 提供版本化的设备描述,确保向后兼容性
运行时无关的设备管理:
• CDI 为 Docker、Podman、containerd、CRI-O等运行时提供统一接口• NVIDIA Container Toolkit 通过 CDI 实现一次开发,多运行时支持 • 简化了 Kubernetes 等编排系统的设备集成
智能设备生命周期管理:
• 自动化的设备注入和清理机制 • 内置的设备冲突检测和资源管理 • 标准化的设备权限和安全策略
2.2 CDI 的核心理念
CDI 引入了设备作为资源的抽象概念。每个设备都通过一个完全限定名称(Fully Qualified Name)来唯一标识,该名称由以下部分构成:
vendor.com/class=unique_name其中:
• vendor.com/class被称为设备种类(device kind)• unique_name是在特定供应商 ID 和设备类别组合下的唯一名称
2.3 CDI 的核心设计理念
CDI 通过引入标准化的设备描述和注入机制,从根本上解决了传统容器设备访问方式的局限性。其核心设计理念包括:
• 设备抽象与标准化:将各类硬件设备抽象为统一的资源模型 • 运行时中立性:设计独立于特定容器运行时的接口规范 • 完整生命周期管理:覆盖设备的全生命周期 • 可扩展的插件架构:支持供应商自定义的设备处理逻辑 • 安全与隔离保障:提供细粒度的权限控制和设备隔离机制
2.4 CDI 与传统方案的对比分析
CDI 相比传统方案在以下关键方面具有显著优势:
| 设备描述 | |||
| 运行时支持 | |||
| 设备管理 | |||
| 冲突检测 | |||
| 性能优化 | 容器启动流程更加简化 并发处理机制更加优化 | ||
| 安全性 | |||
| 生态兼容性 | 显著降低供应商开发成本 明显缩短新手上手时间 |
2.5 CDI 的安全性与可扩展性
2.5.1 安全性设计
CDI 在安全性方面提供了多层次的保障机制:
• 细粒度权限控制:通过 deviceNodes配置,可以精确控制容器对设备的访问权限,包括读写权限和所有权• SELinux/AppArmor 集成:支持与主流 Linux 安全模块集成,提供额外的安全隔离层 • 最小权限原则:容器只能访问显式声明的设备,避免权限过度问题 • 安全的设备发现机制:规范文件扫描过程中进行严格的权限检查,防止恶意规范注入
// 细粒度权限控制示例
"deviceNodes": [
{
"path": "/dev/nvidia0",
"type": "c",
"major": 195,
"minor": 0,
"permissions": "rw",
"uid": 1000,
"gid": 1000
}
]2.5.2 可扩展性设计
CDI 的可扩展性体现在多个方面:
• 多设备类型支持:除 GPU 外,CDI 已支持 FPGA、RDMA、AI 加速卡等多种设备类型 • 供应商扩展机制:通过 annotations字段,供应商可以添加自定义元数据和配置• 插件化架构:支持通过插件扩展 CDI 的功能,如自定义设备发现和资源管理逻辑 • 版本兼容性:严格的版本控制确保向后兼容性,同时允许规范演进
Intel FPGA 设备示例:
以下配置可放置于 /etc/cdi/intel-fpga.yaml 文件中,容器运行时将自动发现并加载该配置:
{
"cdiVersion": "0.8.0",
"kind": "intel.com/fpga",
"devices": [
{
"name": "arria10",
"annotations": {
"intel.com/fpga.family": "arria10",
"intel.com/fpga.interface": "opae"
},
"containerEdits": {
"deviceNodes": [
{
"path": "/dev/intel-fpga-port.0",
"type": "c"
}
],
"mounts": [
{
"hostPath": "/opt/intel/fpga/bitstreams",
"containerPath": "/bitstreams"
}
]
}
}
]
}2.6 CDI 快速上手指南
以下是一个快速上手 CDI 的实操指南,帮助读者快速体验 CDI 的功能:
2.6.1 环境准备
# 安装 NVIDIA Container Toolkit(以 Ubuntu 为例)
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
# 配置 Docker 支持 NVIDIA GPU
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker2.6.2 生成并验证 CDI 规范
# 生成 CDI 规范文件
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
# 注意:根据环境配置,CDI 文件可能生成在 /etc/cdi/ 或 /var/run/cdi/ 目录
# 可以通过以下命令确认文件位置
find /etc/cdi /var/run/cdi -name "*.yaml" 2>/dev/null
# 查看生成的 CDI 设备列表
nvidia-ctk cdi list
# 输出示例:nvidia.com/gpu=0, nvidia.com/gpu=1, nvidia.com/gpu=all2.6.3 使用 Docker/Podman 运行 CDI 容器
# 使用 Docker 运行带 CDI 设备的容器
docker run --rm --device nvidia.com/gpu=0 nvidia/cuda:12.0-base nvidia-smi
# 使用 Podman 运行带 CDI 设备的容器
podman run --rm --device nvidia.com/gpu=0 nvidia/cuda:12.0-base nvidia-smi2.6.4 在 Kubernetes 中使用 CDI
创建一个使用 CDI 注解的 Pod:
注意:CDI 注解方式是 Kubernetes 的实验性机制,目前尚未完全稳定。在生产环境中,请根据集群配置选择合适的设备分配方式。
apiVersion: v1
kind: Pod
metadata:
name: cdi-gpu-pod
annotations:
# 使用 CDI 注解指定设备(实验性功能)
cdi.k8s.io/nvidia-gpu: "nvidia.com/gpu=0"
spec:
containers:
- name: cuda-container
image: nvidia/cuda:12.0-base
command: ["nvidia-smi", "-L"]
resources:
limits:
# 传统 GPU 分配方式,依赖 Device Plugin 注册
nvidia.com/gpu: 1说明:上述示例同时使用了两种设备分配机制:
1. CDI 注解方式( cdi.k8s.io/nvidia-gpu):Kubernetes 新增的实验性机制,直接通过 CDI 规范分配设备2. 传统资源限制方式( resources.limits.nvidia.com/gpu):依赖 Device Plugin 注册的资源分配方式
在实际生产环境中,可能只会启用其中一种方式,具体取决于集群配置和稳定性需求。
应用 Pod 配置:
kubectl apply -f cdi-gpu-pod.yaml
kubectl logs cdi-gpu-pod通过这个快速上手指南,读者可以在几分钟内体验 CDI 的基本功能,了解其在不同容器运行时中的使用方式。
3. CDI 规范详解
3.1 CDI 规范文件结构
CDI 规范文件采用 JSON 或 YAML 格式,包含以下主要字段:
{
"cdiVersion": "0.8.0",
"kind": "vendor.com/class",
"annotations": {
"key": "value"
},
"devices": [
{
"name": "device-name",
"annotations": {
"key": "value"
},
"containerEdits": {
// 设备特定的容器修改
}
}
],
"containerEdits": {
// 全局容器修改
}
}3.2 CDI 规范文件发现机制
CDI 使用预定义的目录结构来发现和加载规范文件:
3.2.1 默认目录配置
// 默认静态目录 - 用于手动配置的 CDI 规范
DefaultStaticDir = "/etc/cdi"
// 默认动态目录 - 用于自动生成的 CDI 规范
DefaultDynamicDir = "/var/run/cdi"
// 默认目录优先级配置
DefaultSpecDirs = []string{DefaultStaticDir, DefaultDynamicDir}3.2.2 规范文件扫描过程
CDI 通过 scanSpecDirs 函数扫描指定目录:
func scanSpecDirs(dirs []string, scanFn scanSpecFunc) error {
for priority, dir := range dirs {
err = filepath.Walk(dir,func(path string, info os.FileInfo, err error) error {
// 跳过子目录
if info.IsDir() {
if path == dir {
return nil
}
return filepath.SkipDir
}
// 只处理 .json 和 .yaml 文件
if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
return nil
}
// 加载并处理规范文件
spec, err := ReadSpec(path, priority)
return scanFn(path, priority, spec, err)
})
}
return nil
}3.3 当前版本信息
当前 CDI 规范版本为 0.8.0。
NVIDIA Container Toolkit 从 v1.12.0 版本开始支持生成 CDI 规范,最新版本为 v1.17.5。规范采用语义化版本控制,任何对规范的修改都会导致至少一个次版本号的增加。
3.4 版本发布历史
以下版本历史信息来源于 CDI 官方 GitHub 仓库 container-orchestrated-devices/container-device-interface:
| v0.3.0 | |
| v0.4.0 | Mount 规范添加 type 字段 |
| v0.5.0 | DeviceNodes 添加 HostPath;允许设备名称以数字开头 |
| v0.6.0 | Spec 和 Device 规范添加 Annotations 字段;允许在 Kind 字段的名称段中使用点号 |
| v0.7.0 | IntelRdt 字段;为 ContainerEdits 添加 AdditionalGIDs |
| v0.8.0 | specs-go 包中移除 .ToOCI() 函数 |
3.5 CDI JSON 规范结构
CDI 规范使用 JSON 格式定义,主要包含以下字段:
{
"cdiVersion": "0.8.0",
"kind": "<vendor.com/device-class>",
"annotations": {
"key": "value"
},
"devices": [
{
"name": "<device-name>",
"annotations": {
"key": "value"
},
"containerEdits": { ... }
}
],
"containerEdits": {
"env": ["<envName>=<envValue>"],
"deviceNodes": [...],
"mounts": [...],
"hooks": [...],
"additionalGIDs": [...],
"intelRdt": {...}
}
}3.5.1 必需字段说明
• cdiVersion(字符串,必需):必须符合语义化版本 2.0 规范,指定供应商使用的 CDI 规范版本 • kind(字符串,必需):指定唯一标识设备供应商的标签 • devices(对象数组,必需):供应商提供的设备列表
3.5.2 Kind 字段规则
kind 标签包含两个部分:前缀和名称,用斜杠(/)分隔:
• 名称段:必需,最多 63 个字符,以字母数字字符开头和结尾,中间可包含破折号、下划线、点号和字母数字字符 • 前缀:必须是 DNS 子域,由点号分隔的 DNS 标签系列,总长度不超过 253 个字符
有效示例:vendor.com/foo、foo.bar.baz/foo-bar123.B_az
无效示例:foo、vendor.com/foo/、vendor.com/foo/bar
3.6 容器编辑(Container Edits)
containerEdits 字段描述对 OCI 规范的编辑操作,支持以下类型的编辑:
3.6.1 环境变量(env)
"env": ["VARNAME=VARVALUE"]3.6.2 设备节点(deviceNodes)
"deviceNodes": [
{
"path": "/dev/card1",
"hostPath": "/vendor/dev/card1",
"type": "c",
"major": 25,
"minor": 25,
"fileMode": 384,
"permissions": "rw",
"uid": 1000,
"gid": 1000
}
]3.6.3 挂载点(mounts)
"mounts": [
{
"hostPath": "/usr/lib/libVendor.so.0",
"containerPath": "/usr/lib/libVendor.so.0",
"type": "bind",
"options": ["ro"]
}
]3.6.4 钩子(hooks)
"hooks": [
{
"hookName": "createContainer",
"path": "/bin/vendor-hook",
"args": ["arg1", "arg2"],
"env": ["ENV_VAR=value"],
"timeout": 30
}
]4. CDI 工作原理
4.1 整体流程图
CDI 与容器运行时的交互涉及多个组件和步骤,下图展示了完整的工作流程:
┌──────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 容器运行时 │ │ CDI 缓存 │ │ CDI 规范文件 │
│ (Docker/Podman) │ │ │ │ (/etc/cdi) │
└──────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ 1. 启动容器请求 │ │
│ --device nvidia.com/gpu=0 │
├──────────────────────→│ │
│ │ 2. 扫描规范文件 │
│ ├──────────────────────→│
│ │ │
│ │ 3. 加载规范内容 │
│ │←──────────────────────┤
│ │ │
│ 4. 返回设备配置 │ │
│←──────────────────────┤ │
│ │ │
│ 5. 注入设备到容器 │ │
│ │ │CDI 与容器运行时交互完整流程图:
阶段 1: 设备供应商准备
┌─────────────────────┐ 安装 ┌──────────────────────┐
│ 设备供应商 │ ────────► │ CDI 规范文件 │
│ │ │ │
│ • 创建 CDI 规范文件 │ │ • /etc/cdi/*.json │
│ • 定义设备配置 │ │ • /var/run/cdi/*.yaml│
└─────────────────────┘ └──────────────────────┘
│
│ 读取
▼
阶段 2: 容器运行时初始化
┌─────────────────────┐ 初始化 ┌─────────────────────┐ 扫描 ┌─────────────────────┐
│ 容器运行时 │ ──────► │ CDI 缓存初始化 │ ─────► │ 设备发现 │
│ │ │ │ │ │
│ • containerd/CRI-O │ │ • 扫描规范目录 │ │ • 解析规范文件 │
│ • 启动时初始化 │ │ • 加载所有规范 │ │ • 构建设备索引 │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
│ │ │
│ 接收请求 │ 提供设备信息 │
▼ ▼ │
│
阶段 3: 容器请求处理 │
┌─────────────────────┐ 请求设备 ┌─────────────────────┐ │
│ 容器创建请求 │ ──────► │ 设备注入处理 │ ◄────────────────┘
│ │ │ │
│ • --device │ │ • InjectDevices() │
│ nvidia.com/gpu=0 │ │ • 查找匹配设备 │
│ • 指定所需设备 │ │ │
└─────────────────────┘ └─────────────────────┘
│
│ 生成修改
▼
阶段 4: OCI 规范修改
┌─────────────────────┐ 启动容器 ┌──────────────────────┐
│ OCI 规范修改 │ ──────► │ 容器启动 │
│ │ │ │
│ • ContainerEdits. │ │ • 使用修改后的 │
│ Apply() │ │ OCI 规范 │
│ • 应用设备配置 │ │ │
└─────────────────────┘ └─────────────────────┘
│
│ 详细操作
▼
ContainerEdits 详细操作
╔═══════════════════════════════════════════════════════════════════════════════╗
║ ContainerEdits 包含的修改操作 ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ • 环境变量 (Env): ║
║ └─ 设置 CUDA_VISIBLE_DEVICES 等环境变量 ║
║ ║
║ • 设备节点 (DeviceNodes): ║
║ └─ 挂载 /dev/nvidia0 等设备文件到容器 ║
║ ║
║ • 挂载点 (Mounts): ║
║ └─ 挂载驱动库和工具目录到容器文件系统 ║
║ ║
║ • 生命周期钩子 (Hooks): ║
║ └─ 容器启动前后执行的自定义脚本 ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝从图中可以看出,CDI 的工作流程主要包括以下四个阶段:
1. 阶段 1: 设备供应商准备:设备供应商创建 CDI 规范文件并安装到系统指定目录(如 /etc/cdi/和/var/run/cdi/)2. 阶段 2: 容器运行时初始化:容器运行时启动时初始化 CDI 缓存,扫描规范目录,解析规范文件并构建设备索引 3. 阶段 3: 容器请求处理:当用户请求创建容器并指定设备时,CDI 进行设备注入处理,查找匹配的设备配置 4. 阶段 4: OCI 规范修改:通过 ContainerEdits.Apply()方法修改 OCI 规范,应用设备配置,最终启动容器
每个阶段都有明确的职责分工,确保了 CDI 系统的模块化和可维护性。底部的详细操作框展示了 ContainerEdits 包含的具体修改类型,包括环境变量、设备节点、挂载点和生命周期钩子等。
4.2 详细工作流程
CDI 的详细工作流程可以分为两个主要阶段:
4.2.1 设备安装阶段
1. 设备驱动安装:用户在机器上安装第三方设备驱动程序和设备 2. CDI 规范创建:设备驱动程序安装软件在已知路径(如 /etc/cdi/vendor.json)写入 JSON 格式的 CDI 规范文件3. 规范文件验证:系统验证 CDI 规范文件的格式和内容是否符合规范要求
4.2.2 容器运行时阶段
1. 设备请求:用户使用 --device参数运行容器,指定所需的设备名称(如nvidia.com/gpu=0)2. 规范文件读取:容器运行时扫描并读取 CDI 规范目录中的 JSON/YAML 文件 3. 设备验证:容器运行时验证请求的设备在 CDI 规范文件中是否有相应的描述 4. 镜像拉取:容器运行时拉取容器镜像(与设备配置并行进行) 5. OCI 规范生成:容器运行时生成初始的 OCI 运行时规范 6. 设备注入:根据 CDI 规范文件中的 containerEdits指令修改 OCI 规范,注入设备相关配置7. 容器启动:使用修改后的 OCI 规范启动容器,确保设备正确挂载和配置
4.3 CDI 缓存机制
CDI 实现了一个高效的缓存系统来管理规范文件和设备信息:
4.3.1 缓存结构
// 必要的导入包
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"log"
"github.com/fsnotify/fsnotify"
oci "github.com/opencontainers/runtime-spec/specs-go"
"sigs.k8s.io/yaml"
cdi "tags.cncf.io/container-device-interface/specs-go"
)
// Cache 结构体定义
type Cache struct {
sync.Mutex
specDirs []string // CDI 规范目录列表
specs map[string][]*Spec // 按供应商分组的规范
devices map[string]*Device // 设备名称到设备对象的映射
errors map[string][]error // 规范文件错误记录
dirErrors map[string]error // 目录访问错误
autoRefresh bool // 自动刷新开关
watch *watch // 文件系统监控
}4.3.2 缓存刷新机制
func (c *Cache) refresh() error {
var (
specs = map[string][]*Spec{}
devices = map[string]*Device{}
conflicts = map[string]struct{}{}
specErrors = map[string][]error{}
)
// 收集每个规范文件的错误
collectError :=func(err error, paths ...string) {
for _, path := range paths {
specErrors[path] = append(specErrors[path], err)
}
}
// 基于设备规范优先级解决冲突
resolveConflict :=func(name string, dev *Device, old *Device) bool {
devSpec, oldSpec := dev.GetSpec(), old.GetSpec()
devPrio, oldPrio := devSpec.GetPriority(), oldSpec.GetPriority()
switch {
case devPrio > oldPrio:
return false
case devPrio == oldPrio:
devPath, oldPath := devSpec.GetPath(), oldSpec.GetPath()
collectError(fmt.Errorf("conflicting device %q (specs %q, %q)",
name, devPath, oldPath), devPath, oldPath)
conflicts[name] = struct{}{}
}
return true
}
// 扫描所有 CDI 目录
_ = scanSpecDirs(c.specDirs,func(path string, priority int, spec *Spec, err error) error {
path = filepath.Clean(path)
if err != nil {
collectError(fmt.Errorf("failed to load CDI Spec %w", err), path)
return nil
}
vendor := spec.GetVendor()
specs[vendor] = append(specs[vendor], spec)
// 处理设备冲突
for _, dev := range spec.devices {
qualified := dev.GetQualifiedName()
other, ok := devices[qualified]
if ok {
if resolveConflict(qualified, dev, other) {
continue
}
}
devices[qualified] = dev
}
return nil
})
// 删除冲突的设备
for conflict := range conflicts {
delete(devices, conflict)
}
c.specs = specs
c.devices = devices
c.errors = specErrors
return nil
}4.3.3 自动监控机制
CDI 支持文件系统监控,当规范文件发生变化时自动刷新缓存:
type watch struct {
watcher *fsnotify.Watcher
tracked map[string]bool
}
func (w *watch) watch(fsw *fsnotify.Watcher, m *sync.Mutex, refreshfunc() error, dirErrors map[string]error) {
for {
select {
case event, ok := <-fsw.Events:
if !ok {
return
}
// 检测到文件变化,触发缓存刷新
m.Lock()
refresh()
m.Unlock()
case err, ok := <-fsw.Errors:
if !ok {
return
}
// 处理监控错误
}
}
}4.4 设备发现和加载机制
CDI 系统通过扫描指定目录来发现和加载设备规范:
4.4.1 规范文件扫描
func (c *Cache) scanSpecDirs() (map[string]*Spec, error) {
specs := make(map[string]*Spec)
for _, dir := range c.specDirs {
files, err := filepath.Glob(filepath.Join(dir, "*.json"))
if err != nil {
continue
}
yamlFiles, err := filepath.Glob(filepath.Join(dir, "*.yaml"))
if err == nil {
files = append(files, yamlFiles...)
}
for _, file := range files {
spec, err := ReadSpec(file, priority)
if err != nil {
continue
}
specs[spec.GetQualifiedName()] = spec
}
}
return specs, nil
}4.4.2 规范文件解析
// ReadSpec 读取指定的 CDI 规范文件
// path: 规范文件路径
// priority: 规范优先级
func ReadSpec(path string, priority int) (*Spec, error) {
data, err := os.ReadFile(path)
switch {
case os.IsNotExist(err):
return nil, err
case err != nil:
return nil, fmt.Errorf("failed to read CDI Spec %q: %w", path, err)
}
raw, err := ParseSpec(data)
if err != nil {
return nil, fmt.Errorf("failed to parse CDI Spec %q: %w", path, err)
}
if raw == nil {
return nil, fmt.Errorf("failed to parse CDI Spec %q, no Spec data", path)
}
spec, err := newSpec(raw, path, priority)
if err != nil {
return nil, err
}
return spec, nil
}
// ParseSpec 解析 CDI 规范数据,支持 JSON 和 YAML 格式
func ParseSpec(data []byte) (*cdi.Spec, error) {
var spec cdi.Spec
var err error
// 尝试解析为 JSON
if err = json.Unmarshal(data, &spec); err != nil {
// 如果 JSON 解析失败,尝试 YAML
err = yaml.Unmarshal(data, &spec)
}
return &spec, err
}4.5 规范验证机制
CDI 实现了严格的规范验证:
func validateSpec(raw *cdi.Spec) error {
validatorLock.RLock()
defer validatorLock.RUnlock()
if specValidator == nil {
return nil
}
err := specValidator.Validate(raw)
if err != nil {
return fmt.Errorf("Spec validation failed: %w", err)
}
return nil
}4.5.1 目录优先级和文件发现
CDI 使用标准化的目录结构和优先级机制:
• 默认目录: /etc/cdi和/var/run/cdi• 文件格式:支持 .json和.yaml格式• 自动发现:容器运行时启动时自动扫描这些目录 • 缓存机制:提高设备查找和注入的性能 • 文件监控:支持规范文件的动态更新
// 默认 CDI 目录配置
var (
DefaultSpecDirs = []string{
"/etc/cdi", // 系统级配置,优先级最高
"/var/run/cdi", // 运行时配置,优先级较低
}
)
// 获取 CDI 规范目录
func GetSpecDirs() []string {
specDirs := os.Getenv("CDI_SPEC_DIRS")
if specDirs != "" {
return strings.Split(specDirs, string(os.PathListSeparator))
}
return DefaultSpecDirs
}4.6 CDI 性能优化机制
CDI 通过多种优化策略来提升设备管理性能:
4.6.1 缓存优化策略
• 多层缓存架构:规范缓存、设备索引、LRU 缓存 • 智能缓存策略:避免重复解析,提供快速设备查找 • 缓存统计监控:实时监控缓存命中率和性能指标
// 高效的设备查找
func (c *Cache) GetDevice(name string) *Device {
// 检查 LRU 缓存
if device, ok := c.lruCache.Get(name); ok {
return device.(*Device)
}
// 检查设备索引
if device, ok := c.deviceIndex[name]; ok {
c.lruCache.Add(name, device)
return device
}
return nil
}4.6.2 并发处理优化
• 工作协程池:使用协程池并发处理规范文件加载 • 读写锁机制:保证并发访问的安全性 • 异步文件监控:实时监控规范文件变化
4.6.3 内存和 I/O 优化
• 对象池技术:重用设备和规范对象,减少内存分配 • 批量文件读取:批量处理规范文件,减少 I/O 操作 • 内存监控:实时监控内存使用情况,及时触发垃圾回收 • 异步文件监控:使用缓冲通道避免阻塞
4.6.4 性能监控和调优
• 性能指标收集:监控设备查找时间、缓存命中率、内存使用等关键指标 • 自动调优机制:根据性能指标自动调整缓存大小和触发垃圾回收 • 实时监控:提供实时的性能监控和告警机制
// CDI 性能监控示例
type PerformanceMetrics struct {
CacheHitRate float64
DeviceLookupTime time.Duration
MemoryUsage uint64
RefreshCount int64
}
// 收集性能指标
func (c *Cache) GetMetrics() *PerformanceMetrics {
return &PerformanceMetrics{
CacheHitRate: c.calculateHitRate(),
DeviceLookupTime: c.avgLookupTime,
MemoryUsage: c.getMemoryUsage(),
RefreshCount: atomic.LoadInt64(&c.refreshCount),
}
}5. 容器运行时集成
5.1 NVIDIA Container Toolkit 中的 CDI 支持
NVIDIA Container Toolkit 支持生成和管理 CDI 规范。CDI 规范可以通过以下方式生成:
# 生成 CDI 规范
nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
# 列出可用的 CDI 设备
nvidia-ctk cdi listCDI 与 Multi-Instance GPU (MIG) 完全兼容,支持 MIG 设备的自动发现和配置。
5.2 CDI 缓存配置
CDI 缓存是容器运行时与 CDI 规范交互的核心组件。以下是 CDI 缓存的配置和使用示例:
package main
import (
"github.com/container-device-interface/cdi/pkg/cdi"
)
func main() {
// 创建 CDI 缓存
cache := cdi.GetRegistry(
cdi.WithAutoRefresh(true),
)
// 配置缓存
err := cache.Configure(
cdi.WithSpecDirs("/etc/cdi", "/var/run/cdi"),
)
if err != nil {
panic(err)
}
// 刷新缓存
err = cache.Refresh()
if err != nil {
panic(err)
}
}5.3 缓存刷新机制
// 缓存刷新函数
func (c *Cache) refresh() error {
var (
specs = map[string][]*Spec{}
devices = map[string]*Device{}
conflicts = map[string]struct{}{}
specErrors = map[string][]error{}
)
// 扫描所有 CDI 目录
_ = scanSpecDirs(c.specDirs,func(path string, priority int, spec *Spec, err error) error {
if err != nil {
specErrors[path] = append(specErrors[path], err)
return nil
}
vendor := spec.GetVendor()
specs[vendor] = append(specs[vendor], spec)
// 处理设备冲突
for _, dev := range spec.devices {
qualified := dev.GetQualifiedName()
devices[qualified] = dev
}
return nil
})
c.specs = specs
c.devices = devices
c.errors = specErrors
return nil
}5.4 NVIDIA GPU CDI 实际使用案例
5.4.1 基础 GPU 访问
# 生成 NVIDIA CDI 规范
nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
# 查看生成的 CDI 设备
nvidia-ctk cdi list
# 输出示例:
# nvidia.com/gpu=0
# nvidia.com/gpu=1
# nvidia.com/gpu=all使用 CDI 设备运行容器:
# 使用单个 GPU
podman run --rm --device nvidia.com/gpu=0 nvidia/cuda:12.0-base-ubuntu20.04 nvidia-smi
# 使用所有 GPU
podman run --rm --device nvidia.com/gpu=all nvidia/cuda:12.0-base-ubuntu20.04 nvidia-smi5.4.2 MIG (Multi-Instance GPU) 支持
# 启用 MIG 模式
nvidia-smi -mig 1
# 创建 MIG 实例
nvidia-smi mig -cgi 1g.5gb,2g.10gb -C
# 重新生成 CDI 规范以包含 MIG 设备
nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
# 查看 MIG 设备
nvidia-ctk cdi list | grep mig
# 输出示例:
# nvidia.com/gpu=0:0
# nvidia.com/gpu=0:1使用 MIG 设备:
# 使用特定的 MIG 实例
podman run --rm --device nvidia.com/gpu=0:0 nvidia/cuda:12.0-base-ubuntu20.04 nvidia-smi5.4.3 GPU 共享和资源限制
{
"cdiVersion": "0.8.0",
"kind": "nvidia.com/gpu",
"devices": [
{
"name": "shared-gpu-0",
"annotations": {
"nvidia.com/gpu.memory": "4096",
"nvidia.com/gpu.compute-capability": "8.6"
},
"containerEdits": {
"env": [
"NVIDIA_VISIBLE_DEVICES=0",
"NVIDIA_MPS_ACTIVE_THREAD_PERCENTAGE=50"
],
"deviceNodes": [
{
"path": "/dev/nvidia0",
"type": "c",
"major": 195,
"minor": 0
}
]
}
}
]
}5.4.4 Kubernetes 集成示例
在 Kubernetes 环境中,CDI 可以通过以下步骤与 Pod 集成:
5.4.4.1 步骤 1: 生成 CDI 规范文件
首先,在 Kubernetes 节点上生成 CDI 规范文件:
# 在每个 GPU 节点上执行
# 生成 CDI 规范文件到标准位置
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
# 验证生成的 CDI 设备
nvidia-ctk cdi list
# 输出示例:
# nvidia.com/gpu=0
# nvidia.com/gpu=1
# nvidia.com/gpu=all5.4.4.2 步骤 2: 选择 CDI 集成方式
Kubernetes 中有两种主要的 CDI 集成方式:
1. 使用 CDI 注解方式:通过 Pod 的 annotations 字段指定 CDI 设备 apiVersion: v1
kind: Pod
metadata:
name: gpu-workload-annotations
annotations:
# 使用 CDI 注解指定设备
cdi.k8s.io/nvidia-gpu: "nvidia.com/gpu=0"
spec:
containers:
- name: cuda-container
image: nvidia/cuda:12.0-base-ubuntu20.04
command: ["nvidia-smi"]
resources:
limits:
# 仍然需要声明资源限制以触发调度
# 注意:该资源键需由 NVIDIA Device Plugin 注册,Kubernetes 本身并未内置 GPU 资源类型
nvidia.com/gpu: 12. 使用 Device Plugin 与 CDI 结合方式:通过 NVIDIA Device Plugin 与 CDI 结合使用 # 首先部署支持 CDI 的 NVIDIA Device Plugin
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
name: nvidia-device-plugin-ds
template:
metadata:
labels:
name: nvidia-device-plugin-ds
spec:
containers:
- name: nvidia-device-plugin-ctr
image: nvcr.io/nvidia/k8s-device-plugin:v0.14.0
args: ["--use-cdi=true"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
- name: cdi-spec
mountPath: /etc/cdi
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
- name: cdi-spec
hostPath:
path: /etc/cdi
然后创建使用 CDI 设备的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: gpu-workload-device-plugin
spec:
containers:
- name: cuda-container
image: nvidia/cuda:12.0-base-ubuntu20.04
command: ["nvidia-smi"]
resources:
limits:
# Device Plugin 会自动将此资源映射到对应的 CDI 设备
# 注意:该资源键需由 NVIDIA Device Plugin 注册,Kubernetes 本身并未内置 GPU 资源类型
nvidia.com/gpu: 15.4.4.3 步骤 3: 高级 CDI 配置示例
使用 MIG 设备和资源限制:
# 在节点上启用 MIG 模式
sudo nvidia-smi -mig 1
# 创建 MIG 实例
sudo nvidia-smi mig -cgi 1g.5gb -C
# 重新生成包含 MIG 设备的 CDI 规范
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yamlapiVersion: v1
kind: Pod
metadata:
name: mig-workload
annotations:
# 指定使用 MIG 实例
cdi.k8s.io/nvidia-mig: "nvidia.com/gpu=0:0"
# 设置 GPU 内存限制
cdi.k8s.io/nvidia-gpu.memory: "4Gi"
# 设置计算能力要求
cdi.k8s.io/nvidia-gpu.compute: "8.6"
spec:
containers:
- name: cuda-container
image: nvidia/cuda:12.0-base-ubuntu20.04
command: ["nvidia-smi"]
resources:
limits:
nvidia.com/mig-1g.5gb: 1上述示例展示了如何在 Kubernetes Pod 中使用 MIG 设备,通过 CDI 注解指定具体的 MIG 实例、内存限制和计算能力要求。这种方式使得 Kubernetes 能够更精细地管理 GPU 资源,提高资源利用率。
5.4.5 多 GPU 工作负载配置
# 为多 GPU 训练任务配置 CDI
nvidia-ctk cdi generate \
--output=/etc/cdi/nvidia-multi-gpu.yaml \
--device-name-strategy=index
# 运行多 GPU 容器
podman run --rm \
--device nvidia.com/gpu=0 \
--device nvidia.com/gpu=1 \
--device nvidia.com/gpu=2 \
--device nvidia.com/gpu=3 \
nvcr.io/nvidia/pytorch:23.12-py3 \
python -c "import torch; print(f'GPUs available: {torch.cuda.device_count()}')"5.4.6 性能监控和调试
# 生成包含调试信息的 CDI 规范
nvidia-ctk cdi generate \
--output=/etc/cdi/nvidia-debug.yaml \
--nvidia-ctk-path=/usr/bin/nvidia-ctk \
--ldconfig-path=/sbin/ldconfig
# 验证 CDI 规范
nvidia-ctk cdi validate --input=/etc/cdi/nvidia-debug.yaml
# 查看详细的设备信息
nvidia-ctk cdi list --verbose5.5 自动监控机制
type watch struct {
watcher *fsnotify.Watcher
tracked map[string]bool
}
// start 启动文件系统监控
func (w *watch) start(m *sync.Mutex, refreshfunc() error, dirErrors map[string]error) {
if w.watcher != nil {
go w.watch(w.watcher, m, refresh, dirErrors)
}
}
// watch 监控文件系统事件并触发缓存刷新
func (w *watch) watch(fsw *fsnotify.Watcher, m *sync.Mutex, refreshfunc() error, dirErrors map[string]error) {
for {
select {
case event, ok := <-fsw.Events:
if !ok {
return
}
// 处理文件系统事件
if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove|fsnotify.Rename) != 0 {
m.Lock()
refresh()
m.Unlock()
}
case err, ok := <-fsw.Errors:
if !ok {
return
}
// 记录监控错误
log.Printf("CDI watcher error: %v", err)
}
}
}5.6 设备注入机制
// InjectDevices 将指定的 CDI 设备注入到 OCI 规范中
// 返回未解析的设备列表和可能的错误
func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error) {
var (
unresolved []string
edits = &ContainerEdits{}
)
if ociSpec == nil {
return nil, fmt.Errorf("can't inject devices, OCI Spec is nil")
}
for _, device := range devices {
d := c.GetDevice(device)
if d == nil {
unresolved = append(unresolved, device)
continue
}
// 应用设备的容器编辑
edits.Append(d.GetContainerEdits())
}
// 将编辑应用到 OCI 规范
if err := edits.Apply(ociSpec); err != nil {
return unresolved, fmt.Errorf("failed to apply container edits: %w", err)
}
return unresolved, nil
}6. CDI 使用示例
6.1 完整的 CDI 规范示例
{
"cdiVersion": "0.8.0",
"kind": "vendor.com/device",
"devices": [
{
"name": "myDevice",
"containerEdits": {
"deviceNodes": [
{
"hostPath": "/vendor/dev/card1",
"path": "/dev/card1",
"type": "c",
"major": 25,
"minor": 25,
"fileMode": 384,
"permissions": "rw",
"uid": 1000,
"gid": 1000
}
]
}
}
],
"containerEdits": {
"env": [
"FOO=VALID_SPEC",
"BAR=BARVALUE1"
],
"deviceNodes": [
{
"path": "/dev/vendorctl",
"type": "b",
"major": 25,
"minor": 25,
"fileMode": 384,
"permissions": "rw",
"uid": 1000,
"gid": 1000
}
],
"mounts": [
{
"hostPath": "/bin/vendorBin",
"containerPath": "/bin/vendorBin"
},
{
"hostPath": "/usr/lib/libVendor.so.0",
"containerPath": "/usr/lib/libVendor.so.0"
}
],
"hooks": [
{
"hookName": "createContainer",
"path": "/bin/vendor-hook"
},
{
"hookName": "startContainer",
"path": "/usr/bin/ldconfig"
}
]
}
}6.2 使用设备的命令行示例
# 使用 podman
$ podman run --device vendor.com/device=myDevice ...
# 使用 docker
$ docker run --device vendor.com/device=myDevice ...6.3 带注解的设备示例
{
"cdiVersion": "0.8.0",
"kind": "vendor.com/device",
"devices": [
{
"name": "myDevice",
"annotations": {
"whatever": "false",
"whenever": "true"
},
"containerEdits": {
"deviceNodes": [
{
"path": "/dev/vfio/71"
}
]
}
}
]
}7. 生态与发展现状
7.1 业界采用情况
CDI 作为一个相对较新的规范,已经获得了多家主要硬件厂商和容器技术提供商的支持:
| NVIDIA | ||
| Intel | ||
| AMD | ||
| Mellanox | ||
| Kubernetes | ||
| containerd | ||
| Podman | ||
| CRI-O | ||
| Docker |
目前,除 NVIDIA 外,Intel(GPU、FPGA)、AMD(ROCm)、Mellanox(RDMA)等厂商也在积极推进对 CDI 的支持,这表明 CDI 正逐渐成为容器设备访问的行业标准。随着更多硬件厂商的加入,CDI 生态系统将更加丰富和完善,为用户提供更加一致和简化的设备访问体验。
7.2 实际应用案例
以下是一些 CDI 在实际环境中的应用案例:
1. 大规模 AI 训练集群:使用 CDI 管理数百个 GPU 节点,简化了设备配置和资源管理 2. 混合云环境:通过 CDI 实现跨云平台的一致设备访问体验 3. 边缘计算:在资源受限的边缘设备上,CDI 提供了轻量级的设备管理解决方案 4. 多厂商硬件环境:在同时使用 NVIDIA、Intel 和 AMD 硬件的环境中,CDI 提供统一接口
7.3 未来发展趋势
CDI 规范仍在积极发展中,未来有望在以下方面取得更大进展:
1. 更广泛的设备支持:扩展到更多类型的硬件设备,如 TPU、DPU、SmartNIC 等专用加速器 2. 更深入的运行时集成:与更多容器运行时和编排系统实现原生集成 3. 更完善的资源调度:提供更精细的设备资源分配和调度能力 4. 更强大的监控能力:增强对设备使用情况的监控和指标收集 5. 更丰富的生态系统:吸引更多硬件厂商和软件供应商加入 CDI 生态
8. 总结
Container Device Interface (CDI) 为容器生态系统提供了一个强大而灵活的第三方设备支持标准。通过标准化设备访问方式,CDI 简化了供应商的开发工作,提高了容器运行时的互操作性,并为复杂设备的容器化使用提供了完整的解决方案。
8.1 主要优势
1. 标准化:为容器运行时提供统一的第三方设备支持标准 2. 简化开发:减少供应商为不同运行时编写和维护多个插件的需要 3. 灵活性:允许运行时和编排器具有很大的灵活性 4. 透明性:对集群管理员和应用程序开发人员透明 5. 可扩展性:支持复杂的设备初始化和供应商特定软件的自动注入
8.2 当前限制
1. 规范稳定性:规范仍在积极开发中,可能引入破坏性更改 2. 生态系统成熟度:作为相对较新的标准,生态系统支持仍在完善中 3. 复杂性管理:对于简单设备访问场景,CDI 规范可能引入额外的配置复杂性,但这种标准化带来的长期收益通常超过初期的复杂性成本 4. 平台限制:CDI 在某些平台和配置上存在限制,需要根据具体环境进行兼容性测试 5. 运行时兼容性:不同容器运行时对 CDI 的支持程度可能存在差异
9. 参考资料
1. NVIDIA GPU Operator CDI 支持 2. NVIDIA Container Toolkit CDI 支持 3. CDI 官方规范 4. Container Device Interface GitHub 仓库 5. NVIDIA GPU Operator 发布说明 6. 容器运行时接口 (CRI) 规范 7. OCI 运行时规范 8. Kubernetes Enhancement Proposal: CDI Support (KEP-3063) 9. Intel GPU CDI 支持 10. AMD ROCm CDI 支持