← 全部笔记

Docker · 3 容器运行与管理

退出后按策略重启,运行时受资源上限约束

重启策略决定进程退出后的动作;内存限制与CPU 配额约束每一次运行。重新启动可以恢复运行,却不会让资源上限自动放宽。

约 15 分钟 · 截至 2026-11-03,Docker Engine 29.x · 适用 Linux 容器;实验未实测

--memory=64m --cpus=0.5 --restart

资源限制由内核执行,退出后重启由 Docker 决策 运行时 · Linux 内核执行限制 应用进程 内存硬上限 --memory=64m CPU 时间预算 --cpus=0.5 退出事件 Docker 守护进程 --restart 停止 / 再启动 重新启动同一容器 资源限制由内核执行,退出后重启由 Docker 决策(竖版) 运行时 · Linux 内核 应用进程 内存硬上限 --memory=64m CPU 时间预算 --cpus=0.5 进程退出 Docker 守护进程 --restart 保持停止 / 重新启动同一容器
  • Docker · 退出后重启决策
  • 内存 · 硬上限
  • CPU · 时间预算
图 1 内存限制和 CPU 配额约束每次运行;重启策略只决定退出后是否再次启动。

学完你能

退出后的动作

重启策略同时考虑退出原因与人工停止

重启策略(Restart policy)决定容器退出后,Docker 是否重新启动它。默认值 no 不自动重启。退出码 0 通常表示正常完成,非零表示失败。[1]

这里的“重启”是再次启动同一个容器里的主进程,容器 ID 保持不变;它不会更新镜像,也不会自动修复应用。需要回顾对象关系时,读镜像、容器与仓库。

一个定时导出程序已经成功完成,退出码是 0。若设为 always,会安静地保持停止吗?先选出你预测的结果。

不会。always 也会重启正常退出的容器,任务可能重复执行。一次性任务通常保留 no;只想在失败后重试时,考虑有次数上限的 on-failure:N。

以下比较容器已成功启动、策略已生效的情形;不包含手动 docker start。
策略自行退出 0自行退出非零docker stop 后
立即自动启动
Docker 守护进程重启后
no默认保持停止保持停止否不自动启动
on-failure[:N]如 on-failure:2保持停止重启;可设最多 N 次否不因守护进程重启而启动
always重启重启否自动启动,包括之前被手动停止的容器
unless-stopped重启重启否恢复运行中的容器;已停止的容器继续停止

人工停止的意图也参与决策。docker stop 后,always 不会立刻把容器拉起;等 Docker 守护进程重启,它才会回来。unless-stopped 则保留停止状态。[1]

# 在宿主机终端执行
docker run -d --name lp-service --restart unless-stopped alpine:3.22 sleep 3600
docker update --restart on-failure:2 lp-service
docker inspect --format '{{.HostConfig.RestartPolicy}}' lp-service
# 清理本例创建的容器
docker rm -f lp-service
深入:启动成功、退避等待与人工停止

官方说明把“成功启动”定义为已运行至少 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 内核可能杀死容器中的进程以回收内存。如果被杀的是容器主进程,容器随之退出。应用自己的内存分配失败,也可能先让程序报错退出。

从内存需求到内核 OOM,再到 Docker 记录与重启决策 容器内存 · 64 MiB · 无 swap 初始已用 48 MiB 剩余 16 新需求待读取 新申请 32 MiB 条形宽度表示内存量 触及上限 Linux 内核 · 处理内存压力 可用 16 MiB 尚未申请更多内存 再申请并触及 32 MiB 需求 32 MiB > 余量 16 MiB 先尝试回收 回收后仍不足 → 触发 OOM 本例:主进程被杀,容器退出 记录退出 Docker · 状态与后续动作 State.OOMKilled:等待读取 退出后才读取 --restart State.OOMKilled = true 已记录内存不足导致的杀进程 State.OOMKilled = true --restart:保持停止 / 再启动
  1. 设定 64 MiB 上限,已有用量为示意的 48 MiB。
  2. 程序再申请并实际写入 32 MiB。先猜:内核一定会无限借出宿主机内存吗?
  3. 不会。达到硬限制后先尝试回收;仍无法满足时可能触发 OOM。
  4. 本例中被杀的是主进程,所以容器退出。多进程容器也可能只有子进程被杀。
  5. 在保留的容器上检查 State.OOMKilled。先猜:被重启后,原来的内存需求会自动变小吗?
  6. 不会。若需求没变,重启后仍可能再次 OOM。先修内存需求或调整经过评估的限制。
