在容器里敲下nvidia-smi,回来的却是command not found;或者宿主机上跑得好好的脚本,一进镜像就抛出no CUDA-capable device is detected——这套组合症状,圈子里被笼统地叫作Docker 找不到 N 卡。它几乎不是"某一条配置写错了"这么简单的事,而是横跨宿主机内核、驱动、容器运行时、镜像内部四层的一次"链路断点"。我在给算法团队搭环境、帮朋友救现场的过程中,被这个问题按在地上摩擦过十几次,从"以为驱动坏了"重装系统,到"其实是少挂了一个设备节点",各种离谱情况都见过。这篇东西就是把这十几回的现场记录整理成一条可复用的排查路线:它是什么、卡在哪一层、用什么命令定位、怎么一次配对不返工,适合刚上手容器化训练的新同学,也适合已经在带环境但总被追问"为什么我这边没卡"的老手。
1. 先给"找不到卡"分个类,别一上来就重装驱动
我见过太多人的第一反应是"驱动崩了,重装"。这个动作的代价是半小时到半天,而实际问题有相当比例只是容器启动参数少写了三个字符。所以在动手之前,先把"找不到 N 卡"拆成几种完全不同的长相,它们对应的处置路径几乎没有交集。
1.1 四层结构决定了故障只有四种长相
从内核往上数,一张显卡要在容器里正常工作,必须同时满足四件事:内核里有可用驱动、设备节点存在且权限正确、容器启动时被注入了这些节点和用户态库、应用侧的 CUDA 版本能被这套驱动撑住。任何一层断掉,表现出来都是"没有卡",但报错文本差异很大。
我把常见的四种长相列在下表,你可以先对照着定位自己站在哪一格:
| 现象 | 大概率断点 | 典型报错文本 |
|---|---|---|
容器内nvidia-smi命令不存在 | 镜像内没装工具 / 运行时未注入库 | command not found |
nvidia-smi存在但报加载失败 | 用户态库与内核驱动版本不一致 | Driver/library version mismatch |
nvidia-smi正常但框架说没设备 | 环境变量或框架侧可见性被限制 | no CUDA-capable device is detected |
| 容器启动直接失败 | 运行时未注册 / 参数拼写错误 | could not select device driver "" with capabilities: [[gpu]] |
最后那一行could not select device driver "" with capabilities: [[gpu]]是最经典的一条,中间那对空引号就是在告诉你:引擎根本不知道有个叫 nvidia 的运行时可以选。看到这句话,别去容器里折腾了,问题 100% 在宿主机引擎配置侧。
1.2 一条黄金排查链路:从外往里剥
我固化的思路是"从外往里剥",顺序绝不能反。理由很简单:内层依赖外层,外层没通的情况下去查内层,你只能看到一堆派生现象,很容易被误导。
顺序是这样的:宿主机nvidia-smi是否稳定 → 引擎是否注册了运行时 → 用官方最小镜像验证一次 → 才进你自己的业务镜像。这四步里任何一步没过,都不要往下走。中间用官方最小镜像这一步特别关键,它把"镜像里少装东西"这个变量彻底排掉了,剩下能出问题的就只有宿主机和引擎。
注意:不要拿自己的业务镜像当第一验证对象。业务镜像里通常还有一堆依赖、版本锁、启动脚本,问题会互相掩盖,你会花两倍时间在错误的方向上。
1.3 最容易被误判的两种情况
第一种是"宿主机nvidia-smi正常,所以我认定宿主机没问题"。这个结论在绝大多数情况下成立,但有一个例外:驱动刚升级完、内核模块还没重新加载的窗口期,nvidia-smi可能因为长时间运行的进程持有旧句柄而暂时显示正常,新起的容器却拿不到设备。表现就是"我明明刚才还能用"。这时候lsmod | grep nvidia和重启相关服务比反复重装镜像管用。
第二种是"容器里ls /dev/nvidia*一个文件都没有,但设备映射我明明写了"。这里的分歧点在于:有些人写的是--device /dev/nvidia0,只映射了编号 0 的设备节点,而nvidia-smi还需要/dev/nvidiactl和/dev/nvidia-uvm才能完成初始化。只挂一两个节点,工具能起来但会报各种奇怪的初始