Docker容器反复重启怎么办?退出码、日志与健康检查排查教程

Docker容器反复重启怎么办?退出码、日志与健康检查排查教程

Docker容器一直处于Restarting状态时,网站可能间歇性出现502、503或连接失败,后台任务也会不断中断。此时最重要的不是反复执行docker restart,而是先暂停自动重启,保留退出现场,再根据退出码、日志和容器状态找到真正原因。

容器反复重启通常不是Docker本身“坏了”,而是容器中的主进程退出后,重启策略又把它拉了起来。配置缺失、启动命令错误、端口冲突、依赖服务未就绪、文件权限、内存不足和健康检查设计不当,都可能制造重启循环。

本文介绍一套适用于Docker Engine和Docker Compose环境的排查方法。

一、先确认容器是否真的在重启

查看全部容器:

docker ps -a

只查看目标容器:

docker ps -a --filter name=myapp

如果状态不断在RestartingUpExited之间切换,继续查看重启次数:

docker inspect -f '{{.RestartCount}}' myapp

再输出完整运行状态:

docker inspect -f '{{json .State}}' myapp

重点关注这些字段:

字段含义
Status当前状态,例如runningexitedrestarting
ExitCode容器主进程退出码
ErrorDocker启动容器时记录的错误
OOMKilled是否被Docker记录为内存不足杀死
StartedAt最近一次启动时间
FinishedAt最近一次退出时间
Health配置健康检查后显示健康状态和探测记录

如果StartedAtFinishedAt间隔只有几秒,通常说明应用在启动阶段就失败了。如果容器运行一段时间后才退出,更需要关注内存增长、外部依赖、定时任务和运行期间的异常。

二、先暂停重启循环,保留故障现场

容器每隔几秒重启一次,会快速刷掉关键日志,也可能持续冲击数据库、消息队列或第三方接口。可以先关闭目标容器的重启策略:

docker update --restart=no myapp

然后停止容器:

docker stop myapp

确认状态:

docker ps -a --filter name=myapp

不要直接删除容器,更不要立刻执行docker compose down -v。删除容器可能丢失未挂载到卷中的现场文件,而-v还会删除Compose管理的命名卷,可能造成实际数据损失。

如果这是线上核心服务,暂停前应先确认是否有其他副本承接流量,并记录当前镜像ID、环境变量来源、挂载、网络和启动参数。

三、查看容器日志

先查看最近200行并显示时间:

docker logs --tail 200 --timestamps myapp

查看最近30分钟:

docker logs --since 30m --timestamps myapp

持续观察下一次启动过程:

docker logs -f --since 5m --timestamps myapp

常见报错包括:

  • 配置文件不存在或格式错误。
  • 环境变量、密钥或证书缺失。
  • 数据库、Redis、消息队列连接失败。
  • 监听端口已被占用。
  • 挂载目录没有读写权限。
  • 启动脚本或可执行文件不存在。
  • 数据库迁移失败。
  • Java、Node.js、PHP或其他运行时抛出未处理异常。

docker logs主要读取容器标准输出和标准错误。如果应用把日志只写进容器内文件,或者使用了不支持本地读取的日志驱动,命令可能没有足够信息。此时需要检查应用日志目录和Docker日志配置:

docker inspect -f '{{.HostConfig.LogConfig.Type}}' myapp

不要为了排查方便就长期启用无限增长的日志。生产环境应配置日志轮转或日志平台,避免日志本身占满宿主机磁盘。

四、结合Docker事件还原时间线

查看最近30分钟内与目标容器有关的事件:

docker events \
  --since 30m \
  --filter container=myapp

事件中可能出现startdierestartoomhealth_status等记录。它能帮助确认是主进程主动退出、Docker重启策略触发,还是健康状态发生变化。

docker events不是永久审计日志,历史事件可用范围有限。重要生产环境应把容器事件、应用日志和主机监控统一采集,避免故障发生几小时后已经找不到现场。

五、正确理解常见退出码

退出码是排查入口,不是最终结论。

