docker stop 本身不保存中间状态,仅发送 SIGTERM 信号通知容器优雅退出;能否保存检查点、ETL 进度或日志,取决于代码是否捕获该信号、主进程是否为 PID 1、以及是否通过 volume 挂载实现持久化。
直接说结论:docker stop 本身不保存中间状态,它只是发送 sigterm 信号让容器内主进程“优雅退出”。能否保存训练检查点、etl 进度或日志,取决于你是否在代码里响应了这个信号,并配合挂载卷做了持久化。关键不在 stop 命令,而在整个退出链路的设计。
理解 stop 的真实行为
执行 docker stop 时,Docker 会向容器 PID 1 进程发送 SIGTERM(默认等待 10 秒),若进程未退出,再发 SIGKILL 强制终止。这意味着:
- 容器不会自动保存任何数据——它只管“通知你该停了”
- 你的 Python/Java/Scala 主程序必须捕获 SIGTERM 并触发保存逻辑
- 如果主进程没做信号处理,或者用了 nohup & 或 exec 启动方式绕过信号转发,stop 就等同于断电
三步实现中间状态精准保存
以 PyTorch 训练流水线为例,需同步完成以下三件事:
-
代码层:注册信号处理器
在训练主脚本开头加入信号监听,收到 SIGTERM 时调用 save_checkpoint() 并 exit(0):
def handle_exit(signum, frame):
save_checkpoint(model, optimizer, epoch)
print("Checkpoint saved. Exiting gracefully.")
exit(0)
signal.signal(signal.SIGTERM, handle_exit)
-
镜像层:确保信号可传递
避免使用 shell 形式 CMD(如 CMD ["sh", "-c", "python train.py"]),改用 exec 形式:
CMD ["python", "train.py"] —— 这样 Python 进程才是 PID 1,能直接接收信号 -
运行层:挂载持久化卷
必须通过 -v 挂载模型目录和日志目录到宿主机,不能依赖容器内临时路径:
docker run -v /data/train:/app/checkpoints -v /data/logs:/app/logs your-ml-image
配套的容错与验证策略
仅靠信号处理还不够,还需预防意外中断:
- 训练循环中定期保存(如每 5 个 epoch),避免单次 SIGTERM 前大量进度丢失
- 启动时自动加载最新 checkpoint(判断 /app/checkpoints/latest.pth 是否存在)
- 用 docker inspect 查看容器 ExitCode:0 表示正常退出(大概率已保存),137 表示被 SIGKILL 终止(可能丢失)
- 对 ETL 类任务,可在写入目标前先落盘临时文件 + rename 原子操作,防止部分写入
不要踩的典型坑
这些做法会让 stop 完全失效:
- 用 docker kill 替代 docker stop —— 直接触发 SIGKILL,跳过所有优雅退出逻辑
- 把 checkpoint 写到 /tmp 或未挂载的容器路径下 —— 容器一停,数据就消失
- 在 Jupyter Notebook 或交互式 shell 中运行训练 —— PID 1 是 shell,不是训练进程,信号根本传不到
- Conda 环境装在容器可写层,又没导出 environment.yml —— 下次重建容器,连依赖都不同











