☰
Ubuntu下Docker与NVIDIA Container Toolkit安装配置全攻略
2026/10/17 7:46:17 网站建设 项目流程

1. 为什么要在Ubuntu上组合Docker和NVIDIA Container Toolkit

先说一个我遇到过很多次的场景:开发环境里跑深度学习训练,或者部署推理服务,代码在本地调得好好的,一上服务器就报CUDA版本不匹配、驱动找不到、环境变量乱套。这些问题的根子,绝大多数在于Conda、pip、系统全局Python混在一起,依赖拆东墙补西墙。容器化的意义,不但是把代码跑起来,而是把整个运行环境——CUDA、cuDNN、Python、依赖库——连同操作系统层面的兼容性一起打包,换一台机器照样能跑。

Docker在Linux下性能损耗极低,进程隔离、文件系统分层这些机制,让运维和开发能共用一套交付物。但在安装Docker时,Ubuntu用户通常会撞上一个坑:Docker官方源的网络不稳定,尤其在服务器地域偏僻或者防火墙策略严格的情况下,apt update直接卡死或超时。所以国内源适配不只是“换个地址加速”那么简单,还涉及GPG密钥、源格式、系统版本对应关系这些细节,搞错了连apt本身都会崩。

NVIDIA Container Toolkit解决的是另一个层次的问题:容器默认环境是无GPU的,Docker本身不会把你的NVIDIA驱动透传进容器。Toolkit做的事情,就是把宿主机的GPU设备、驱动库、CUDA运行时按需映射到容器内部,让容器里的程序直接调用nvidia-smi和CUDA。注意,它不含驱动程序,宿主机必须有NVIDIA驱动,容器内装的是CUDA镜像层。

这套组合装好之后,基本的效果是:你可以在宿主机上只维护一套驱动,然后跑任意CUDA版本的容器镜像,互不干扰。比如一个容器用CUDA 11.8跑旧项目,另一个用CUDA 12.4做新实验,底层驱动统一,镜像各自独立。文章后面会完整演示从零开始安装、配置、验证、排查的过程,覆盖Ubuntu 20.04和22.04两个常见版本,你照着抄就能用。

核心关键词先理清楚:Docker Engine、NVIDIA Container Toolkit、国内软件源、GPU透传、nvidia-smi验证。后续所有操作都围绕这几个环节展开。

2. 环境准备与前置检查

2.1 确认操作系统版本和架构

这一步看似废话,但实际翻车的频率极高。Ubuntu的LTS版本之间,apt源的格式、Docker的稳定仓库路径、Toolkit的兼容范围都有差异。操作前先跑一条命令:

lsb_release -a

重点关注Codename这一行,20.04是focal,22.04是jammy。这一步决定了后面用的源地址,因为不同版本对应的Docker源路径里带的代号不一样,写错了apt会直接报404。架构方面,除非是树莓派这类ARM设备,大部分服务器是x86_64,用uname -m确认一下。如果你是ARM架构,驱动和Toolkit的适配方式完全不同,本文不展开。

2.2 检查GPU驱动状态

Docker和Toolkit装得再漂亮,宿主机没有驱动也白搭。执行下面命令:

nvidia-smi

正常情况下会输出驱动版本、CUDA版本、GPU型号和显存占用。如果提示command not found,说明驱动没装或者没进PATH;如果输出了错误信息,比如“NVIDIA-SMI has failed because it couldn‘t communicate with the NVIDIA driver”,多半是内核驱动和用户态驱动版本不匹配,这时要先解决驱动问题再继续。

驱动版本和CUDA版本的理解不要混淆。nvidia-smi输出的CUDA Version,指的是当前驱动支持的“最高CUDA运行时版本”,不代表系统里装了CUDA Toolkit。容器内用哪个CUDA版本,由你拉取的镜像决定,只要不超过驱动支持的版本就可以。

2.3 清理旧版本残留

