直接答案:容器持续 Restarting 时,先查看 docker inspect 的 State、ExitCode、OOMKilled、Error、StartedAt/FinishedAt 和 RestartCount,再读取 docker logs --since。常见根因是入口命令立即退出、配置/秘密缺失、端口或文件权限错误、依赖未就绪、健康检查设计不当、内存被 cgroup OOM 杀死,或 always/unless-stopped 策略把一次失败变成循环。不要先反复 docker restart;它会缩短取证窗口并持续冲击依赖。
本文适合单容器或 Compose 服务反复启动退出。若状态是 Up (unhealthy) 而非 Restarting,Docker 本身不会仅因普通 healthcheck 失败自动重启单个容器,需检查编排器或外部监控策略。
本文按 2026 年 9 月可查的官方文档核对。示例命令以只读取证为主;凡涉及删除、覆盖、重启、权限、网络或数据库参数的动作,都应先确认操作对象、备份、维护窗口和带外恢复入口。不同发行版与软件版本的路径可能不同,必须先查询实际配置再改。
先用证据判断故障在哪一层
| 现象或检查点 | 它能说明什么 | 直接证据 | 不要误判为 |
|---|---|---|---|
| ExitCode非零且日志有应用异常 | 应用/配置主动退出 | 最后日志、入口命令与配置 | Docker守护进程故障 |
| OOMKilled=true或退出137 | 内存限制或宿主OOM可能参与 | inspect、内核日志与cgroup指标 | 一定是程序崩溃 |
| 退出0仍重启 | 主进程正常结束但策略要求常驻 | Entrypoint/Cmd与restart policy | 容器健康 |
| 挂载后才失败 | 文件、权限、架构或配置被覆盖 | Mounts与容器内实际路径 | 镜像损坏 |
| 依赖稍后可用仍循环 | 启动探针、等待或重试策略不足 | 时间线、DNS与依赖日志 | 只需增加depends_on |
这张表的用法不是“看到一个关键词就改一个参数”,而是把客户端、DNS或边缘、Web 代理、应用、数据层依次分开。至少保留故障时间、完整 URL、状态码、关键响应头、同一时刻的服务日志和一份正常样本。只有时间能够对齐,才能判断两个现象是否属于同一次故障。
六步排查与修复顺序
1. 保存状态并降低循环冲击
记录容器 ID、镜像 digest、创建时间、重启次数和当前策略。若循环正在压垮数据库或日志,可在维护判断后停止单个容器;先保存 inspect 和最近日志,不要删除容器。
2. 读取退出码与上一轮日志
ExitCode、OOMKilled 和 Error 提供第一层方向;日志要包含上一轮短暂运行窗口。若应用把日志写文件而非 stdout/stderr,检查挂载目录和权限,但不要进入循环容器里手工修改后丢失可复现性。
3. 核对镜像入口与运行参数
查看 Entrypoint、Cmd、WorkingDir、User、Env、Mounts、端口和架构,比较 Compose 解析后的配置。空的环境变量、被 bind mount 覆盖的目录、CRLF 脚本或无执行权限都可能让 PID 1 立即退出。
4. 区分内存、健康和依赖
退出 137 不总是 OOM,结合 inspect 与内核日志;依赖启动顺序不等于可用,应用应重试。healthcheck 要检查真实服务但不能过重,start_period 应覆盖正常初始化时间。
5. 修复根因再设置重启策略
在临时无自动重启的隔离容器中复现入口命令,修配置、权限或依赖,并固定镜像版本。选择 on-failure、always 或 unless-stopped 时明确预期;不要用 host systemd 和 Docker policy 双重管理同一容器。
6. 验证启动、运行与重启
新容器先保持稳定超过 Docker 监控窗口,检查健康、日志和资源,再主动重启 Docker/主机验证策略。应用还要经过一次真实读写和依赖短暂不可用测试,确认没有重连风暴或数据损坏。
可复制的只读取证命令
以下命令中的域名、服务名、路径和时间范围要替换为自己的真实值。先执行查询命令并保存原始输出;不要把“命令能运行”误当成“业务已恢复”。
text
docker ps -a --filter 'status=restarting' --no-trunc
docker inspect --format '{{json .State}}' your-container
docker inspect --format '{{json .HostConfig.RestartPolicy}}' your-container
docker logs --timestamps --since 20m your-container
journalctl -k --since '30 minutes ago' | grep -Ei 'oom|killed process|memory cgroup'
docker ps -a --filter 'status=restarting' --no-trunc:列出重启中的容器及完整命令摘要docker inspect --format '{{json .State}}' your-container:查看退出码、OOMKilled、错误和时间docker inspect --format '{{json .HostConfig.RestartPolicy}}' your-container:查看自动重启策略docker logs --timestamps --since 20m your-container:保存最近多轮stdout/stderr日志journalctl -k --since '30 minutes ago' | grep -Ei 'oom|killed process|memory cgroup':查宿主内核是否记录OOM或cgroup杀进程
如命令输出包含公网 IP、用户名、Cookie、令牌、数据库连接串或客户数据,分享工单前先脱敏。对比时保留时间戳、时区和命令本身,否则后来无法复现同一观察条件。
五个最容易扩大故障的误区
- 不断 docker restart:只重复失败并覆盖日志,不会修复入口或依赖。
- 立刻 docker rm:会丢失 inspect、挂载和退出状态证据。
- 看到137就断言OOM:137表示被SIGKILL,需结合 OOMKilled 和内核日志。
- depends_on当成就绪检查:它不能保证数据库已可接受业务查询,应用仍需退避重试。
- 同时设置Docker和systemd重启:两个管理器会制造竞态、重复实例和不可预测停止行为。
变更、回滚与数据边界
保留旧容器、镜像 digest、Compose 配置和 volume 快照。新镜像或配置失败时,停止新容器并以原 digest/配置启动旧版本;数据库模式若已不可逆迁移,不能只切回镜像。修改 restart policy 后若产生循环,先恢复 no 或上一策略止损,再处理根因;不要删除 volumes 作为回滚步骤。
如果只能先恢复服务、还不能确认根因,应把措施标记为“临时缓解”,例如限流、摘除单节点、切回上一配置或临时绕过一层代理。恢复后仍要在受控环境复现原因,并验证同类故障不会在下一次重启、发布或高峰重新出现。
统一验收清单
- [ ] State、ExitCode、OOMKilled和RestartCount已保存
- [ ] 上一轮失败日志完整
- [ ] Entrypoint、Cmd、User、Env和Mounts已核对
- [ ] 依赖就绪与启动顺序已区分
- [ ] 自动重启策略与业务目标一致
- [ ] 容器持续稳定且健康检查通过
- [ ] Docker/主机重启后按预期恢复
- [ ] 真实读写、日志和资源无异常
验收必须同时覆盖机器内部和真实外部路径。首页返回 200 不能证明上传、长连接、登录、数据库写入或移动端都正常;同样,控制台显示“运行中”也不能证明应用已能处理请求。至少选一个正常请求、一个边界请求和一个失败请求,记录预期与实际结果。
相关内容与隔离测试入口
建议先阅读Docker Compose生产部署、云服务器内存不足、服务器日志保留,把本问题放回完整请求链路。若生产环境不适合直接试验,可查看美国弹性云服务器的当前规格与库存,搭建与生产隔离的复现环境;测试机不能替代生产备份,价格、库存和可选配置以实时产品页为准。
常见问题
为什么日志是空的?
进程可能在日志系统初始化前退出、写入文件、权限失败或入口未执行。查看 State.Error、入口和挂载。
退出码0为什么仍一直重启?
主进程正常结束,但 restart policy 要求容器常驻。检查命令是否应前台运行,以及任务型容器是否用了不合适策略。
healthcheck失败会自动重启吗?
普通Docker容器只会标记 unhealthy,不一定自动重启;Swarm/Kubernetes或外部监控可能另有行为。
使用 --restart always 能提高可用性吗?
只能在进程退出后再次启动,不能修坏配置、数据或依赖,甚至可能形成重启风暴。
容器重建会丢数据吗?
写在可写层的数据会随容器删除而丢失;明确的 volume/bind mount 仍需备份和恢复验证。