Docker基础:镜像、容器、Dockerfile、常用命令
核心原理:镜像与容器的关系
在模型训练管道中,我们最常遇到的困惑是“镜像”和“容器”到底有什么区别。简单来说,镜像(Image)是一个只读的、静态的文件系统模板,它包含了运行某个应用所需的全部依赖——Python解释器、CUDA驱动、PyTorch库、训练脚本等。而容器(Container)是镜像的运行实例,它拥有自己的文件系统读写层、网络栈和进程空间。你可以把镜像想象成一张光盘,容器则是用这张光盘启动的一台虚拟机——光盘本身不会变,但虚拟机里可以写入临时文件。
这种分层设计对模型训练至关重要。当我们基于 pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime 镜像启动容器时,所有训练产生的模型权重、日志文件都写入容器的可写层。一旦容器被删除,这些数据也随之消失——除非我们使用卷(Volume)将数据持久化到宿主机。理解这一机制,是后续设计训练管道的第一步。
方法流程:构建训练镜像的Dockerfile
编写Dockerfile是Docker化的核心工作。一个典型的训练镜像Dockerfile包含以下关键指令,我们以PyTorch GPU训练环境为例:
# 1. 指定基础镜像(必须包含CUDA驱动)
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
# 2. 设置工作目录
WORKDIR /workspace
# 3. 复制依赖清单并安装(利用缓存层)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 复制训练代码
COPY train.py .
COPY models/ ./models/
# 5. 声明容器运行时监听的端口(可选)
EXPOSE 6006
# 6. 设置默认启动命令
ENTRYPOINT ["python", "train.py"]
构建命令为 docker build -t my-trainer:v1 .,其中 -t 指定镜像名称和标签。这里有一个重要的缓存策略:将依赖安装步骤放在代码复制之前。因为Docker会逐层缓存,只要 requirements.txt 未变化,后续构建就会直接复用“安装依赖”这一层,大幅缩短迭代时间。在模型训练场景中,我们经常需要频繁修改训练脚本,但依赖很少变动,这个顺序能显著提升开发效率。
案例场景:训练容器的生命周期管理
假设我们要运行一个分布式训练任务,使用 train.py 脚本,需要挂载数据集并保存模型。以下是完整的命令流程:
# 1. 构建镜像
docker build -t trainer:latest .
# 2. 查看本地镜像
docker images
# 3. 启动容器(前台运行,便于查看日志)
docker run --gpus all \
--name resnet50-job \
-v /mnt/data:/workspace/data \
-v /mnt/output:/workspace/output \
trainer:latest
# 4. 另开终端查看容器进程
docker ps
# 5. 训练完成后停止并删除容器
docker stop resnet50-job
docker rm resnet50-job
这里 --gpus all 将宿主机GPU暴露给容器,-v 实现目录挂载。训练结束后,模型权重保存在宿主机的 /mnt/output 目录中,即使容器被删除也不会丢失。如果训练中断,我们可以用 docker start resnet50-job 重新启动容器,但要注意:容器内的进程状态不会自动恢复,需要训练脚本本身支持断点续训(例如读取checkpoint)。
常用命令速查表
| 命令 | 用途 | 训练场景示例 |
|---|---|---|
docker build |
构建镜像 | docker build -t trainer:v2 . |
docker run |
启动容器 | docker run --gpus all -v /data:/data trainer:v2 |
docker ps -a |
查看所有容器 | 检查训练任务是否退出 |
docker logs |
查看容器日志 | docker logs -f resnet50-job |
docker exec |
进入运行中容器 | docker exec -it resnet50-job bash |
docker cp |
复制文件 | docker cp resnet50-job:/workspace/log.txt ./ |
docker commit |
将容器保存为新镜像 | 保存调试后的环境 |
误区与复盘:常见陷阱
误区1:在容器内使用 pip install 后不提交镜像。 容器一旦删除,所有手动安装的包都会丢失。正确做法是修改Dockerfile并重新构建,或者使用 docker commit 将当前容器保存为新镜像。
误区2:忽略 .dockerignore 文件。 如果不排除 __pycache__、.git、大型数据集等目录,docker build 会把它们全部发送到Docker守护进程,导致构建极慢且镜像臃肿。
误区3:以交互方式运行训练任务。 使用 docker run -it 启动训练脚本,一旦SSH断开,容器收到SIGHUP信号就会终止。生产环境应使用 docker run -d 后台运行,或配合 nohup 和 tmux。
复盘建议: 每次训练任务结束后,执行 docker system df 查看磁盘占用,及时清理悬空镜像(docker image prune)和已停止的容器(docker container prune)。在团队协作中,将Dockerfile纳入版本控制,并固定基础镜像的标签(不要使用 latest),确保每次构建的可复现性。