很多服务器是从老教程一路升过来的,系统里可能残留着旧版Docker(docker.io、docker-engine)或者早期的nvidia-docker。不清理干净,装新版时会遇到命令冲突、二进制文件被锁、systemd服务起不来的问题。建议先执行:

sudo apt remove docker docker-engine docker.io containerd runc

这一步会保留/var/lib/docker里的数据目录,如果确定重装后不需要旧数据,可以手动删掉这个目录。但注意,删了就真的没了,别在不稳定网络环境下做这个决定。

旧版nvidia-docker同理:

sudo apt remove nvidia-docker2 nvidia-container-runtime

此外,检查一下/etc/docker/daemon.json,如果存在且里面有配置,先备份一份,后面可能要合并。有些老配置里残留了非法的registry-mirrors地址,会导致新版本Docker启动失败。

3. Docker Engine安装与国内源配置

3.1 apt源选择:官方源和国内镜像对比

Docker官方文档推荐的安装方式是把官方源写进/etc/apt/sources.list.d/docker.list。但在国内网络条件下,这个源经常连不上,所以要做国内源适配。这里选择使用TUNA(清华源)作为例子,注意这与Docker Hub的镜像加速器不同,TUNA是完整的apt软件源,镜像的是Docker的apt仓库,而镜像加速器是拉取镜像时用的。两者要分开配置。

TUNA提供的Docker apt源地址格式是:

https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu

这个源里包含了docker-ce、docker-ce-cli、containerd.io等所有官方组件,而且和官方源的GPG签名保持一致。后面配置镜像加速器时,我们会在/etc/docker/daemon.json里设置registry-mirrors,这样你pull镜像时走的也是国内节点。两条路都畅通,安装才是完整的国内适配。

3.2 添加GPG密钥的正确姿势

这里有个常见的坑:很多人直接把官方GPG密钥地址换成了镜像源的,结果apt update时报错签名校验失败。正确做法是从官方地址下载密钥,因为密钥只是一个公钥文件,不涉及大流量下载,不需要走镜像。

sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg

这里有个细节:gpg --dearmor会把ASCII格式的密钥转成二进制,二进制格式的密钥文件在apt源配置里是被推荐的,旧教程里那种直接复制ASCII密钥到sources.list文件的方式不推荐。另外,生成的文件权限要设置为a+r,否则apt可能会因为权限问题拒绝使用。

3.3 写入apt源配置

创建源文件:

echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

这条命令里的$(lsb_release -cs)会自动替换成当前系统代号,比如focal或jammy。需要注意:如果你用的系统是基于Ubuntu的衍生发行版,lsb_release -cs的结果可能和Ubuntu官方仓库路径对不上,这时需要手动指定一个Ubuntu LTS代号。

配置完成后,先更新再装:

sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

docker-compose-plugin是Docker官方打包的Compose v2插件,建议一并安装,后续跑多容器项目会方便很多。这条命令会顺便装上buildx插件,构建镜像时能直接启用BuildKit,性能和传统模式差距明显。

3.4 配置Docker镜像加速器

镜像加速器没用对,前面装上源也是白忙。新建或修改/etc/docker/daemon.json:

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.nju.edu.cn" ] }

需要说明的是,这些加速器的可用性在不同时期会有变化,而且有些加速器只支持Docker Hub的公共镜像,拉取私有仓库或者特定来源的镜像不会生效。配置多个加速器是合理的做法,Docker拉取时会在列表里自动切换,但第一个不可用时,切换过程会有额外超时开销,所以不要无脑堆十几个,三四个合适。

改完配置后重启Docker:

sudo systemctl restart docker

然后可以测试镜像加速是否生效:

docker info | grep -A 5 "Registry Mirrors"

看到列表输出就算成功。更直接的测试是拉一个常用镜像,比如hello-world,如果秒下不用转圈,说明加速器工作正常。

4. NVIDIA Container Toolkit完整安装与配置