图 2 OOM 可能先让主进程退出,重启策略才接着决策;图中用量为教学示意。

退出码 137 不能单独证明发生过 OOM

主进程收到强制终止信号 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。这是容器内所有进程与线程加起来的预算。多核上可以并行花掉预算,但不能因此增加总配额。

CPU 总配额:0.5 × 100 ms = 50 ms CPU 时间配额的聚合示意,非实际调度时间线。 CPU 时间 · 双逻辑 CPU 主机 每周期 100 ms,共 200 ms 容量 0 100 200 横轴:每周期总 CPU 时间(ms)

总预算 50 ms / 周期,最多占这台双 CPU 主机总容量的 25%;用完后等待补充。

图 3 cpus 约束总 CPU 时间;0.5 在双 CPU 主机上相当于总容量的四分之一。此图为配额示意,不是性能实测。

这是上限,并不预留算力或保证速度。主机忙、程序等待磁盘或网络、线程不足时,实际使用可能更少。多线程进程可以在不同 CPU 上运行;若要限制在哪些 CPU 上运行,另有 --cpuset-cpus。[2]

深入:100 ms 从哪里来?

Docker 文档给出的默认 CPU 周期为 100000 微秒,也就是 100 ms。--cpus=0.5 等价于周期 100000 微秒、配额 50000 微秒。通常直接使用 cpus,不必手动设置两个底层参数。[2]

程序短时间内可能并行消耗配额,所以这幅“总量条”不表示必须先运行连续 50 ms 再停连续 50 ms。CPU 限流会增加延迟,应用仍可能因自己的超时逻辑报错。

在你自己的机器上验证

保留退出现场,才能用 inspect 验证原因

未实测

本页编写环境没有 Docker。下面是可执行的实验命令与预期检查项,不是实测终端记录,待维护者补充输出。请在装有 Docker Engine 的宿主机 Bash / POSIX shell 中执行。

使用 Linux 容器、支持内存与 CPU 控制的内核;Docker Desktop 使用其 Linux 虚拟机。先执行 docker info 检查资源限制相关警告。Rootless 模式还需要 cgroup v2 与 systemd 支持。[7]

短任务能在资源上限内正常完成

这一步给短任务设置内存和 CPU 上限,执行完自动删除容器。镜像标签是选择的实验依赖,不是本环境实测的版本信息。

docker run --rm --memory=64m --memory-swap=64m --cpus=0.5 alpine:3.22 echo limits-ok

预期检查:程序应打印命令中指定的文本 limits-ok 并成功退出。首次拉取可能先出现下载信息。若资源控制不受支持或镜像拉取失败,先处理环境问题再继续。

on-failure:2 最多自动重试两次

运行后先等待约 45 秒。sleep 12 让每轮进程运行超过文档中的启动成功门槛,再用 exit 1 主动模拟失败;on-failure:2 最多自动重启两次。[3]

docker run -d --name lp-retry --restart=on-failure:2 \
--memory=32m --memory-swap=32m --cpus=0.5 \
alpine:3.22 sh -c 'echo starting; sleep 12; exit 1'
sleep 45
docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} restarts={{.RestartCount}}' lp-retry
docker logs lp-retry

预期检查:全部尝试结束后,状态应为 exited、退出码 1、重启次数 2;日志中程序应打印三次 starting。首次运行不计入重启次数。若仍处于运行或重启中,稍后再 inspect,不把等待时间当精确定时保证。

超量申请后,需要读取状态验证是否发生 OOM

下面的 Python 主进程尝试创建并触及一个 128 MiB 的字节数组,而整个容器只允许 64 MiB,且不允许 swap。--restart=no 保留故障状态;故意不加 --rm,便于 inspect。

docker run --name lp-oom --restart=no \
--memory=64m --memory-swap=64m --cpus=0.5 \
python:3.13-alpine python -c 'data = bytearray(128 * 1024 * 1024); print(len(data))'
docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' lp-oom
docker inspect --format 'memory={{.HostConfig.Memory}} swap-total={{.HostConfig.MemorySwap}} nano-cpus={{.HostConfig.NanoCpus}}' lp-oom
docker logs lp-oom

