从云原生走向 AI 原生:一套面向未来的架构方法论 → 阅读《AI 原生基础设施》

容器与 GPU 的问题:为什么不能开箱即用

草稿

GPU 无法在容器里“开箱即用”不是缺陷,而是架构使然:容器是内核特性的组合,而 GPU 恰恰不是内核原生资源。

容器究竟如何工作

容器不是轻量级虚拟机。它是一组 Linux 内核特性的集合(主要是命名空间 namespace 与 cgroups,前者负责进程、网络、文件系统、用户隔离,后者负责资源记账与限制),让一个进程相信自己独占整台机器。

当你运行 docker run nginx 时,容器运行时(Docker → containerd → runc)为进程创建隔离的命名空间、挂载容器镜像文件系统、配置网络,并对 CPU 和内存施加 cgroup 限制。这一切都是纯 Linux 内核,除了内核本来就管理的东西,没有任何硬件驱动的参与。

这正是容器启动快、重量轻的原因:它们共享主机内核,没有虚拟机管理器开销。但这个设计在加入 GPU 时制造了一个棘手问题。

根本问题:GPU 不是内核原生资源

与 CPU 核和系统内存不同(Linux 内核通过进程调度和虚拟内存系统原生管理它们),GPU 由 GPU 厂商加载的专有内核模块管理。对 NVIDIA 来说,就是 nvidia.ko 内核模块及其上的用户态驱动栈。

GPU 通过一组设备文件向用户态暴露自己。在只有一块 NVIDIA GPU 的节点上,你会发现:

# Device files created by the NVIDIA kernel module
/dev/nvidia0          # GPU 本体
/dev/nvidiactl        # GPU 控制设备(所有 GPU 共享)
/dev/nvidia-uvm       # 统一虚拟内存(UVM)驱动
/dev/nvidia-uvm-tools # UVM 调试工具
/dev/nvidia-modeset   # 显示模式设置(可选)

# 同时:必须存在于容器中的 NVIDIA 库
/usr/lib/x86_64-linux-gnu/libcuda.so.1        # CUDA 运行时
/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1   # NVML(管理接口)
/usr/lib/x86_64-linux-gnu/libnvidia-glcore.so # GL 加速

问题就在这里:一个标准容器得不到其中任何一项。容器文件系统与主机隔离,设备文件在容器里不存在,NVIDIA 驱动库也没有被挂载。即使你在容器镜像里装了 CUDA,运行时它也无处可谈。

天真的失败
不做 GPU 配置直接运行 docker run my-cuda-app,结果是:CUDA error: no kernel image is available for execution on the device,或者干脆 nvidia-smi: command not found,更糟的是:进程正常启动但悄悄回落到 CPU。没有报错,只是推理慢了 100 倍。

早期尝试:nvidia-docker v1(2016)

2016 年 NVIDIA 发布 DGX-1(世界上第一台“盒子里的 AI 超算”),随机器附带了 NVIDIA Docker,一个改造过的 Docker CLI 包装器。nvidia-docker v1 的做法是维护一个单独的 Docker 卷插件,把 GPU 库和设备文件打包进去,启动时再挂载到容器里。

这能用,但问题很深:

  • 它要求一条单独的 nvidia-docker 命令,破坏了标准 Docker 工作流和 CI/CD 流水线
  • 卷里和主机上的驱动版本必须完全一致,维护起来是噩梦
  • 它无法与 Kubernetes、Docker Swarm 或任何绕过 Docker CLI 的编排器配合
  • 每次 nvidia-docker 升级都要重建卷插件,每逢内核或驱动更新都极其脆弱

AI 社区在快速膨胀,这个 hack 走不远。

nvidia-docker v2:OCI 钩子方案(2017)

2017 年,NVIDIA 推倒重来。nvidia-docker v2 不再包装 Docker CLI,而是引入了自定义 OCI 运行时的概念,在最底层钩进容器创建过程本身。

关键洞察是:开放容器倡议(Open Container Initiative,OCI)运行时规范定义了容器如何被创建。参考实现 runc 遵循该规范。规范允许预启动钩子(pre-start hook):在容器创建之后、启动之前运行的可执行文件。NVIDIA 正是利用这个钩子点把 GPU 访问注入容器。

架构转变
NVIDIA 没有修改 Docker,而是在 runc 外面包了一层薄壳,把 GPU 资源作为预启动钩子注入。Docker 调用这个包装器(nvidia-container-runtime)的方式与调用 runc 完全一样。Docker 无需任何改动;containerd、CRI-O 及任何兼容 OCI 的运行时同理。

这一钩子机制后来被标准化为 CDI(容器设备接口),并成为 DRA 路径的注入基础,从 v2 到 CDI 再到 DRA 一脉相承。

总结

容器与 GPU 的缝隙本质上是两套世界的接口失配:容器世界只认识内核原生资源,GPU 世界由厂商内核模块和用户态库构成。nvidia-docker v2 用 OCI 预启动钩子在进程创建的最底层缝合了这道缝,其思想延续至今。下一章将拆解完成这件事的完整工具链。

创建于 2026/08/30 更新于 2026/08/30 1523 字 阅读约 4 分钟