4.1 添加Toolkit的apt源

NVIDIA官方源和Docker官方源一样有网络问题,所以这里同样要适配国内镜像。Toolkit的源配置方式比较特殊,它用的是NVIDIA官方的apt仓库,但镜像源地址可以直接换成清华源。先创建密钥目录和源文件:

curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/nvidia-container-toolkit/gpgkey | sudo gpg --dearmor -o /etc/apt/keyrings/nvidia-container-toolkit.gpg

这里注意一个细节:NVIDIA官方的安装文档里,GPG密钥地址通常是:

https://nvidia.github.io/libnvidia-container/gpgkey

但在TUNA镜像上的路径略有不同。密钥下载成功后,写入源配置:

echo \ "deb [signed-by=/etc/apt/keyrings/nvidia-container-toolkit.gpg] https://mirrors.tuna.tsinghua.edu.cn/nvidia-container-toolkit/stable/ubuntu22.04/$(ARCH) /" | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null

需要注意,这里我在示例里直接写了ubuntu22.04,因为不同Ubuntu版本对应的仓库路径不完全一样。20.04版本下这个路径中的系统代号要替换成ubuntu20.04,具体写法在源文件里写清楚,避免安装时出现404。

一个比较容易踩的坑是:arm64架构机器上源路径中需要带上arm64字样,或者直接省略ARCH变量让NVIDIA自动选择。大部分x86服务器不需要担心,但这类细节在你换机器时需要重新审视。

4.2 安装Toolkit本体

执行安装:

sudo apt update sudo apt install -y nvidia-container-toolkit

安装完成后,需要配置Docker的runtime。NVIDIA Toolkit安装时不会自动修改Docker配置,需要手动执行一行命令:

sudo nvidia-ctk runtime configure --runtime=docker

这条命令会自动修改/etc/docker/daemon.json,在里面加入nvidia runtime的配置。你可以打开daemon.json验证一下,大概会多出类似这样的内容:

{ "runtimes": { "nvidia": { "path": "nvidia-container-runtime", "runtimeArgs": [] } } }

如果daemon.json里已经有了registry-mirrors配置,nvidia-ctk不会覆盖它,而是会把这两个配置合并在一起。这一步在旧版本工具上可能会出现问题,但在当前稳定版本上实测是没问题的。

配置完成后,重启Docker:

sudo systemctl restart docker

4.3 验证GPU是否成功透传

先跑一个最基础的验证:

docker run --rm --gpus all nvidia/cuda:11.8.0-base-ubuntu20.04 nvidia-smi

看到nvidia-smi输出GPU信息,说明Toolkit工作正常。如果你不想为这么一个小验证拉一个几百兆的基础镜像,也可以先拉一个小的测试镜像,比如nvidia/cuda:12.4.0-base-ubuntu22.04,体积相对小一些。不过说实话,首次拉镜像的等待是值得的,后面跑项目反正也要用。

验证通过后,可以再检查一下运行时是否被正确识别:

docker info | grep -i runtime

正常输出里应该能看到nvidia这个runtime,说明Docker已经认到了。

5. GPU容器运行验证与性能排查

5.1 容器内调用GPU的完整工作流

Toolkit装好后,日常跑容器的方式就变成了:

docker run --gpus all --shm-size=8g -it --rm nvidia/cuda:12.4.0-devel-ubuntu22.04 bash

--shm-size这个参数值得专门提一下。PyTorch的DataLoader默认使用SharedMemory做进程间通信,OpenCV在某些场景下也依赖共享内存。如果容器内/dev/shm只有默认的64MB,训练时经常莫名崩溃,报错信息可能根本不提示和共享内存相关,你排查半天最后发现只是shm太小。深度学习相关的容器建议至少给8g,不够就16g。

容器起来后,用下面命令确认GPU可见:

nvidia-smi python -c "import torch; print(torch.cuda.is_available())"

