2.4 镜像与依赖的"踩坑速查":把常见翻车现场变成清单 第二章前三节给了骨架设计的方法论,这一节把工程现场里最容易翻车的九个场景,整理成「症状 → 根因 → 解法」的速查表。建议你把这一节当作"出事时的第一站"——遇到诡异现象,先来这里对照,往往比盲目搜报错快十倍。 速查总表 症状 | 常见根因 | 本章对应的解法 改一行依赖,构建要重拉 8 GB | 分层顺序错,频繁变动的内容放在了稳定层之上 | 2.1 镜像分层:稳定层在下、易变层在上 装出的版本和同事不一致 | 只锁了 CUDA,没锁 Python 包版本 | 2.2 四层版本锁定 容器一关,权重和日志全没了 | 数据写进了容器可写层,没挂 volume | 2.
第二章前三节给了骨架设计的方法论,这一节把工程现场里最容易翻车的九个场景,整理成「症状 → 根因 → 解法」的速查表。建议你把这一节当作"出事时的第一站"——遇到诡异现象,先来这里对照,往往比盲目搜报错快十倍。
| 症状 | 常见根因 | 本章对应的解法 |
|---|---|---|
| 改一行依赖,构建要重拉 8 GB | 分层顺序错,频繁变动的内容放在了稳定层之上 | 2.1 镜像分层:稳定层在下、易变层在上 |
pip install 装出的版本和同事不一致 |
只锁了 CUDA,没锁 Python 包版本 | 2.2 四层版本锁定 |
| 容器一关,权重和日志全没了 | 数据写进了容器可写层,没挂 volume | 2.3 挂载与数据卷策略 |
| 映射了 8888 却连不上 Jupyter | 端口映射写错方向,或 Jupyter 没监听 0.0.0.0 | 2.3 端口映射要点 |
镜像能跑,换台机器 CUDA error |
宿主驱动版本低于镜像要求 | 1.4 疑问一 / 2.2 驱动兼容性 |
| 多容器抢同一张卡互相 OOM | 都 --gpus all,未切分设备视图 |
1.4 疑问三 / 3.2 隔离边界 |
| CI 里构建慢得像蜗牛 | 没用缓存,或基础镜像每次重新拉 | 2.1 缓存命中 + 私有镜像仓库 |
| 半年后镜像跑不起来 | 基础镜像标签被改、依赖源失效 | 2.2 不可变标签 + 依赖归档 |
| 同事拉我的镜像却缺文件 | .dockerignore 漏写,或 COPY 路径不对 |
2.1 构建上下文与忽略规则 |
Docker 的层是增量且可缓存的:只要某一层及其之前的指令没变,这层就直接复用缓存。新手常犯的错是把"最容易变"的指令(比如 COPY . . 整个代码、或最后的 pip install)放在"最稳定"的指令之前,导致下面所有层全部失效、每次重来。
正确顺序是:先固定基础镜像 → 再装系统级稳定依赖 → 再装 Python 依赖(基于锁定文件)→ 最后才 COPY 业务代码。这样你改一行业务代码,只重建最后一层;改一个 pip 包,只重建依赖层往上的部分。
可复现不是一句口号,它要求四层各锁各的:
FROM nvidia/cuda:12.1.1-runtime 锁定具体补丁版本,而非 latest;torch==2.2.1+cu121 这种带 CUDA 后缀的精确版本,而不是 torch;requirements.lock 或 poetry.lock 钉死 transitive 依赖。任何一层不锁,复现性就漏风。很多"在我机器能跑"的悲剧,根源就是只锁了第 1 层、其余放任。
容器本身是可丢弃的——你 docker rm 之后,里面可写层的一切都没了。权重、数据集、训练日志这些宝贵资产,必须活在 volume 或 bind mount 里,挂在容器之外。2.3 节给的约定是:代码可进镜像,数据必须走挂载。这样你随时能删容器、重建容器,而权重和日志毫发无损。
速查表里绝大多数问题,根子都在"骨架没设计好"。一个自查信号:如果你每周都在手动 pip install 补依赖、或每次换机都重配环境,说明骨架还不够"资产化"。回到 2.1–2.3 把分层、锁定、挂载三件事做扎实,日常烦恼会少一大半。下一章我们进入更硬核的 GPU 原理。