Docker 容器一直 Restarting?重启策略救不了根因,6 步找出它为什么停(附命令)

2 阅读7分钟

刚把服务部署到 VPS,docker ps 里它一直是 Restarting (1),偶尔闪一下 Up,几秒后又回去了。很多人第一反应是改重启策略、加内存,或者直接 docker compose down && up 再来一次。

但重启策略只做一件事:进程停了,把它重新拉起来。它不会修好让进程停下的那个原因。所以先别改配置,先搞清楚进程为什么一次次停下。

下面的命令都只查看、不改动容器。YOUR_CONTAINER 换成实际的容器名或 ID,只在你有权限的服务器上执行。另外提一句:能操作 Docker 基本等于有 root 权限,排障时别为了方便把 Docker API 开到公网,或者把管理权限交给不认识的人。

1. 先确认真的是重启循环

docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} finished={{.State.FinishedAt}} restarts={{.RestartCount}}' YOUR_CONTAINER
docker events --since 30m --until "$(date +%s)" --filter container=YOUR_CONTAINER --filter event=die --filter event=start

怎么看:

  • 隔一会儿再跑一次 inspect,restarts 在变大;或者 docker events 里 die / start 成对反复出现,就是循环。
  • 顺手确认镜像是你想部署的那个,记下故障时间和最近一次部署时间,后面对日志要用。
  • finished 是 UTC 时间(结尾带 Z),北京时间要加 8 小时。

Restarting (N) 括号里的数字是上一次的退出码,只能缩小范围,不是答案。比如 1 多半是应用自己报错,127 是找不到命令,137 是收到了 SIGKILL,但不能直接说明是内存不够。每个数字的含义和该跑的命令,单独整理在这里:Docker 退出码是什么意思?容器秒退、立即退出时看 0、1、125、137、139、143、255。

2. 看重启策略,理解为什么越来越久才 Up 一下

docker inspect --format 'policy={{.HostConfig.RestartPolicy.Name}} max_retries={{.HostConfig.RestartPolicy.MaximumRetryCount}}' YOUR_CONTAINER

四种策略:

  • no(默认):从不自动重启。
  • on-failure[:N]:只在非零退出时重启,可以限制最多 N 次,用完后停在 Exited。
  • always:停了就重启;你手动停掉后,要等 daemon 或容器本身被重启才会再拉起。
  • unless-stopped:和 always 类似,但你手动停掉的容器,daemon 重启后也不会被拉起。

很多人困惑的一点:为什么容器大部分时间都显示 Restarting,很少看到 Up?因为 Docker 每次重启前会等得更久一点,间隔从 100 毫秒开始翻倍,最长 1 分钟;容器只要连续跑满 10 秒,间隔就重置。一个反复崩溃的容器,等待时间越拉越长,Up 只是一闪而过。

改策略修不好失败的启动命令,只会改变你看到失败的频率。还有一个坑:同一个容器不要同时交给 Docker 重启策略和 systemd 去管,两边一起拉起,现象会更乱。Compose 里策略写在服务的 restart: 下。

3. 只看故障附近的日志,找第一条报错

docker logs --since 15m --tail 200 --timestamps YOUR_CONTAINER

重点是每次停下之前的第一条异常,而不是最后一行。循环里同样的错误会一遍遍重复,最后一行往往只是收尾。

常见的几类:

  • 连接数据库被拒绝:先看第 4 步。容器里的 localhost 是容器自己,不是隔壁的数据库服务。
  • 权限不足、文件不存在:核对挂载路径、文件属主、运行用户和启动参数。别图省事直接 chmod 777。
  • 配置或数据库迁移报错:对照最近的变更,先想清楚回滚办法和数据兼容,再动生产配置。

日志是空的也不代表没出错:应用可能把日志写进了文件,或者当前的日志驱动拿不到这部分输出,这时去看应用的日志配置。

4. 依赖还没就绪:最常见的一种循环

应用比数据库、缓存或某个 API 先起来,第一次连接失败就退出,然后被一遍遍拉起,直到依赖碰巧就绪。宿主机重启后特别容易遇到。

docker compose ps
docker compose logs --since 15m --timestamps YOUR_DEPENDENCY
  • Compose 里只写 depends_on,只等依赖的容器启动,不等它就绪。condition: service_healthy 会等依赖的健康检查通过,前提是那个服务配了健康检查。
  • 把应用里用的地址,和依赖在同一网络里的服务名、端口对一遍。
  • 应用连一次失败就放弃的话,在应用里加一个简短的重试,通常比靠容器重启兜底更合适。

5. 健康检查:unhealthy 不等于会被重启

docker inspect --format '{{json .State.Health}}' YOUR_CONTAINER

这条命令能看到健康状态,以及最近几次检查的结果和输出。

一个容易误会的点:单机 Docker Engine 只会把容器标成 unhealthy,不会因为健康检查失败去重启它。 如果你看到不健康的容器在被重启,说明是别的东西在干这件事,比如 Swarm、编排工具或某个辅助容器,该去看那个工具的日志。

反过来,健康检查写得太严,或者启动慢的应用没设启动宽限期(start period),会让依赖它的服务一直卡在 service_healthy。先看健康检查自己的输出,再怀疑应用。

6. 资源:内存、磁盘、端口

docker inspect --format 'memory_limit_bytes={{.HostConfig.Memory}}' YOUR_CONTAINER
docker stats --no-stream YOUR_CONTAINER
df -h
  • 内存:OOMKilled=true 是 Docker 记下的内存终止线索;只有一个 137 证明不了。docker stats 是当前快照,还原不了故障那一刻的峰值。
  • 磁盘:盘写满了,数据库和应用在启动时就会失败。先查清是什么占满的,别急着删文件。
  • 端口:报 port is already allocated,说明端口被别的进程或容器占着,用 sudo ss -ltnp 看是谁。

把证据变成下一步

查到这里,先写四行:现象 → 证据 → 待验证的原因 → 最小的下一步检查。生产环境动手之前,先说清影响范围、挑好时间、想好回滚。删数据、删容器、放大权限,都不是通用的修复办法。

说明性示例(不是真实客户案例):宿主机重启后,应用容器显示 Restarting (1),重启次数一直涨,数据库容器是 Up。应用每次启动后的第一条日志都是连接数据库被拒绝;数据库日志显示,那几个时间点它还在初始化。Compose 里只写了 depends_on,没加健康条件。这些证据支持「依赖没就绪」。可选的改法有两个:给数据库加健康检查、应用用 condition: service_healthy;或者在应用里加连接重试。两种都是变更,改完再看重启次数是不是不涨了。

重启策略只会把失败藏起来;退出码、事件和第一条报错,才告诉你它到底是什么。


文中的命令都建议自己手敲、一条一条核对;改 Compose、健康检查和重启策略之前先想好回滚。我们在做的 OpsMate(itops.sh)把 SSH 终端和 AI 放在同一页,可以让 AI 帮你对照日志、重启次数和依赖状态。未通过安全检查的命令会被拦截且不会执行。低风险修复默认自动执行,每一步都会记录。每次让 AI 帮忙看,终端里最近的输出都会一起发到云端 AI,敏感信息请先去掉。

原文和完整版见 itops.sh 指南:www.itops.sh/guides/dock…

每月免费 500 次 AI 请求、服务器数量不限,注册:app.itops.sh/login?scree…

本文借助 AI 起草,发布前经人工审核。