第二行命令需要你在容器里已经安装过PyTorch,如果没装,输出的是ModuleNotFoundError而非False,这是正常现象。

5.2 常见配置问题的定位思路

GPU透传失败时的报错五花八门,这里整理一个排查路径。首先是常见错误类型的对照表:

错误特征可能原因解决方向
提示could not select device driver “” with capabilities: [[gpu]]运行时未配置或Docker未重启重新执行nvidia-ctk runtime configure,重启Docker
提示Unknown runtime specified nvidiadaemon.json里的runtimes配置格式错误检查JSON语法,确认runtime名称是nvidia
容器内nvidia-smi不存在镜像未包含NVIDIA工具换用带nvidia-smi的CUDA镜像,或容器内单独安装
容器能启动但GPU驱动报权限错误用户态驱动库未映射检查/usr/lib/x86_64-linux-gnu/libnvidia-ml.so是否存在
提示CUDA version not supported容器内CUDA版本高于驱动支持上限换低版本CUDA镜像,或升级宿主机驱动

其中运行时未配置这个错误最典型,几乎每天都能在技术社区看到有人问。原因很直接:Toolkit安装完成后,必须执行一次nvidia-ctk runtime configure,否则Docker根本不知道nvidia这个runtime的存在。这个步骤在不同版本的教程里容易被跳过。

另一个常见问题是用sudo docker跑容器报环境变量方式不对。比如有的用户设置NVIDIA_VISIBLE_DEVICES=all时,没有区分这个环境变量是给nvidia-container-runtime用的,而它只能通过docker run -e参数传入。写成daemon.json里的环境变量配置,在旧版本上是不生效的。

5.3 性能测试与调优

容器跑GPU推理的额外性能损耗其实可以做到非常小,正常配置下基本可以忽略。但如果出现明显性能下降,先排查这几个位置:

  1. GPU时钟是否降频:容器内跑nvidia-smi -pl 250这样的命令(如果允许),或者主机侧用nvidia-smi -q -d CLOCK查看当前时钟频率。GPU降频常见原因是温度过高或者供电不足,和容器化本身关系不大。

  2. PCIe带宽是否正常:用nvidia-smi topo -m查看拓扑结构,如果GPU被分到CPU远端节点,跨NUMA访问会有额外的PCIe损耗。在容器内做张量搬运测试,对比宿主机CPU直接访问和容器内访问的带宽差异。

  3. 是否打开了GPU的persistence模式:宿主机执行nvidia-smi -pm 1,让GPU保持常驻工作状态。默认模式下GPU可能因为空闲自动降低工作状态,首次调用时会有唤醒延迟。对于在线推理服务,这个延迟在某些场景下是不可接受的。

还有一点经常被忽略:容器内的CUDA版本要和驱动支持的版本匹配。比如宿主机驱动是535.104.05,它支持的CUDA最高版本是12.2,你硬跑一个CUDA 12.4的容器镜像,程序可能能在部分接口上工作,但在某些模块上会出现奇怪的行为。不建议这样做。

6. 避坑心得与实用建议

6.1 必定会遇到的问题

这套环境装得多了,我把最容易出问题的点按概率排个序:

第一是apt源和系统代号不匹配。这个问题常见于麒麟、Deepin这类基于Ubuntu的国产发行版,它们把自己的版本号变成了别的代号,直接套用上面的命令会404。解决方式是手动固定代号,例如在源文件里写jammy而非使用变量。

第二是daemon.json格式错误。JSON的逗号、引号漏写一个,Docker直接起不来,而且是systemctl看不出具体原因的内部错误。排查方式是先跑dockerd手动启动,看控制台输出,或者直接看journalctl -u docker的日志。

第三是镜像加速器失效。这里多说一句:有些加速器要求Docker版本高于特定阈值,老版本会报HTTPS证书过期的错误,这时候升级Docker反而更重要。