退出码常见含义下一步
0主进程正常结束检查服务是否误把一次性脚本当成长驻进程,或程序是否启动后自行退出
1应用通用错误查看应用日志、配置和启动参数
125docker run自身或Docker守护进程未能正常启动容器查看命令参数、Docker错误和守护进程日志
126找到命令但无法执行检查执行权限、文件格式和架构
127找不到启动命令检查镜像中的路径、ENTRYPOINTCMD
137进程收到SIGKILL,常见于OOM或被强制终止检查OOMKilled、内核日志、内存限制和人工操作
143进程收到SIGTERM后退出检查停止、部署、超时和优雅退出流程

退出码137经常被直接判断为内存不足,但它只说明进程受到强制终止。只有结合下面的信息,才能判断是否发生OOM:

docker inspect -f '{{.State.OOMKilled}}' myapp

查看容器内存限制:

docker inspect -f '{{.HostConfig.Memory}}' myapp

查看当前资源使用:

docker stats --no-stream myapp

检查宿主机内核日志:

sudo journalctl -k --since '1 hour ago' \
  | grep -Ei 'out of memory|oom|killed process'

HostConfig.Memory输出为字节,值为0通常表示没有设置容器内存上限,但进程仍可能因为宿主机整体内存耗尽而被内核终止。

六、检查启动命令和PID 1

查看镜像与容器最终使用的启动配置:

docker inspect -f '{{json .Config.Entrypoint}}' myapp
docker inspect -f '{{json .Config.Cmd}}' myapp

常见问题包括:

  • ENTRYPOINT脚本没有执行权限。
  • Windows换行符导致Linux脚本无法解释。
  • 脚本中引用了镜像内不存在的命令。
  • 工作目录错误,相对路径找不到文件。
  • 启动脚本把服务放到后台后自身退出。
  • Shell没有使用exec把主进程交给PID 1。

Dockerfile更推荐使用exec形式启动长驻进程:

ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["/app/server"]

启动脚本最后可以使用:

exec "$@"

这样应用会成为容器中的主进程,能够更直接地接收停止信号并返回真实退出码。

如果镜像内包含Shell,可以临时覆盖入口进行检查:

docker run --rm -it \
  --entrypoint /bin/sh \
  your-image:tag

精简镜像或distroless镜像可能没有/bin/sh。不要为了调试就随意修改线上镜像,可在同一构建版本上制作专用调试镜像。

七、检查环境变量、挂载和文件权限

查看容器配置:

docker inspect myapp

重点检查:

  • Config.Env中的必要变量是否存在。
  • Mounts中的源路径和目标路径是否正确。
  • 配置文件是否被空目录覆盖。
  • 容器运行用户是否有权读取证书、配置和数据目录。
  • 挂载文件在宿主机上是否存在。

查看挂载:

docker inspect -f '{{json .Mounts}}' myapp

检查容器运行用户:

docker inspect -f '{{.Config.User}}' myapp

宿主机目录权限应与容器内进程使用的UID和GID匹配。不要为了快速恢复直接把目录改成chmod 777,这会扩大安全风险,也可能掩盖镜像用户配置错误。

docker inspectdocker compose config可能展开环境变量,其中可能包含密码、令牌和连接串。排查输出不要直接粘贴到公开工单或聊天群。

八、检查依赖服务是否尚未就绪

应用容器可能比数据库、Redis或消息队列更早启动。如果程序连接失败后直接退出,再配合自动重启策略,就会形成循环。

先确认各服务状态:

docker compose ps

查看依赖服务日志:

docker compose logs --tail 200 db redis

Docker Compose可以用健康状态控制启动依赖,例如:

services:
  db:
    image: mysql:8.4
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

  app:
    image: example/app:1.0
    depends_on:
      db:
        condition: service_healthy
    restart: "on-failure:5"

这只能改善启动顺序,不能代替应用自身的重试、超时和断线重连。数据库在运行期间短暂不可用时,应用仍应采用有上限的退避重试,而不是遇到一次连接失败就结束整个进程。

