CDI 规格的读与写
一句话总结
CDI 是一份用 YAML 描述“要把这个设备放进容器,需要做什么”的规范。它把 runtime hook 这个黑盒变成了标准文件。
为什么需要它
过去要在 Docker 中使用 GPU,必须插入名为 nvidia-container-runtime 的 runtime hook。容器启动前,hook 会挂载驱动库并添加设备节点。它虽然能工作,却存在问题。
- **外部无法知道它做了什么。**hook 内部逻辑是黑盒。
- **每个厂商都制作自己的 hook。**NVIDIA、AMD、FPGA、InfiniBand 各用不同方式。
- **必须更换 runtime。**需要修改设置,以 nvidia-container-runtime 代替 runc。
CDI 把它改成了声明式规范。只要在 /etc/cdi/*.yaml 中写明“请求这个设备名时,添加这些设备节点、挂载和环境变量”,支持 CDI 的 runtime(podman、containerd、CRI-O)就会照此执行。
它如何工作
规范结构
cdiVersion: "0.6.0"
kind: nvidia.com/gpu
devices:
- name: "0"
containerEdits:
deviceNodes:
- path: /dev/nvidia0
- path: /dev/nvidiactl
- path: /dev/nvidia-uvm
mounts:
- hostPath: /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.550.90.07
containerPath: /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.550.90.07
options: ["ro", "nosuid", "nodev", "bind"]
env:
- NVIDIA_VISIBLE_DEVICES=0
hooks:
- hookName: createContainer
path: /usr/bin/nvidia-ctk
args: ["nvidia-ctk", "hook", "update-ldcache"]
- name: all
containerEdits:
deviceNodes:
- path: /dev/nvidia0
- path: /dev/nvidiactl
核心规则如下。
kind使用<벤더>/<클래스>格式,如nvidia.com/gpu、amd.com/gpu、intel.com/fpga。vendor 部分必须是 domain 格式。- **设备名以
kind=name引用。**例如nvidia.com/gpu=0、nvidia.com/gpu=all。 containerEdits是连接该设备时添加到 OCI spec 的内容,常见的有 deviceNodes、mounts、env、hooks。- spec 文件放在
/etc/cdi或/var/run/cdi;rootless 时也可指定用户路径。
实际使用
# 스펙 생성 (실제 GPU 가 있는 호스트에서)
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
# 등록된 장치 확인
nvidia-ctk cdi list
# nvidia.com/gpu=0
# nvidia.com/gpu=all
# 컨테이너에서 사용
podman run --rm --device nvidia.com/gpu=all nvidia/cuda:12.4.1-base nvidia-smi
Docker 的 --gpus all 对应 --device nvidia.com/gpu=all。新版 podman 也把 --gpus 作为兼容 flag 接受,但 CDI 写法才是正式格式。
**rootless 也能使用 GPU。**GPU 驱动在内核空间运行,因此容器权限级别不影响性能。能够读取系统级 /etc/cdi/nvidia.yaml 时可直接使用;否则可在用户空间生成 spec 并指定该目录。
在 containerd 中注册
Kubernetes 中由 containerd 读取 CDI。config.toml 需要启用相应设置,而单独注册 runtime 的旧方式仍在使用。
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
BinaryName = "/usr/bin/nvidia-container-runtime"
SystemdCgroup = true
使用该 runtime 的 workload 通过 RuntimeClass 选择它。
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: nvidia
handler: nvidia
诀窍是不要把 default_runtime_name 改为 nvidia,否则不用 GPU 的 Pod 也会经过该 runtime。使用 RuntimeClass 只指定需要的 workload 更安全。
实际工作中的表现
更新驱动后无法识别 GPU。十有八九是忘了重新生成 CDI spec(nvidia-ctk cdi generate)。spec 中包含带版本号的库文件路径,驱动升级后旧路径就会消失。
**容器内 nvidia-smi 正常,但 CUDA 程序失败。**这通常是只挂载了部分所需库,应检查 spec 的 mounts 列表。
下一项实践将做什么
在 /etc/cdi/ 中直接编写 CDI spec。由于没有真实 GPU,评分将检查规范语法、放置位置以及 --device 参数格式,这正是现场容易出错的地方。