第四是Toolkit安装后没重启Docker。很多人装完就跑了--gpus all,结果报错,然后卸载重装一遍,浪费不少时间。

6.2 一些你可能没注意到的细节

  1. 从Docker Hub拉镜像时,如果经常因为速率限制失败,可以考虑配置登录凭证。Docker Hub对匿名用户的拉取频率有限制,尤其是共享IP环境下更明显。

  2. daemon.json里的storage-driver不要改成devicemapper。Ubuntu默认的overlay2工作正常,真遇到磁盘性能问题,优先检查文件系统类型和挂载参数,比如ext4的discard选项、挂载时是否加了noatime。

  3. NVIDIA Container Toolkit 2.0以上版本会默认启用CDI(Container Device Interface)支持,这是新特性。但如果你用的容器编排工具(比如某些中间件)不兼容CDI规范,需要在/etc/nvidia-container-runtime/config.toml里禁用CDI,否则可能会有诡异的GPU设备识别问题。

  4. 当宿主机驱动版本升级后,容器内体验不一定完全一致。比如驱动从535升级到550,容器内老版本CUDA程序依赖的某些库文件在宿主机驱动里不存在,这时你会看到类似于“libcuda.so.1 not found”的错误。但这个错误有误导性,因为libcuda.so.1往往在/usr/lib/x86_64-linux-gnu下是存在的,真正的差距可能在其它版本依赖上。排除这类问题的做法是,在容器镜像里正确绑定安装对应的libnvidia-container库,而不是依赖宿主机的动态库。

6.3 日常工作流程参考

我现在的新机器装完这套环境后,会顺手做几件事:

  1. 设置Docker开机自启:sudo systemctl enable docker。服务器场景基本都需要,否则重启后容器全部停掉。

  2. 整理一份容器环境基线:把常用的nvidia/cuda镜像tag、对应驱动要求、启动参数写在一个README里,下次直接用,不现场翻文档。

  3. 验证GPU算力:跑一个小的矩阵乘法或卷积基准,记录结果。这样后续如果发现性能下降,能对比基线快速定位问题。

  4. 定期更新Toolkit和Docker:sudo apt update && sudo apt upgrade虽然简单,但很多人忘了,结果某天拉新镜像时提示运行时版本过旧,到时再补就得多花时间。

6.4 关于系统更新的一个建议

独立显卡驱动和系统内核的更新存在联动。如果你终端提示有新的内核版本,务必先确认新内核是否兼容当前NVIDIA驱动。Ubuntu的自动安全更新有时候会自动更新内核,但不自动重新编译NVIDIA驱动模块。于是你会在重启后看到驱动起不来的提示。而在容器侧的表现则是:容器启动时找不到GPU。这类问题建议通过安装dkms来解决,让内核更新后驱动模块自动重建:

sudo apt install dkms

NVIDIA驱动如果安装时开启了dkms支持,后续内核升级时模块会自动编译,省掉很多麻烦。

7. 最后补充一点个人体会

我在实际使用中发现,Docker给GPU环境带来了一种“环境环保”的体验。以前配一套深度学习环境,装完就害怕换机器或者重装系统,那感觉和搬家时发现地下室堆满了家具一模一样。现在所有环境细节都固化在镜像和启动配置里,重来一遍最多半小时。特别是需要同时维护多个项目时,每个项目像是一个独立的小工作台,互不干扰,随时可以推倒重来,样本经过清理后再构建全新的环境也不会费多少力气。

对于确定长期要用GPU做开发或者部署的朋友,我还有一个小建议:不要停留在“能跑”这个层面,花点时间把自动构建镜像的流程搭建起来。先把基础CUDA镜像固定一个版本,然后把自己的常用依赖分层打进去,推到私有仓库。这样以后任何机器上只要docker pull就能拿到完整环境,不再依赖那个“当时一定是你配置不对”的老环境。工具链不应该成为你交付项目的瓶颈,这套安装指南就是帮你把这个瓶颈移除的第一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询