退出后的动作
重启看退出原因
重启策略(Restart policy)决定容器退出后,Docker 是否重新启动它。默认值 no 不自动重启。退出码 0 通常表示正常完成,非零表示失败。[1]
这里的“重启”是再次启动同一个容器里的主进程,容器 ID 保持不变;它不会更新镜像,也不会自动修复应用。需要回顾对象关系时,读镜像、容器与仓库。
一个定时导出程序已经成功完成,退出码是 0。若设为 always,会安静地保持停止吗?先写下你的判断。
不会。always 也会重启正常退出的容器,任务可能重复执行。一次性任务通常保留 no;只想在失败后重试时,考虑有次数上限的 on-failure:N。
| 策略 | 自行退出 0 | 自行退出非零 | docker stop 后 立即自动启动 | Docker 守护进程重启后 |
|---|---|---|---|---|
| no默认 | 保持停止 | 保持停止 | 否 | 不自动启动 |
| on-failure[:N]如 on-failure:2 | 保持停止 | 重启;可设最多 N 次 | 否 | 不因守护进程重启而启动 |
| always | 重启 | 重启 | 否 | 自动启动,包括之前被手动停止的容器 |
| unless-stopped | 重启 | 重启 | 否 | 恢复运行中的容器;已停止的容器继续停止 |
人工停止的意图也参与决策。docker stop 后,always 不会立刻把容器拉起;等 Docker 守护进程重启,它才会回来。unless-stopped 则保留停止状态。[1]
深入:启动成功、退避等待与人工停止
官方说明把“成功启动”定义为已运行至少 10 秒并进入监控。下面的重试实验每次先运行 12 秒,再退出,以避开刚创建就失败的边界。[1]
重启之间有逐渐增加的等待时间,称为退避(Backoff),不是持续立即启动。CLI 文档说明运行至少 10 秒后会重置等待时间。不要把 10 秒理解成每次固定等待,也不要靠“观察一瞬间”判定策略失效。[3]
手动 docker start 可以恢复已停止容器,之后继续按策略处理退出。前台 docker run 的终端连接在容器退出时会断开,后台重启不代表终端也重新连接。[1]
你把维护中的服务 docker stop 了。希望 Docker 守护进程重启后它仍保持停止,该用哪种策略?
always 只在当前守护进程会话中尊重这次停止;守护进程重启后会再次启动它。
docker stop 是人工操作,不能当作普通故障重试。on-failure 也不会因守护进程重启自动恢复服务。
unless-stopped 适合“平时退出后重启,但我停下就保持停下”的意图。若要连人工停止也在守护进程重启后恢复,才考虑 always。
内存的硬边界
内存不足可能被杀
--memory=64m 设置容器的内存硬上限。默认没有这个限制,容器可能与宿主机其他进程争用内存。这里的 64m 是 64 MiB,即 64 × 1024 × 1024 字节。[2][3]
内存不足(Out Of Memory,OOM)时,Linux 内核可能杀死容器中的进程以回收内存。如果被杀的是容器主进程,容器随之退出。应用自己的内存分配失败,也可能先让程序报错退出。
- 设定 64 MiB 上限,已有用量为示意的 48 MiB。
- 程序再申请并实际写入 32 MiB。先猜:内核一定会无限借出宿主机内存吗?
- 不会。达到硬限制后先尝试回收;仍无法满足时可能触发 OOM。
- 本例中被杀的是主进程,所以容器退出。多进程容器也可能只有子进程被杀。
- 在保留的容器上检查 State.OOMKilled。先猜:被重启后,原来的内存需求会自动变小吗?
- 不会。若需求没变,重启后仍可能再次 OOM。先修内存需求或调整经过评估的限制。
137 是线索,OOMKilled 是证据
主进程收到强制终止信号 SIGKILL(信号 9)时,常见退出码为 137。手动强杀也会出现这个码,所以只看 Exited (137) 不能断定是 OOM。[4]
docker inspect 的 .State.OOMKilled 表示自容器上次启动以来,Docker 是否记录到内存不足导致的杀进程;配合 .State.ExitCode、状态与日志判断。[5]
为了保留这次退出的证据,下面的 OOM 实验禁用自动重启,也不加 --rm。容器被移除后无法再 inspect;自动重启也会更新运行状态,不能把它当作完整的历史记录。
深入:为什么实验还设置 memory-swap?
交换空间(Swap)把部分内存页放到磁盘。--memory-swap 是内存与交换空间的合计上限,并非单独的 swap 容量。
本页让 --memory=64m 与 --memory-swap=64m 相等,禁止该容器使用 swap,减少实验差异。仅设 memory 时,若宿主机支持 swap,默认还允许等量 swap。宿主机配置会影响 OOM 发生的时机。[2]
不要为实验关闭 OOM 保护。容器中的解释器、库与其他进程也计入用量,64 MiB 并不全留给你申请的数组。
CPU 的时间预算
CPU 超额会等
--cpus=0.5 表示容器最多使用相当于 0.5 个 CPU 的计算时间。它不是固定使用编号为 0.5 的核心,也不是整台机器的 50%。CPU 在这里按逻辑 CPU 计数。[2]
Linux 用周期内的配额(Quota)限制总 CPU 时间。用完配额后会限流(Throttling),等待配额补充;单纯超过 CPU 配额不会像 OOM 那样杀死进程。
先算再调图:假设周期为 100 ms,--cpus=0.5 在每个周期允许的总 CPU 时间上限是多少?
ms 的 CPU 时间。
0.5 × 100 ms = 50 ms。这是容器内所有进程与线程加起来的预算。多核上可以并行花掉预算,但不能因此增加总配额。
总预算 50 ms / 周期,最多占这台双 CPU 主机总容量的 25%;用完后等待补充。
这是上限,并不预留算力或保证速度。主机忙、程序等待磁盘或网络、线程不足时,实际使用可能更少。多线程进程可以在不同 CPU 上运行;若要限制在哪些 CPU 上运行,另有 --cpuset-cpus。[2]
深入:100 ms 从哪里来?
Docker 文档给出的默认 CPU 周期为 100000 微秒,也就是 100 ms。--cpus=0.5 等价于周期 100000 微秒、配额 50000 微秒。通常直接使用 cpus,不必手动设置两个底层参数。[2]
程序短时间内可能并行消耗配额,所以这幅“总量条”不表示必须先运行连续 50 ms 再停连续 50 ms。CPU 限流会增加延迟,应用仍可能因自己的超时逻辑报错。
在你自己的机器上验证
保留现场再检查
本页编写环境没有 Docker。下面是可执行的实验命令与预期检查项,不是实测终端记录,待维护者补充输出。请在装有 Docker Engine 的宿主机 Bash / POSIX shell 中执行。
使用 Linux 容器、支持内存与 CPU 控制的内核;Docker Desktop 使用其 Linux 虚拟机。先执行 docker info 检查资源限制相关警告。Rootless 模式还需要 cgroup v2 与 systemd 支持。[7]
会拉取缺失的镜像并写入 Docker 本地缓存;创建少量有资源上限的容器。不发布端口、不挂载宿主目录;无需修改宿主网络、iptables 或使用 --privileged。下面的名称须未被占用。
实验 A:先确认能正常运行
这一步给短任务设置内存和 CPU 上限,执行完自动删除容器。镜像标签是选择的实验依赖,不是本环境实测的版本信息。
预期检查(未实测):程序应打印命令中指定的文本 limits-ok 并成功退出。首次拉取可能先出现下载信息。若资源控制不受支持或镜像拉取失败,先处理环境问题再继续。
实验 B:故障最多重试两次
运行后先等待约 45 秒。sleep 12 让每轮进程运行超过文档中的启动成功门槛,再用 exit 1 主动模拟失败;on-failure:2 最多自动重启两次。[3]
预期检查(未实测):全部尝试结束后,状态应为 exited、退出码 1、重启次数 2;日志中程序应打印三次 starting。首次运行不计入重启次数。若仍处于运行或重启中,稍后再 inspect,不把等待时间当精确定时保证。
实验 C:只让受限容器申请过量内存
下面的 Python 主进程尝试创建并触及一个 128 MiB 的字节数组,而整个容器只允许 64 MiB,且不允许 swap。--restart=no 保留故障状态;故意不加 --rm,便于 inspect。
预期检查(未实测):若内核 OOM 杀死该主进程,应看到停止状态 exited、OOMKilled 为 true,退出码通常为 137。日志可能为空,因为进程在打印长度前被杀;不要依赖必定出现某句“内存不足”提示。
同时核对配置:memory 与 swap-total 都应对应 64 MiB 的字节数;nano-cpus 以十亿分之一个 CPU 为单位,0.5 应对应 500000000。这些是配置值,不能当作实际用量。[5]
若得到程序自己的 MemoryError(内存分配异常)或 OOMKilled=false,记录真实结果并检查环境、退出码与日志,不把它改写成“已观察到内核 OOM”。
如果把实验 C 改成 --rm,随后还能用同一个名字 inspect 它的 OOMKilled 吗?为什么?
不能。--rm 在退出后删除容器,连同可供 inspect 的容器状态一起清理。排障时先保留现场;重启策略也不能与自动删除同时使用。[3]
实验 D:观察 CPU 忙时的限流
启动一个主动计算 20 秒的进程,趁它运行时在同一宿主机执行 stats。程序使用墙钟计时,到时主动退出;无需等待 CPU 配额把它杀掉。
预期检查(未实测):运行中 CPU 用量受限,最终应正常退出,退出码 0 且 OOMKilled=false。Linux 上 stats 的 CPU 100% 约代表一个逻辑 CPU 的用量;持续忙时 0.5 配额的读数通常接近 50%,不是整台双 CPU 主机的 50%。采样、负载和调度会带来波动,不能要求恰好 50.00%。[6]
清理实验容器
只在这些名字确实属于本次实验时执行。镜像仍会保留在本地缓存;本页不要求重启 Docker 守护进程来验证策略,以免中断你机器上其他容器。
把命令连到执行者
限制由内核执行
控制组(Control groups,cgroups)是 Linux 内核按进程组统计并限制资源的机制。Docker 把 memory、cpus 等参数转成容器控制组的配置。[2][6]
一个容器反复 OOM 后被 always 重启。为什么只改重启次数,不能解决它的内存问题?用两句话说明两个执行者的分工。
内核仍按同一个 cgroup 内存上限限制每次运行,应用的内存需求也没有消失。Docker 只在退出后重新启动进程;限制重试可以停止循环,修复仍要检查应用需求与资源配置。
- 提到 cgroups 的资源限制不会因重启而放宽。
- 提到重启只恢复运行,未修改应用的资源需求。
深入:到模块 7 再看哪些细节?
模块 7 会展开 cgroup v1/v2、控制器与内核文件。这里先记住:资源限制由控制组执行,重启由守护进程决定。Docker Desktop 的 Linux 容器受其虚拟机可用资源约束,不会凭空增加物理主机的算力。
把证据串起来
先诊断再重启
- 正常结束,还是失败退出?按任务意图区分 no、on-failure、always 与 unless-stopped。
- 退出码 137,还要查什么?检查 OOMKilled 和日志;137 也可能来自人工强杀。
- CPU 配额耗尽会怎样?通常等待补充配额;0.5 是半个逻辑 CPU 的时间上限。
- 重启后仍会 OOM 吗?会。先评估内存需求与上限,cgroups 不会因重启自动放宽。
一个单主进程容器设置 memory=64m、cpus=0.5、restart=on-failure:2。退出后 inspect 显示 OOMKilled=true、ExitCode=137,重启后又出现相同现象。最合理的下一步是什么?
CPU 配额约束计算时间,OOM 是内存问题。单纯增大 cpus 不会放宽 memory 上限。
无限重启仍会遇到相同内存限制,并且可能重复失败。先定位资源需求与配置。
OOMKilled=true 提供了 OOM 的证据,重启后同样失败说明恢复动作没有消除原因。先查看日志并测量内存需求;修复内存异常或在宿主机容量允许时调整上限,再验证行为。
来源
核对日期:2026-11-03。以下为 Docker 官方文档;本页示意图、配额计算与实验检查项不代表本机测量记录。
- Start containers automatically:四种策略、人工停止、启动成功门槛与前台连接。
- Resource constraints:内存硬上限、swap、OOM、CPU 配额与 cgroups。
- docker container run:restart 重试次数、退避、RestartCount、rm 的互斥与参数单位。
- docker container ls · Filter by exit signal:137 与 SIGKILL,不能据此单独判定 OOM。
- Docker Engine API v1.52:ContainerInspect 的 State、OOMKilled、ExitCode、HostConfig 与 NanoCpus 字段。
- Runtime metrics 与 Engine API · ContainerStats:cgroup 统计与 Linux CPU 百分比计算。
- Rootless mode · Limiting resources:资源限制需要 cgroup v2 与 systemd。