← 全部笔记

Docker · 3 容器运行与管理

退出后能重启,
运行时有上限

重启策略决定进程退出后的动作;内存与 CPU 限制约束它运行时的资源。

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

资源限制与重启策略作用在不同阶段 运行中的进程受 memory 和 cpus 约束。进程退出后,Docker 守护进程读取 restart 策略,再决定保持停止还是重新启动同一容器。 容器边界 · 运行时 应用进程 --memory --cpus 进程退出 Docker 按 --restart 决策 重新启动 或保持停止
图 1 资源限制作用于运行过程;重启策略在容器退出后决定是否再启动。

退出后的动作

重启看退出原因

重启策略(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 内核可能杀死容器中的进程以回收内存。如果被杀的是容器主进程,容器随之退出。应用自己的内存分配失败,也可能先让程序报错退出。

从触及内存上限到检查退出原因 示意内存上限 64 MiB,已有用量 48 MiB;再申请并触及 32 MiB 后,可用内存不足,内核杀死主进程,Docker 记录 OOMKilled,再按重启策略决定是否重新启动。 容器内存 · 示意,无交换空间 已用 48 MiB剩余 16 上限 64 MiB 再申请并触及 32 MiB 需求超过余量 回收后仍不足 → 触发 OOM 本例:主进程被杀,容器退出 State.OOMKilled = true 随后读取 --restart 策略决定:保持停止 / 再启动
  1. 设定 64 MiB 上限,已有用量为示意的 48 MiB。
  2. 程序再申请并实际写入 32 MiB。先猜:内核一定会无限借出宿主机内存吗?
  3. 不会。达到硬限制后先尝试回收;仍无法满足时可能触发 OOM。
  4. 本例中被杀的是主进程,所以容器退出。多进程容器也可能只有子进程被杀。
  5. 在保留的容器上检查 State.OOMKilled。先猜:被重启后,原来的内存需求会自动变小吗?
  6. 不会。若需求没变,重启后仍可能再次 OOM。先修内存需求或调整经过评估的限制。
图 2 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。这是容器内所有进程与线程加起来的预算。多核上可以并行花掉预算,但不能因此增加总配额。

CPU 总配额随 cpus 参数改变 假设宿主机有两个逻辑 CPU,每周期 100 毫秒,总容量为 200 毫秒 CPU 时间。cpus 等于 0.5 时允许 50 毫秒,占总容量四分之一。 2 个逻辑 CPU · 周期 100 ms 聚合资源量,非实际调度时间线 0100200 横轴:每周期总 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 限流会增加延迟,应用仍可能因自己的超时逻辑报错。

在你自己的机器上验证

保留现场再检查

未实测

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

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

会拉取缺失的镜像并写入 Docker 本地缓存;创建少量有资源上限的容器。不发布端口、不挂载宿主目录;无需修改宿主网络、iptables 或使用 --privileged。下面的名称须未被占用。

实验 A:先确认能正常运行

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

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

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

实验 B:故障最多重试两次

运行后先等待约 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,不把等待时间当精确定时保证。

实验 C:只让受限容器申请过量内存

下面的 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]

实验 D:观察 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 守护进程来验证策略,以免中断你机器上其他容器。

把命令连到执行者

限制由内核执行

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

Docker 配置策略,内核执行资源限制 Docker 在用户态写入 memory 和 CPU 配额到内核 cgroup。内核对进程组执行限制。主进程退出事件回到 Docker 后,Docker 根据 restart 决定是否再启动。 用户态 · Docker 守护进程 保存 --restart,配置资源参数 写入资源限制 内核态 cgroup:内存上限、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。