还在纠结容器里跑nvidia-smi老是在内网服务器上报错?直接把nvidia-docker2换成nvidia-container-toolkit离线装一遍,省掉一堆在线源依赖的麻烦。
先交代下背景,这类问题最常见的场景是这样的:生产线上的 GPU 服务器和公网完全隔离,没有 apt/yum 在线源,但业务方又明确要求所有推理服务必须容器化,容器里必须能用到 GPU。你手上可能只有一块刚装好驱动的机器,搜遍全网发现nvidia-docker2早在 2022 年之后就被官方整合到nvidia-container-toolkit一套体系里了,直接在线装当然简单,离线装就涉及到依赖包、仓库源、daemon 配置这些一连串连锁问题。这篇文章就是我从实际项目里踩完坑之后整理出来的完整离线安装流程,面向有内网 GPU 服务器、需要给 Docker 加 GPU 能力的运维和 AI 平台工程师,不讲废话,直接给能抄作业的命令和踩坑记录。
1. 项目概述与核心需求拆解
1.1 先说清楚 nvidia-docker2 和 nvidia-container-toolkit 的关系
很多老运维还停留在nvidia-docker2时代。2022 年之前,NVIDIA 官方提供的是一个叫nvidia-docker2的元包,它会自动拉取nvidia-container-runtime和nvidia-container-toolkit这两个组件。2022 年之后,官方把组件整合成了nvidia-container-toolkit,nvidia-docker2则变成了一个兼容性的过渡包。
核心的组件其实有三个:
| 组件名 | 作用 | 说明 |
|---|---|---|
| libnvidia-container1 | 底层 C 库,负责和 NVIDIA 驱动交互 | 所有组件的依赖基础 |
| libnvidia-container-tools | 命令行工具集 | 提供nvidia-container-cli |
| nvidia-container-toolkit | 将 GPU 能力桥接到 Docker/containerd | 核心运行时插件 |
| nvidia-docker2 | 元包,自动配置 Docker daemon | 老版本兼容包 |
所以离线安装的本质,就是把这几个 deb/rpm 包全部找齐,传到内网机器上按顺序安装,再配置 Docker 的 runtime。
1.2 离线安装和在线安装的路径差异
在线安装只需要两行命令,因为系统会自动从 NVIDIA 官方源拉取所有依赖:
apt-get install -y nvidia-docker2 systemctl restart docker但离线环境里没有源,系统根本不知道去哪里找libnvidia-container1。所以我们要做的,是在一台能联网的同版本系统上,把所需软件包及它们的所有依赖先下载到本地,再做成压缩包或者本地仓库带到内网去用。
NVIDIA 官方目前维护了两套仓库:deb 系针对 Ubuntu/Debian,rpm 系针对 CentOS/RHEL/Rocky。离线机器是哪个发行版,就必须在相同版本的联网机器上下包。比如内网是 Ubuntu 22.04,你拿 CentOS 7 的包过去,大概率会因为 glibc 版本过老或过新直接装不上。
2. 离线安装前的三件事:确认系统环境、找齐依赖包、规划安装方式
2.1 上机器先记下这三个信息
不要急着下载任何东西,先在内网机器上收集三个关键点:
cat /etc/os-release # 发行版和版本号 docker version --format '{{.Server.Version}}' # Docker 版本 nvidia-smi # host 上 NVIDIA 驱动是否正常这三个信息决定了后续所有操作。原因很直接,nvidia-container-toolkit对 Docker 版本有最低要求,Docker 19.03 以上才支持--gpus参数,Docker 20.10 以上配合 toolkit 才能稳定使用 NVIDIA Container Runtime。host 的 NVIDIA 驱动必须已经装好且nvidia-smi能正常输出,否则后面容器里就算配置对了也会报 driver 错误。
我遇到过一个典型的内网环境:系统是 Ubuntu 20.04,Docker 版本是 19.03.9,一开始直接从官网拿最新的nvidia-container-toolkitdeb 包,结果装完 Docker 启动报错 runtime 不识别。查了半天才发现是新版 toolkit 需要 Docker 的--gpus支持,而 19.03.9 虽然有这个参数,但和 toolkit 新版之间存在兼容缝隙。最终换回nvidia-container-toolkit=1.14.5才稳定下来。
2.2 在联网机器上把依赖包全部下载下来
这里分 deb 系和 rpm 系两套操作,目标都是在联网机器上先添加 NVIDIA 官方源,然后强制下载全部依赖到本地。
deb 系(Ubuntu/Debian)官方推荐做法:
curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add - distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | sudo tee /etc/apt/sources.list.d/libnvidia-container.list sudo apt-get update下载安装包,这一步一定要用--download-only:
cd /tmp/nvidia-offline apt-get install --download-only nvidia-docker2下载完成后,所有 deb 文件会躺在/var/cache/apt/archives/。整理一下:
mkdir -p /tmp/nvidia-offline cp /var/cache/apt/archives/*.deb /tmp/nvidia-offline/ tar czf nvidia-offline-deb.tar.gz nvidia-offline/要注意的是,apt-get install --download-only只会下载包本身和它声明的依赖,但如果系统里已经装过某些依赖包,就不会重复下载。理论上没问题,因为离线机器一般也是干净的,但保险起见,我通常还会手动把核心包再apt-get download一遍:
cd /tmp/nvidia-offline apt-get download libnvidia-container1 libnvidia-container-tools nvidia-container-toolkit nvidia-container-runtime nvidia-docker2rpm 系(CentOS/Rocky)做法:
curl -s -L https://nvidia.github.io/libnvidia-container/rpm/libnvidia-container.repo | sudo tee /etc/yum.repos.d/libnvidia-container.repo yum install -y yum-utils cd /tmp/nvidia-offline yumdownloader --resolve nvidia-docker2如果联网机和离线机架构不一致,比如你是在 x86 机器上下载但内网是 ARM64 机器,记得在配置文件里改baseurl中的x86_64为aarch64,或者用--arch=aarch64指定下载架构。
2.3 离线安装方案的三种选择
离线环境中实际有三种做法,根据服务器数量和维护成本来选择:
| 方案 | 适用场景 | 操作方式 |
|---|---|---|
| 纯 rpm/deb 本地安装 | 单台或几台机器 | rpm -ivh *.rpm或dpkg -i *.deb |
| 搭建本地 Yum/Apt 仓库 | 批量服务器,方便统一管理 | 用createrepo或dpkg-scanpackages生成仓库元数据 |
| 容器镜像方式 | 和现有 CI/CD 深度集成 | 将 toolkit 打包进基础镜像,直接在容器内配置 runtime |
根据我们实际运维经验,3 台以内用第一种,超过 5 台建议直接搭本地仓库,因为手动rpm -ivh装 5 台以上很容易出依赖不一致的问题,而且后续版本升级还要重新走一遍流程。我们团队后来统一做了个内网 apt 源,nvidia-container-toolkit相关包定期同步一次,各服务器apt-get install -y一条命令就完事。
3. 核心安装流程与配置落地
3.1 上传解压,按依赖顺序安装 deb 包
拿到离线压缩包后,上传到内网服务器,解压后先dpkg -i试装一次:
tar xzf nvidia-offline-deb.tar.gz -C /tmp/ cd /tmp/nvidia-offline dpkg -i *.deb如果依赖缺失,dpkg会明确列出缺哪些包,最常见的是libseccomp2、libcap2。这时候不要直接在内网机上去apt-get install -f,因为它会尝试访问在线源导致超时。正确的做法是回到联网机上补齐这些包,再回到内网机执行dpkg -i。
正确的安装顺序建议是:
dpkg -i libnvidia-container1_*.deb dpkg -i libnvidia-container-tools_*.deb dpkg -i nvidia-container-toolkit_*.deb dpkg -i nvidia-container-runtime_*.deb dpkg -i nvidia-docker2_*.deb为什么强调顺序?因为nvidia-docker2这个元包在安装时会触发 Docker daemon 的配置操作,如果前面的 runtime 没装好,它会直接报错,哪怕你把依赖全塞进去了也可能因为 runtime 缺失而失败。我踩过一次坑,直接dpkg -i *.deb一次全装,结果nvidia-docker2先把 daemon 配置改了,发现/usr/bin/nvidia-container-runtime还不存在,配置文件生成一半就中断了,后面排查起来特别费劲。
rpm 系同理:
rpm -ivh libnvidia-container1-*.rpm rpm -ivh libnvidia-container-tools-*.rpm rpm -ivh nvidia-container-toolkit-*.rpm rpm -ivh nvidia-container-runtime-*.rpm rpm -ivh nvidia-docker2-*.rpm3.2 核心配置文件 daemon.json 的写法
装完包后,Docker 并不会自动切换 runtime,需要手动在/etc/docker/daemon.json里注册。这也是离线安装和在线安装最容易出问题的地方。
检查一下/etc/docker/daemon.json是否存在,如果不存在就新建。写入内容如下:
{ "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } } }如果机器上之前配过镜像加速或者其他参数,要追加这部分,不要直接覆盖:
{ "registry-mirrors": ["https://docker.mirrors.example.com"], "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } } }然后重启 Docker:
systemctl restart docker重启后验证 runtime 是否被识别:
docker info | grep -A 5 Runtimes正常情况下能输出类似Runtimes: nvidia runc的信息。如果没有出现nvidia,先检查 daemon.json 的 JSON 格式是否合法,然后确认/usr/bin/nvidia-container-runtime这个路径是否存在。我遇到过有的版本安装后二进制在/usr/bin/nvidia-container-runtime-hook,真正的入口脚本是/usr/bin/nvidia-container-runtime,如果你那台机器缺这个文件,可以直接建一个软链过去。
3.3 检查 toolkit 自带的 config.toml
nvidia-container-toolkit安装后会自动生成一个配置文件/etc/nvidia-container-runtime/config.toml。一般不需要手动改,但内网环境有个特殊点:如果宿主机的/dev/nvidia*设备节点没有被 Docker 挂载进去,需要确认nvidia-container-runtime-hook的nvidia-container-cli路径是否正常。
执行下面的命令,看是否能正常查出 GPU:
nvidia-container-cli info正常输出类似:
NVRM version: 525.60.11 CUDA version: 12.0 Device Index: 0 Device Minor: 0 Model: Tesla T4如果这里报错,后面容器里肯定也是不行的。nvidia-container-cli info是排障的第一入口,比直接跑docker run快得多。
4. 实际验证 GPU 容器是否可用
4.1 从 Docker Hub 拉一个带 CUDA 的镜像到内网
验证容器内 GPU 是否可用,最直接的方法是跑一个带 nvidia-smi 的基础镜像。但内网没有外网,所以需要先在能联网的机器上拉取再导出:
docker pull nvidia/cuda:11.8.0-base-ubuntu20.04 docker save -o cuda-base.tar nvidia/cuda:11.8.0-base-ubuntu20.04然后把这个 tar 包传到离线机器上:
docker load -i cuda-base.tar如果你连镜像都不方便传,也可以用宿主机自带的方式验证:在docker run时挂载宿主机的nvidia-smi二进制和驱动目录。不过这个方案对驱动库依赖很高,很容易报库文件找不到,我建议还是直接传镜像。
4.2 用 --gpus 参数启动容器
Docker 19.03 及以上版本支持--gpus参数:
docker run --rm --gpus all nvidia/cuda:11.8.0-base-ubuntu20.04 nvidia-smi如果配置正确,容器内会正常输出nvidia-smi的结果。这验证了两层能力:第一层是 Docker 能正确识别并使用nvidiaruntime,第二层是 runtime 能成功把宿主机的 GPU 设备文件、驱动库和一个最小化的 CUDA 运行环境挂载进容器。
如果你的 Docker 版本刚好是 19.03 以下,只能通过环境变量方式触发:
docker run --rm -e NVIDIA_VISIBLE_DEVICES=all nvidia/cuda:11.8.0-base-ubuntu20.04 nvidia-smi这种方式会走nvidia-container-runtime-hook的兼容路径,也能达到同样的效果。但我还是建议尽量升级 Docker,毕竟 19.03 以下的版本太老,和现在多数容器编排系统兼容性不好。
4.3 指定哪块 GPU 资源
--gpus all在物理机上是全部 GPU,也可以精确控制:
docker run --rm --gpus '"device=0,1"' nvidia/cuda:11.8.0-base-ubuntu20.04 nvidia-smi -L也可以通过环境变量方式限定:
docker run --rm -e NVIDIA_VISIBLE_DEVICES=0 nvidia/cuda:11.8.0-base-ubuntu20.04 nvidia-smi -L这里的0是宿主机nvidia-smi里看到的 GPU 序号。需要注意的是,如果宿主机上有两块 GPU,而你不指定NVIDIA_VISIBLE_DEVICES,默认情况下容器内是看不到任何 GPU 的,只有显式加上--gpus all或设置设备列表才会暴露进去。新版 toolkit 也支持通过 UUID 指定 GPU,写法是:
docker run --rm --gpus '"device=GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"' ...这些参数在部署多租户环境时非常有用,可以保证每个容器只看得到分配给它的那张卡。
5. 常见问题与排查技巧实录
5.1 问题速查表
离线装完nvidia-docker2后,跑容器大概率会碰到下面这些错误,我按实际踩坑频率排了个序:
| 报错信息 | 可能原因 | 解决办法 |
|---|---|---|
Unknown runtime specified nvidia | daemon.json 未生效或配置格式错误 | docker info确认 Runtimes,检查 JSON 格式 |
could not select device driver "nvidia" with capabilities: [[gpu]] | Docker 没有注册 nvidia runtime | 重启 Docker,重查 daemon.json |
nvidia-container-cli: initialization error: driver error: failed to process request: unknown | 宿主机驱动问题 | 先确认nvidia-smi正常,检查/dev/nvidia*节点 |
nvidia-container-cli: mount error: failed to mount /dev/nvidia0 | 容器权限或版本限制 | 确认 libnvidia-container 与驱动版本匹配 |
docker: Error response from daemon: Unknown runtime specified nvidia. | daemon.json 配置后没重启 | systemctl restart docker |
dpkg: dependency problems - leaving triggers unprocessed | 依赖关系不完整 | 回到联网机补齐缺失依赖包 |
rpm -ivh报libnvidia-container1 conflicts | 之前装过旧版本 | rpm -e卸载旧包或使用--replacefiles |
5.2 排查技巧:先分清是 Docker 层问题还是 toolkit 层问题
这是最核心的排障思路。遇到容器报错,先看报错来自哪一层。
如果是 Docker 层,比如Unknown runtime specified nvidia,问题一定出在 daemon.json 和 Docker 的 runtime 注册上,和 GPU 驱动无关。先跑docker info看 Runtimes 列表。
如果是 toolkit 层,比如nvidia-container-cli开头的报错,问题大概率在驱动和设备文件。这时直接在宿主机上跑:
nvidia-container-cli info如果这个命令都不通过,说明 toolkit 根本无法和驱动正常通信,那问题就回到了宿主机驱动侧,而不是容器侧。这个思路能帮你节约大量时间,不用一头扎进容器日志里去看那些 dump 出来的 NVRM 错误。
5.3 需要注意的离线环境坑
第一个坑是不要在内网机上直接执行apt-get install -f或yum install -y,因为内网没有源,命令会卡在连接超时上,有时还会把 dpkg 数据库锁住,后面想手动修都麻烦。
第二个坑是版本匹配。nvidia-container-toolkit非常依赖宿主机的 NVIDIA 驱动版本,比如驱动是 470 系列,而 toolkit 是最新版本,有时会出现NVRM version mismatch的报错。解决方案就是下载时不要盲目追求最新,可以到 NVIDIA 官方 GitHub Release 页面找一个和驱动匹配的 toolkit 版本。
第三个坑是 Docker 和 containerd 的差异。如果你用的是 Kubernetes 环境,节点上跑的是 containerd 而不是 Docker CLI,配置方式完全不是daemon.json,而是要改 containerd 的 config.toml,把nvidiaruntime 注册进去。这个场景不在本文范围内,但提醒一下,别拿着一套配置到处套。
第四个坑是 kernel 模块和/dev/nvidia*设备节点。有些服务器重启后/dev/nvidia*节点可能没有自动创建,导致容器运行时找不到设备。可以用nvidia-smi -pm 1开启持久化模式,或者检查 udev 规则。内网机器如果装了最新驱动但 udev 规则没配置好,重启后很容易出现这个问题。
6. 扩展思路:从单机安装到批量部署
单台服务器离线装好只是开始。如果是管理一个集群,所有机器都走手动传包显然不现实。两个方向可以做。
方向一是自建内网源。在联网环境定期同步libnvidia-container和nvidia-container-toolkit的包到内网仓库,然后各节点配置/etc/apt/sources.list或/etc/yum.repos.d/,之后直接在线安装。本质上是把离线问题转成了内网统一的软件源问题,这对运维团队来说是最省心的长期方案。
方向二是把整个 GPU 容器运行环境做成镜像。在基础镜像里预置nvidia-container-toolkit的相关配置,部署时直接跑自定义运行脚本,不用在每台宿主机上重复配置。这个方案适合容器平台已经标准化、但宿主机操作系统碎片化比较严重的场景。
另外如果你的离线服务器是 composes 环境、容器编排平台直接管理容器运行时,那就要跳出 Docker 的概念,直接看 containerd 的配置方式。NVIDIA 官方文档里有 containerd 的完整配置示例,核心是在/etc/containerd/config.toml里增加一个 runtime 选项,指定/usr/bin/nvidia-container-runtime作为 runc 的替代品,然后用nerdctl run --gpus all或 Kubelet 配置来触发 GPU 调用。
我在交付一个客户现场的 GPU 推理平台时,200 多台内网机器就是靠自建源加无人值守脚本完成的离线部署。先在源服务器上停好所有 deb/rpm 包,然后写一个安装脚本,判断每台机器的系统版本、Docker 版本、是否有 GPU,再自动执行对应安装步骤,整个过程从原来的单台半小时缩短到十分钟以内。这个思路你可以直接用,脚本不用太复杂,核心就是判断发行版和架构,然后调用对应命令。
7. 最后再分享一个小技巧:保留一套完整的离线安装包
我在实际项目里养成了一个习惯:每次给客户交付离线安装方案,都会保留一套完整的离线包文件,包括所有 deb/rpm 包、daemon.json 示例、验证命令脚本、常见问题说明。用 tar 打成一个包归档。这样下次有同版本的新机器要部署,直接复制过去,十分钟内就能解决。
很多显卡厂商在发布新驱动时,也会同步更新nvidia-container-toolkit的版本号。建议把内网仓库中的 toolkit 版本和驱动版本一起更新,避免出现驱动新但 runtime 旧这种半新不旧的尴尬状态。如果离线环境里已经有一套跑通的组合,我建议你记录下这个组合的精确版本号,不要轻易变更。我在生产上见过太多因为 toolkit 小版本升级导致容器起不来的故障,内网环境尤其要克制“顺手升级一下”的冲动。
按这个流程走下来,从收集依赖包到最终 Docker 容器里正常输出nvidia-smi,一台新机器最多二十分钟就能搞定。核心就是提前把包备好、顺序装对、daemon 配置准确,剩下的就是验证环节的事。