本节摘要:值班手册第一页——run 命令的完整参数体系与"主进程决定容器生死"的铁律。本节拆解 run 在引擎内部的两步动作,讲透前台与后台、重启策略与退出码,帮你从源头避开"容器秒退"与"僵尸箱"两大迷案。
上一章你把箱子装好了,本章它要上船。值班的第一课是把 run 拆开看——run 并不是一个原子动作,它等于 create 加 start:先按镜像建档(叠可写层、分配名字与网络),再起吊(启动主进程)。建档与起吊分开的场景不多,但理解这两步能让你看清容器的本质:容器是"一份档案 + 一个主进程"。
# 两步等价于一步(效果完全一致) docker create --name step-demo nginx:1.25-alpine # 第一步:建档 docker start step-demo # 第二步:起吊 # 日常一气呵成 docker run -d --name one-shot nginx:1.25-alpine
主进程决定容器生死是本节的中心定律:容器的存活状态完全跟随镜像里定义的主进程(CMD 或 ENTRYPOINT 声明的那个)。主进程退出,容器即停止——不管你多希望它"继续留着"。用两个实验把定律钉死:
# 迷案一:秒退的容器。alpine 镜像的主进程是 sh,没有终端可读,读完即退 docker run alpine # (命令瞬间返回,看起来什么都没发生) # 全体清单里能看到它:状态 Exited (0),退出码 0 表示正常干完就退 docker ps -a --latest # STATUS: Exited (0) Less than a second ago # 迷案二的正解:让主进程有事干,它就一直活着 docker run -d alpine sleep 3600 # 主进程是 sleep,睡满一小时才退 -> 容器持续运行一小时
秒退不是故障,是主进程无活可干。同理可得"僵尸箱"的诊断思路:容器显示 Up 但应用无响应,往往是主进程还活着而它的子进程(真正的应用)已经挂了——4.4 节的排查手法会用到这条。
run 是 Docker 参数最多的命令,按用途分五组记。
运行姿态组:-d 后台(detach)让主进程在后台跑,终端立即归还;不加 -d 就是前台模式,日志直打屏幕、Ctrl-C 即停(联调时方便)。身份组:--name 挂牌(可读性刚需);不加名字引擎会随机分配代号。泊位组:-p 宿主端口:容器端口 对外发布,-P 让引擎自动挑高位泊位。环境组:-e 注入环境变量,应用配置的标准通道。存档组:-v 挂卷(第 5 章专讲)。
综合示例,一只配置完整的 Web 箱子:
# 一只参数完整的箱子:后台、挂牌、泊位、时区、日志上限、失败自动重吊 docker run -d \ --name order-api \ -p 8080:8000 \ -e TZ=Asia/Shanghai \ -e DB_HOST=database.internal \ --restart unless-stopped \ --log-opt max-size=10m --log-opt max-file=3 \ myapp:0.1 # 起吊核验:状态、泊位映射、启动命令一目了然 docker ps --filter name=order-api # NAMES STATUS PORTS IMAGE # order-api Up 4 seconds 0.0.0.0:8080->8000/tcp myapp:0.1
主进程退出时留下的退出码是排障的第一现场,几个高频值值得刻在脑子里:
# 用一个故意失败的主进程制造退出码 2 docker run --rm alpine sh -c "exit 2" docker ps -a --filter ancestor=alpine --format "{{.Status}}" # Exited (2) # 0 正常退出(干完了该干的) # 1 应用内部错误(异常、配置错误,去看应用日志) # 2 误用命令行参数(启动参数拼错常见) # 125 引擎层错误(run 命令本身的问题,如端口被占) # 126 命令存在但无执行权限 # 127 找不到命令(镜像里没有这个可执行文件,或路径不对) # 137 主进程被强制杀掉——内存超限被内核终止的典型信号(4.5 节展开)
退出码 137 值得单独记住:它是 OOM(内存耗尽)事件的惯用标记,生产上见到它第一反应查内存配额,而不是重启了事。
最后补一个日常姿态的选择题:前台还是后台?前台模式(不加 -d)日志直打终端、Ctrl-C 即停,适合"起一只箱子看它骂什么"的快速验证;后台模式是日常默认,日志用 logs 拉。一个常见的尴尬是新手用前台起了数据库箱子,发现终端被占满后 Ctrl-C 把箱子停了还以为是故障——记住分工:联调用前台,值守用后台,两种姿态切换只是一个 -d 的距离。
值班员不该半夜起来手工重吊故障箱子,重启策略(--restart)就是替你值夜的机制。三档策略的语义用一张表说清:
| 策略 | 行为 | 适用 |
|---|---|---|
| no | 不自动重启(默认) | 一次性任务、试验 |
| on-failure | 仅在非零退出时重启,可限次数 | 可能失败的批处理 |
| always | 总是重启(手工 stop 的除外) | 常驻服务 |
| unless-stopped | 同 always,但手工停过就不再自动起 | 常驻服务的更温和版本 |
always 与 unless-stopped 的差异是高频考点:前者在引擎重启后连"被手工停掉的箱子"也一起拉起来,后者尊重手工停机决定。生产常驻服务的主流选择是 unless-stopped——自动恢复故障,同时不与你的人工操作打架。
# 更新已运行容器的重启策略(update 命令,不必重建容器) docker update --restart unless-stopped order-api # 验证策略生效(看 RestartPolicy 字段) docker inspect order-api --format "重启策略: {{.HostConfig.RestartPolicy.Name}}" # 输出:重启策略: unless-stopped
箱子起得来,还得停得住。下一节把停止、重启、暂停、删除四个动词的精确语义掰开。