Docker 容器管理:从创建到销毁的完整生命周期
容器的生命周期管理,是 Docker 日常操作里最基础也最频繁的部分。说实话,我刚接触 Docker 那阵子,光记命令就费了不少劲,后来发现其实就那么几个操作——创建、启动、停止、删除、进入、看日志、限制资源。搞懂了这些,无论多复杂的批处理流程都能控制住。
核心概念:容器本质是一个运行中的进程隔离环境。你创建它时,它还是一个“待机”状态;只有 start 后进程才会跑起来。这一点我见过不少新手搞混,以为 create 就等于启动了。
容器的创建与启动:先“制造”再“点火”
创建容器,我通常用 docker create 配合镜像名和启动命令。为什么不用 docker run 一步到位?因为有时候你需要先准备好容器环境,再择机启动。举个例子:
docker create --name qc-pipeline \
-v /data/inputs:/workspace/inputs \
-v /data/outputs:/workspace/outputs \
ubuntu:22.04 /bin/bash -c "python3 run_qc.py"
这条命令创建了一个名为 qc-pipeline 的容器,挂载了两个数据卷,但此时容器并未运行。启动它也很简单:
docker start qc-pipeline
我的习惯:在批次质控流程里,我倾向于先 create 再 start,这样可以在启动前检查挂载路径是否正确,避免一跑起来就报错。
如果需求简单,直接用 docker run 一步到位也行。它会自动 pull 镜像、创建容器、启动容器,一条命令搞定。比如:
docker run --name batch-qc -d --rm \
-v $(pwd)/data:/data \
qc-image:latest
注意这里的 --rm 参数——容器停止后自动删除。我之前在自动化流程里忘加这个,跑完一批测试后磁盘被几百个停止状态的容器占满了。嗯,踩过坑你就记住了。
停止与删除:不要留垃圾
容器停止可以用 docker stop,它会发 SIGTERM 信号给主进程,等待超时(默认10秒)后强制 SIGKILL。如果你确认容器内进程已经处理完,想立刻停掉:
docker kill qc-pipeline
删除容器我建议分两步:先停,再删。当然也可以用 docker rm -f 强制删除运行中的容器。但这样做可能会有数据丢失的风险。我曾经有一次在删除前没等质控结果写盘,结果那批样本的中间结果全丢了……从那以后我再也不敢强删正在写数据的容器。
| 操作 | 命令 | 说明 |
|---|---|---|
| 优雅停止 | docker stop CONTAINER |
发 SIGTERM,等超时后强制 |
| 强制停止 | docker kill CONTAINER |
直接发 SIGKILL |
| 删除容器 | docker rm CONTAINER |
只能删已停止的 |
| 强制删除 | docker rm -f CONTAINER |
删运行中的容器(小心) |
进入容器:调试和配置的入口
当你需要查看容器内部情况、修改配置或手动执行命令时,docker exec 是标配。比如以交互式终端进入正在运行的容器:
docker exec -it qc-pipeline /bin/bash
如果容器里没有 bash(比如 alpine 镜像),可以用 sh:
docker exec -it qc-pipeline sh
我个人经常用这个命令排查问题——比如批次质控脚本没输出日志,我就进去检查 Python 包有没有装全、路径对不对。注意 -it 参数:-i 保持标准输入打开,-t 分配伪终端。少了它,很多交互式命令没法用。
注意:exec 进入的容器必须处于运行状态。如果容器已经停止,你需要先启动它,或者用 docker commit 将其保存为新镜像再启动排查。
日志查看:容器内部的“黑匣子”
看我日志,就像看飞行数据记录仪一样,能还原容器里发生了什么。docker logs 默认返回标准输出和标准错误。实时跟踪最新日志:
docker logs -f qc-pipeline
加上 --tail 只看最近几行:
docker logs --tail 100 qc-pipeline
时间戳有时候也很关键——特别是要跟外部调度系统对齐时:
docker logs -t qc-pipeline
我在做批次回溯时,会把每批容器的日志按文件名保存下来:
docker logs batch_20240301 > /logs/20240301/batch.log 2>&1
这样万一后面发现某个样本的质控异常,翻日志就能定位到问题环节。
资源限制:别让一个容器拖垮整个节点
在质控流程里,经常有多批次容器同时跑。不管制资源的话,一个写 Python 死循环的脚本就能把 CPU 吃满,其他容器跟着遭殃。我建议在创建容器时就限制好内存和 CPU:
docker run --name test-qc \
--memory=4g \
--memory-swap=4g \
--cpus=2 \
--cpuset-cpus=0,1 \
qc-image:latest
参数说明:
--memory:最大可用内存,单位 b/k/m/g。超出会触发 OOM kill。--memory-swap:总内存+交换分区大小。设置为与 memory 相同则禁用交换。--cpus:允许使用的 CPU 核数,可以是小数(如 1.5)。--cpuset-cpus:绑定到特定物理 CPU 核心(如 0,1)。
实战技巧:对于 IO 密集型的质控步骤,还可以限制磁盘 IO:--device-read-bps /dev/sda:50mb --device-write-bps /dev/sda:50mb。我有一回就是忘了限制写带宽,结果四个容器同时写盘,把 NAS 打挂了……
查看容器当前资源使用情况,用 docker stats:
docker stats qc-pipeline
它会实时显示 CPU%、内存、网络 IO、磁盘 IO。我常常开着它对多个容器做对比,判断哪个流程更吃资源。
实践要点:我这几年总结的几个习惯
- 给容器起有意义的名称——不要用自动生成的随机名,用
--name带上批次号或流程标识。 - 批量清理已停止的容器:
docker container prune一键清除所有停止状态的容器。 - 日志统一管理:Docker 默认日志驱动是 json-file,日志文件会越积越大。我习惯加
--log-opt max-size=10m --log-opt max-file=3限制日志轮转。 - 资源限制一定要设——哪怕你觉得自己写代码很稳,也要防一手数据异常导致的全流程雪崩。
我个人最常用的一条组合拳:docker run -d --restart=on-failure:3 --memory=2g --cpus=1 name:tag。这样容器崩溃后最多自动重启 3 次,既能容错又不会无限重试把资源耗尽。
好了,容器的生命周期管理就这些。你只要把 create、start、stop、rm、exec、logs 和资源限制这几个操作练熟,绝大部分批次质控流程的容器化部署都不会有障碍。下次遇到容器跑着跑着没反应的情况,先别慌,docker exec -it 进去看一眼,再 docker logs 查下输出,多半能找到线索。