九、健康检查失败会自动重启容器吗

普通Docker容器的健康检查会把状态标记为startinghealthyunhealthy,并保存最近的探测结果,但unhealthy本身不会触发Docker Engine的重启策略。

查看健康状态:

docker inspect -f '{{json .State.Health}}' myapp

如果容器状态是Up但健康状态为unhealthy,应查看健康检查输出和应用内部状态,而不是把它和“主进程退出后自动重启”混为一谈。某些编排平台或外部监控会根据健康状态执行替换或重启,那是编排层行为。

一个较完整的Compose健康检查示例:

services:
  app:
    image: example/app:1.0
    restart: unless-stopped
    init: true
    healthcheck:
      test: ["CMD", "/app/healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

/app/healthcheck必须真实存在于镜像中,并在服务可用时返回0。如果使用curlwget探测HTTP接口,要先确认镜像中安装了对应工具。健康检查过于频繁、超时太短或依赖外部网络,也可能制造大量误报。

十、检查重启策略是否合理

查看当前策略:

docker inspect -f '{{json .HostConfig.RestartPolicy}}' myapp

Docker常见重启策略包括:

策略行为
no不自动重启,默认值
on-failure[:max-retries]仅在非零退出时重启,可限制次数
always容器停止后持续尝试重启;人工停止后,要等Docker守护进程重启或手动启动才会恢复
unless-stoppedalways相近,但人工停止的容器在Docker守护进程重启后仍保持停止

重启策略只有在容器成功启动并持续运行至少约10秒后才会生效,这可以避免Docker把一个从未正常启动的容器无限当作稳定服务处理。

排障阶段更适合暂时使用no,批处理任务可根据业务选择带最大次数的on-failure,长驻服务通常使用unless-stopped或由统一编排平台管理。不要同时让宿主机进程管理器和Docker重启策略反复拉起同一个容器,否则故障行为会更难判断。

修复后恢复策略:

docker update --restart=unless-stopped myapp

如果使用Compose,应修改compose.yaml中的配置后重新创建容器,避免运行时配置与文件不一致:

docker compose up -d --force-recreate app

十一、修复后如何验证

不要只看到容器状态变成Up就结束排查。建议继续验证:

docker ps --filter name=myapp
docker inspect -f '{{.RestartCount}}' myapp
docker logs --since 10m --timestamps myapp
docker stats --no-stream myapp

同时确认:

  1. 重启次数不再增长。
  2. 容器持续运行时间正常。
  3. 健康状态达到healthy,且探测输出没有间歇性失败。
  4. 应用端口、接口和后台任务实际可用。
  5. CPU、内存和磁盘使用没有持续上升。
  6. 数据库迁移、消息消费和定时任务没有重复执行。

如果故障涉及OOM、数据卷或数据库连接,还应观察一段完整业务高峰。短时间“看起来正常”并不能证明问题已经解决。

十二、一套实用的排查顺序

遇到Docker容器反复重启,可以按下面的顺序处理:

  1. 使用docker ps -a确认状态和容器名称。
  2. docker inspect记录退出码、OOMKilled、重启次数和时间。
  3. 暂时关闭重启策略,避免日志和依赖服务被持续冲击。
  4. 使用docker logsdocker events还原退出前后的时间线。
  5. 根据退出码检查内存、启动命令、配置、权限和外部依赖。
  6. 单独检查健康状态,不要把unhealthy直接等同于自动重启。
  7. 修复后重新创建或启动容器,并持续观察重启次数和资源使用。

重启策略的作用是帮助服务从偶发故障中恢复,不是替代错误处理。一个容器每隔几秒被重新拉起,只说明恢复动作在执行,并不说明服务已经恢复正常。

知识库

MySQL查询速度慢怎么办?慢查询日志、EXPLAIN与索引优化教程

2026-8-19 16:51:18

知识库

开源软件的商业连续性风险:当你的“免费”基础设施突然消失

2025-12-4 12:05:51