第一次在Ubuntu上让Docker容器里跑出nvidia-smi时,我盯着屏幕上的Driver Version: 545.23.08愣了几秒。当时前前后后折腾了大半天,最后发现缺的其实就是一个运行库和一个配置项。这篇东西就把我在ubuntu上配置docker使用GPU资源时踩过的坑、验证过的方法、以及为什么必须这么做讲清楚。
会涉及NVIDIA显卡、Docker 19.03+、NVIDIA Container Toolkit这条主流链路,也会单独讲双显卡笔记本(比如Intel UHD核显 + NVIDIA RTX 4060 Laptop独显这种组合)的注意点。无论你是跑pytorch训练、gpu计算任务,还是想在k8s里调度gpu,这套流程都适用。
1. 容器为什么天生摸不到GPU:先从底层机制说起
很多人第一次配置时都会困惑:我在宿主机上明明能看到显卡,nvidia-smi输出正常,为什么容器里执行同一命令就报"couldn't find libcuda.so"或者一片空白?这不是权限问题,而是Docker的隔离机制和GPU驱动的软件栈天然不兼容。
1.1 程序访问GPU要经过"两道门"
一台装好NVIDIA驱动的机器,GPU能工作靠的是两层东西:
- 内核层:
nvidia.ko、nvidia_uvm.ko这些内核模块,负责把用户态请求翻译给GPU硬件。 - 用户态层:
libcuda.so.1、libnvidia-ml.so这些动态库,以及nvidia-smi工具本身,给普通进程提供调用入口。
进程要使用GPU,必须先找到/dev/nvidia0、/dev/nvidiactl、/dev/nvidia-uvm这些设备节点,再加载用户态库。Docker默认只隔离进程、文件系统和网络,设备节点一个都不给容器暴露。换句话说,容器里没有任何一张"通往GPU的门卡"。
1.2 NVIDIA Container Toolkit到底在做什么
新版的NVIDIA Container Toolkit(以前叫nvidia-docker2)本质上就是做"配门卡"这件事。它不是一个驱动包,而是一层辅助工具,会在容器启动时自动注入三样东西:
- 设备节点:把宿主机的
/dev/nvidia*挂进容器。 - 用户态库:把宿主机
/usr/lib/x86_64-linux-gnu/下那些NVIDIA库 bind mount 进去。 - 环境变量:设置
CUDA_VERSION、NVIDIA_VISIBLE_DEVICES等变量,让容器里的CUDA运行时能找到正确版本的库。
这也是为什么容器里不需要重新装显卡驱动,驱动永远在宿主机上。你只需要保证宿主机驱动OK,再让toolkit把这套软件栈"递"进容器。
1.3 谈一个常见的误解
有些人以为装个带CUDA的镜像就等于能用GPU了,比如直接pull一个nvidia/cuda镜像,然后docker run进去,发现nvidia-smi还是报错。原因就是上面说的:镜像里只有CUDA工具链和用户态库的副本,但没有设备节点,也没有与宿主机内核模块的对接渠道。镜像本身只是"有菜谱的厨房",还得有系统帮忙递菜。想要递菜,就得到第3节的toolkit安装。
2. 动手前的三轮核对:别把问题留给容器
配置GPU容器之前,强烈建议先在宿主机把环境确认清楚。这一步很多人跳过,导致后来排查问题时根本不知道是驱动坏了、Docker版本太老,还是toolkit没生效。
2.1 第一轮:硬件和驱动状态
lspci | grep -i nvidia这条命令能看到物理显卡是否被系统识别。如果是双显卡笔记本,通常会看到两行输出,比如Intel Corporation UHD Graphics和NVIDIA Corporation GA107M [GeForce RTX 4060 Laptop GPU]。
接着看驱动加载情况:
lsmod | grep nvidia nvidia-sminvidia-smi能正常打印表格,说明内核模块和用户态库都活着。这里有个容易被忽略的细节:哪怕宿主机上显示驱动正常,也要记下驱动版本号。比如Driver Version: 545.23.08,后面选镜像时会参考它。NVIDIA Container Toolkit基本不挑驱动版本,但CUDA镜像最好别超过驱动支持的CUDA版本上限。
2.2 第二轮:Docker的runtime情况
docker version docker info | grep -i runtimeDocker 19.03以上才支持--gpus参数。如果输出里能看到Runtimes: nvidia,说明之前可能已经装过toolkit;如果只有runc,那还没配置好。
另外,Ubuntu上安装Docker时最常见的坑是:用apt install docker.io装完后,默认没有docker官方仓库的较新版本。docker.io版本一般够用,但如果你要跑最新版toolkit,建议用Docker官方apt源装社区版。命令行执行docker run --gpus all会明确提示"could not select device driver",这基本能定位到runtime问题。
2.3 第三轮:镜像选型思路
镜像不是越大越好,也不是越全越好。常见的几类镜像差别很实在:
| 镜像Tag | 包含内容 | 适用场景 |
|---|---|---|
nvidia/cuda:12.1.0-base-ubuntu22.04 | 最小运行时,只有用户态库 | 临时验证、跑简单nvidia-smi |
nvidia/cuda:12.1.0-runtime-ubuntu22.04 | 带CUDA runtime入口 | 普通计算任务 |
nvidia/cuda:12.1.0-devel-ubuntu22.04 | 再加上nvcc编译工具链 | 需要编译算子的场景 |
pytorch/pytorch:2.3.1-cuda12.1-cudnn8-runtime | CUDA + PyTorch + cuDNN | 深度学习训练推理 |
如果只做验证,拿nvidia/cuda:12.1.0-base-ubuntu22.04就够,镜像小,拉取快,干净。
3. 安装NVIDIA Container Toolkit的关键步骤与验证
这部分是核心。以Ubuntu 22.04为例,走的是官方apt源安装路径。这里不推荐老的nvidia-docker2了,新项目统一用nvidia-container-toolkit,配置方式也从手动改daemon.json变成了敲一条命令。
3.1 添加apt源并安装
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit这段命令里的sed很多人看不懂,其实是把官方repo模板里的路径加上签名key路径,避免apt源验证报错。安装完成后,可执行文件在/usr/bin/nvidia-ctk和/usr/bin/nvidia-container-runtime-hook下面。
3.2 让Docker加载NVIDIA runtime
这一步是最容易遗漏的。toolkit装上只是起点,必须把它注册进Docker的运行时列表:
sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart dockernvidia-ctk runtime configure做的事其实是修改/etc/docker/daemon.json,往里写一段runtimes配置。我见过有人手动改这个文件,但格式稍微写错就可能导致Docker起不来,所以能用命令就用命令。重启完再验证一下:
docker info | grep -i runtime输出里如果出现nvidia,说明注册成功。
注意:如果你用的是Docker Desktop(Linux版),不能走
systemctl restart docker,需要在Docker Desktop设置里手动重启引擎,或者它的CLI方式会有点差异。最稳妥的还是直接装纯CLI模式的Docker Engine。
3.3 验证方法:从nvidia-smi到PyTorch
装完先跑最直接的检查:
docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi看到容器里的nvidia-smi输出,并且Driver版本和宿主机一致,就成功了大半。
接着验证CUDA能真正干活:
docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 \ bash -c "echo '#include <cuda_runtime.h>' > /tmp/test.cu && nvcc -arch=sm_86 /tmp/test.cu -o /tmp/test"不过base镜像没有nvcc,这一步要用devel镜像。平时跑PyTorch更直接:
docker run --rm --gpus all pytorch/pytorch:2.3.1-cuda12.1-cudnn8-runtime \ python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"能输出True NVIDIA GeForce RTX 4060 Laptop GPU,整个链路就算闭环了。
4. 双显卡笔记本的特殊战场:Intel核显与RTX 4060独显并存
热搜词里专门有"显卡有两个intel uhd graphics 和nvidia geforce rtx 4060 laptop gpu",这几乎是所有游戏本用户都会遇到的情况。配置Docker GPU资源时,这类机器有一些独有的坑,值得单独拎出来说。
4.1 先分清是NVIDIA驱动压根没装上,还是没被选中
很多人在这种机器上跑nvidia-smi,报的错不是"no CUDA driver",而是"Command 'nvidia-smi' not found"。这种情况先别急着配Docker,问题根本在驱动层。
先看系统里NVIDIA设备是否被识别:
lspci | grep -i nvidia ubuntu-drivers devices如果能看到设备信息但没驱动,大概率是nouveau开源驱动或者完全空白。建议先禁用nouveau(在/etc/modprobe.d/blacklist-nvidia-nouveau.conf里写两行),然后通过sudo apt install nvidia-driver-545这类命令装官方驱动。装完重启,nvidia-smi应该能出来。
还有一种常见状态是:驱动装了,但系统当前走的是核显渲染,nvidia-smi也正常输出,这种其实不影响Docker复用GPU,因为nvidia-smi和NVIDIA Container Toolkit依赖的都是独立显卡的设备节点和库,跟系统当前用什么卡做图像输出无关。
4.2 验证NVIDIA模块和设备节点
lsmod | grep nvidia ls /dev/nvidia*正常情况下能看到nvidia.ko、nvidia_uvm.ko这些模块,设备节点也应该存在/dev/nvidia0、nvidiactl、nvidia-uvm。设备节点缺失时,toolkit也挂载不了。如果两者都没有,需要从驱动安装阶段重新排查。
再说一句:双显卡机器上跑容器,容器里默认会看到整台机器所有NVIDIA GPU。这是正常的。如果你只想用其中一张(或者显存规格不同,想按卡分配),用环境变量控制:
docker run --rm --gpus '"device=0"' nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi -L宿主机的nvidia-smi -L里,GPU编号是从0开始排列的,用这个方式选择最直观。
4.3 一个和WSL2/虚拟机相关的额外提醒
热词里也有vmware虚拟机安装ubuntu。虚拟机里配Docker GPU,情况会更复杂:如果是VMware,默认不把显卡的PCIe设备直通给虚拟机,因此vGPU能力有限,--gpus基本用不了。真要在虚拟机里玩GPU容器,要么做PCIe直通,要么直接本机跑。这个先明确,免得在虚拟机里白折腾。
5. 线上最常翻车的五个症状及完整排查链路
配置完不等于一直好用。我见过太多人,第一次配置完跑了三天,重启机器后Docker里GPU又消失了。这里把最常遇到的几个症状和完整排查思路按链路理一遍。
5.1 症状一:docker run --gpus all直接报"could not select device driver"
这是最经典、也最好定位的问题。错误信息里带着capabilities: [[gpu]],几乎可以断定toolkit没生效。排查链路:
docker info | grep -i runtime- 如果
Runtimes里没有nvidia:说明nvidia-ctk runtime configure没跑,或者Docker没重启。 - 如果
systeamctl status docker不正常:检查/etc/docker/daemon.json,看是不是手动改坏过。 - 如果以上都正常但还报错:确认toolkit版本和Docker版本是否太旧,建议升级Docker到20.10以上、toolkit到1.14以上。
5.2 症状二:容器启动了,但nvidia-smi内部报错
这种情况一般是工具链版本错位。比如宿主机驱动是470.xx,镜像却选了CUDA 12.1——CUDA 12.x对驱动有最低版本要求。先在宿主机看nvidia-smi的Driver Version,然后镜像选择CUDA版本时遵守一个简单规则:镜像要求的CUDA版本,对应的最低驱动版本要小于等于宿主机的驱动版本。查对应关系直接去NVIDIA官方CUDA compatibility文档。
再一个原因可能是NVIDIA_VISIBLE_DEVICES被容器内的应用覆写成了空值。可以在docker run时显式传环境变量:
docker run --rm --gpus all -e NVIDIA_VISIBLE_DEVICES=all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi5.3 症状三:能跑nvidia-smi,但容器里跑PyTorch报"CUDA not available"
这里有两个方向:
- PyTorch安装时用的是CPU版,没装成CUDA版。验证方法是容器里执行
python -c "import torch; print(torch.version.cuda)",如果是None,说明镜像里的PyTorch是纯CPU编译的,直接换官方pytorch/pytorch的cuda标签镜像就行。 - 用了
tensorflow/tensorflow镜像,TF和新版CUDA的兼容性比PyTorch更敏感,建议直接用NVIDIA官方TF容器。
5.4 症状四:重启后Docker里的GPU没了
toolkit配置是持久化的,daemon.json不会自己消失。重启后出问题先查三个东西:
systemctl status docker docker info | grep -i runtime docker system info | grep -i nvidia大概率是某个内核模块在重启后没加载成功。lsmod | grep nvidia如果为空,说明nvidia模块启动的时候崩了,需要看dmesg | grep -i nvidia,常见原因是内核升级后驱动模块没跟着重编译,最好重装一遍和当前内核匹配的驱动。
5.5 症状五:多个容器并发访问GPU时报"Cannot allocate memory"或segfault
这不是Docker的问题,是显存和UVMM内存分配的问题。容器共享宿主机的GPU显存,几个容器同时跑大模型就会把显存撑爆。排查时用nvidia-smi看宿主机侧显存占用,配合docker stats看容器资源。真要限制单容器显存用量,可以用CUDA环境变量CUDA_VISIBLE_DEVICES给不同容器分配不同卡,或者直接用后面第6节说的--gpus '"device=0"'语法。
6. 单机验证完之后的编排扩展:Compose与Kubernetes的GPU配置
单机跑通了,很多人下一个需求就是上编排。这里给两个直接从实际项目里能用的模板。
6.1 Docker Compose里声明GPU资源
新版Compose已经支持声明式GPU配置,比命令行更清晰:
services: pytorch: image: pytorch/pytorch:2.3.1-cuda12.1-cudnn8-runtime deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] command: python -c "import torch; print(torch.cuda.is_available())"执行:
docker compose up这个写法不需要在Compose文件里硬编码runtime,只要宿主机toolkit配置好了,Compose会交给Docker引擎调度。这里有个容易混淆的点:老版本Compose写runtime: nvidia的写法已经废弃,新版本只会看reservations.devices。
6.2 Kubernetes调度GPU
k8s调GPU跟单机docker完全是另一套玩法。要点是:
- 每个GPU节点要安装NVIDIA驱动和toolkit。
- 部署NVIDIA官方提供的k8s-device-plugin作为DaemonSet,它会自动把节点上的GPU资源注册成
nvidia.com/gpu这种可调度资源。 - Pod里显式声明
resources.limits:
resources: limits: nvidia.com/gpu: 1Device plugin会把Pod调度到有GPU的节点上,并自动注入环境变量和设备节点。这一步做完,Pod里就能直接用nvidia-smi了。
踩过的坑是:如果节点上有GPU,但Pod一直Pending,先看kubectl describe node里是否出现nvidia.com/gpu: 1这个字段。没出现就是device plugin的问题,检查DaemonSet日志;出现了但调度不上去,大概率是节点标签或者资源规格写错。
个人建议:单机阶段先把apt源、toolkit、驱动版本这三件事固定住,别频繁升级。特别是nvidia-container-toolkit和Docker,升级Docker大版本后记得重跑一次nvidia-ctk runtime configure --runtime=docker,否则runtime注册可能失效。我自己的环境通常会在装完新机器后顺手准备一个别名,比如:
alias gpu-check='docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi'这样每次换机器、换镜像、重启服务后,一条命令就能确认整个GPU容器链路是否还活着。配置坑大部分就集中在"toolkit装了没、runtime注册了没、镜像和驱动版本匹配不匹配"这三件事上,把这三点盯住,基本不会再翻车。