预期检查:若内核 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]

CPU 忙时仍受配额限制,程序到时可以正常退出

启动一个主动计算 20 秒的进程,趁它运行时在同一宿主机执行 stats。程序使用墙钟计时,到时主动退出;无需等待 CPU 配额把它杀掉。

docker run -d --name lp-cpu --memory=64m --memory-swap=64m --cpus=0.5 \
python:3.13-alpine python -c 'import time
end = time.monotonic() + 20
while time.monotonic() < end:
pass'
docker stats --no-stream lp-cpu
# 约 20 秒后检查;若仍 running,稍后再查
docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' lp-cpu

预期检查:运行中 CPU 用量受限,最终应正常退出,退出码 0 且 OOMKilled=false。Linux 上 stats 的 CPU 100% 约代表一个逻辑 CPU 的用量;持续忙时 0.5 配额的读数通常接近 50%,不是整台双 CPU 主机的 50%。采样、负载和调度会带来波动,不能要求恰好 50.00%。[6]

清理仅删除本页创建的三个实验容器

docker rm -f lp-retry lp-oom lp-cpu

只在这些名字确实属于本次实验时执行。镜像仍会保留在本地缓存;本页不要求重启 Docker 守护进程来验证策略,以免中断你机器上其他容器。

把命令连到执行者

Docker 配置控制组,Linux 内核执行限制

控制组(Control groups,cgroups)是 Linux 内核按进程组统计并限制资源的机制。Docker 把 memory、cpus 等参数转成容器控制组的配置。[2][6]

用户态配置资源,内核执行限制 用户态 · Docker 守护进程 保存 --restart memory / cpus 转成控制组配置 写入资源限制 内核态 · cgroups 内存硬上限 / CPU 配额 约束容器内全部进程与线程 退出事件回到 Docker,再按策略决策

Docker 守护进程 保存重启策略,并把资源参数写成内核可执行的控制组配置。

内核对同组进程执行内存上限和CPU 配额。重新启动进程不会放宽这些限制。

图 4 Docker 配置 cgroups,Linux 内核执行限制;重启策略仍由 Docker 守护进程处理。

一个容器反复 OOM 后被 always 重启。为什么只改重启次数,不能解决它的内存问题?用两句话说明两个执行者的分工。

内核仍按同一个 cgroup 内存上限限制每次运行,应用的内存需求也没有消失。Docker 只在退出后重新启动进程;限制重试可以停止循环,修复仍要检查应用需求与资源配置。

  • 提到 cgroups 的资源限制不会因重启而放宽。
  • 提到重启只恢复运行,未修改应用的资源需求。
深入:到模块 7 再看哪些细节?

模块 7 会展开 cgroup v1/v2、控制器与内核文件。这里先记住:资源限制由控制组执行,重启由守护进程决定。Docker Desktop 的 Linux 容器受其虚拟机可用资源约束,不会凭空增加物理主机的算力。

把证据串起来

重启恢复运行,却不会消除资源不足的原因

  1. 正常结束,还是失败退出?按任务意图区分 no、on-failure、always 与 unless-stopped。
  2. 退出码 137,还要查什么?检查 OOMKilled 和日志;137 也可能来自人工强杀。
  3. CPU 配额耗尽会怎样?通常等待补充配额;0.5 是半个逻辑 CPU 的时间上限。
  4. 重启后仍会 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 官方文档;本页示意图、配额计算与实验检查项不代表本机测量记录。

  1. Start containers automatically:四种策略、人工停止、启动成功门槛与前台连接。
  2. Resource constraints:内存硬上限、swap、OOM、CPU 配额与 cgroups。
  3. docker container run:restart 重试次数、退避、RestartCount、rm 的互斥与参数单位。
  4. docker container ls · Filter by exit signal:137 与 SIGKILL,不能据此单独判定 OOM。
  5. Docker Engine API v1.52:ContainerInspect 的 State、OOMKilled、ExitCode、HostConfig 与 NanoCpus 字段。
  6. Runtime metrics 与 Engine API · ContainerStats:cgroup 统计与 Linux CPU 百分比计算。
  7. Rootless mode · Limiting resources:资源限制需要 cgroup v2 与 systemd。