Docker 容器一直 Restarting 怎么排查:日志、退出码、Healthcheck 与 OOM 定位
Docker 容器反复进入 Restarting 状态,通常不是 Docker 本身“卡住了”,而是容器主进程退出后,重启策略又将它拉起。常见原因包括启动参数错误、依赖不可用、健康检查配置不当、进程被信号终止,以及容器或宿主机内存不足。
排查时不要急着删除并重建容器。删除操作可能丢失现场信息,甚至误删未持久化的数据。更有效的顺序是:
- 确认容器是否真的在重启;
- 保存日志、退出码和事件;
- 检查重启策略;
- 根据退出码定位进程退出原因;
- 分别检查 Healthcheck、OOM、挂载、权限及外部依赖;
- 修复后验证容器是否稳定运行。
一、先确认是“进程退出”,还是仅仅“不健康”
执行:
docker ps -a
典型状态可能是:
Restarting (1) 5 seconds ago
这表示容器主进程已经退出,Docker 正根据重启策略再次启动它。
如果显示的是:
Up 10 minutes (unhealthy)
则容器主进程仍在运行,只是健康检查失败。二者不是同一个问题。
需要特别注意:
在普通 Docker 或 Docker Compose 环境中,容器变成
unhealthy并不会仅凭 Healthcheck 自动重启。
如果一个不健康的容器同时反复重启,通常还有其他原因:
- 应用发现自身异常后主动退出;
- 配置了外部自动修复工具;
- 容器由 Swarm 等编排系统管理;
- 主进程崩溃,Healthcheck 只是更早暴露了问题;
- 运维脚本或监控平台正在执行重启。
二、第一时间保存现场信息
先指定容器名称或 ID:
c=my-container
1. 查看完整运行状态
docker inspect -f \
'status={{.State.Status}} running={{.State.Running}} restarting={{.State.Restarting}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{printf "%q" .State.Error}} started={{.State.StartedAt}} finished={{.State.FinishedAt}} restarts={{.RestartCount}}' \
"$c"
重点关注:
status:当前状态;exit:最近一次退出码;oom:是否被记录为 OOMKilled;error:Docker 启动容器时遇到的错误;restarts:该容器的累计重启次数;started与finished:每次是否只运行了几秒。
如果容器被 Docker Compose 重新创建过,旧容器的 RestartCount 不会自动继承。因此还要结合容器 ID、创建时间和事件判断。
2. 查看应用日志
docker logs --tail 200 --timestamps "$c"
持续观察:
docker logs -f --tail 200 --timestamps "$c"
只查看最近 30 分钟:
docker logs --since 30m --timestamps "$c"
Docker Compose 服务可使用:
docker compose logs --tail 200 --timestamps <服务名>
重点搜索以下信息:
permission deniedconnection refusedtimeoutno such file or directoryaddress already in useout of memorykilledsegmentation fault- 数据库认证失败
- 配置文件语法错误
- 证书过期或主机名不匹配
如果 docker logs 没有内容,不代表应用没有报错。还要检查日志驱动:
docker inspect -f '{{.HostConfig.LogConfig.Type}}' "$c"
应用也可能将日志写入容器内文件或挂载目录,而不是标准输出。此时需要检查镜像配置、挂载路径和应用日志目录。
3. 查看 Docker 事件
Docker 事件能够补充日志中没有记录的信息:
docker events \
--since 30m \
--filter type=container \
--filter container="$c"
常见事件包括:
startdierestartoomhealth_status: unhealthykill
如果需要单独检查近期 OOM 事件:
docker events \
--since 1h \
--filter type=container \
--filter event=oom
docker events 默认持续等待新事件,可以按 Ctrl+C 结束。
三、先看重启策略,判断为什么会被反复拉起
查看容器的重启策略:
docker inspect -f \
'policy={{.HostConfig.RestartPolicy.Name}} max_retry={{.HostConfig.RestartPolicy.MaximumRetryCount}}' \
"$c"
常见策略如下:
| 重启策略 | 行为 |
|---|---|
no |
容器退出后不自动重启 |
always |
主进程退出后自动重启 |
unless-stopped |
除非被人工停止,否则自动重启 |
on-failure |
仅在退出码非 0 时重启 |
on-failure:N |
非 0 退出时重启,最多尝试 N 次 |
例如,应用正常执行完任务并以退出码 0 结束,但容器配置了 restart: always,也会形成持续重启。这类问题不是应用崩溃,而是容器用途与重启策略不匹配。
查看 Docker Compose 最终生效配置:
docker compose config
不要只查看原始 Compose 文件,因为变量替换、多个配置文件合并和命令行参数可能改变最终结果。
临时停止重启循环
为了保留现场并避免日志被不断刷屏,可以先记录原重启策略,再临时关闭:
docker update --restart=no "$c"
docker stop -t 30 "$c"
完成排查后,再按实际需求恢复:
docker update --restart=unless-stopped "$c"
如果容器由 Docker Compose 管理,最终仍应修改 Compose 文件中的 restart 配置并重新部署,避免下次重建后恢复旧配置。
如果容器是 Docker Swarm 的任务实例,不应只修改底层容器,因为 Swarm 会重新创建任务。应改为检查服务:
docker service ps --no-trunc <服务名>
docker service logs --timestamps <服务名>
docker service inspect <服务名>
四、根据退出码缩小排查范围
查询最近一次退出码:
docker inspect -f '{{.State.ExitCode}}' "$c"
常见退出码及含义如下:
| 退出码 | 常见含义 | 优先检查 |
|---|---|---|
0 |
进程正常结束 | 是否误用了 always;程序是否本来就是一次性任务 |
1 |
通用应用错误 | 应用日志、配置文件、环境变量、依赖服务 |
2 |
命令参数或程序使用错误 | 启动参数、命令行选项、脚本语法 |
126 |
命令存在但不能执行 | 执行权限、挂载选项、文件格式、架构 |
127 |
找不到命令 | ENTRYPOINT、CMD、PATH、镜像内容 |
137 |
通常表示收到 SIGKILL |
OOM、人工 docker kill、宿主机强制终止 |
139 |
通常为段错误 SIGSEGV |
原生程序崩溃、动态库、架构或硬件问题 |
143 |
通常表示收到 SIGTERM |
正常停止、编排平台滚动更新、超时终止 |
Linux 中,退出码大于 128 时,常见计算方式是:
退出码 = 128 + 信号编号
例如:
137 = 128 + 9,对应SIGKILL;143 = 128 + 15,对应SIGTERM。
但退出码只能作为线索,不能单独作为结论。特别是:
退出码 137 不等于一定发生了 OOM。
人工执行 docker kill、宿主机脚本发送 SIGKILL,也可能得到 137。必须结合 .State.OOMKilled、Docker 事件和内核日志确认。
五、检查实际启动命令和入口脚本
镜像默认命令、Compose 覆盖项和运行参数可能共同决定最终启动方式。
查看容器实际配置:
docker inspect -f 'path={{.Path}} args={{json .Args}}' "$c"
查看镜像层面的入口和命令:
docker inspect -f \
'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}' \
"$c"
常见问题包括:
1. 启动脚本不存在
日志可能出现:
exec: "/app/start.sh": stat /app/start.sh: no such file or directory
检查:
- Dockerfile 是否正确
COPY; .dockerignore是否意外排除了脚本;- 卷挂载是否覆盖了镜像中的
/app; - 文件路径和大小写是否正确。
2. 脚本没有执行权限
典型日志:
permission denied
Dockerfile 中应显式设置权限:
COPY start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh
也可以使用支持该语法的构建器:
COPY --chmod=755 start.sh /usr/local/bin/start.sh
3. Windows 换行符导致脚本无法运行
脚本使用 CRLF 时,可能出现:
/bin/sh^M: bad interpreter
应将脚本转换为 LF,并在版本库中统一换行规则。
4. 卷挂载覆盖镜像文件
例如镜像内已有 /app/start.sh,但运行时挂载了空目录:
volumes:
- ./app:/app
此时镜像原有 /app 内容会被挂载目录遮盖。使用以下命令检查挂载:
docker inspect -f '{{json .Mounts}}' "$c"
如已安装 jq,可以更清晰地显示:
docker inspect "$c" | jq '.[0].Mounts'
5. 镜像与宿主机架构不兼容
常见错误包括:
exec format error
检查宿主机架构:
uname -m
检查镜像信息:
docker image inspect <镜像名> \
-f 'os={{.Os}} arch={{.Architecture}}'
多架构镜像还可以查看清单:
docker buildx imagetools inspect <镜像名>
六、Healthcheck 失败怎么排查
1. 查看健康状态和失败输出
docker inspect -f '{{json .State.Health}}' "$c"
如有 jq:
docker inspect "$c" | jq '.[0].State.Health'
只查看每次检查的时间、退出码和输出:
docker inspect -f \
'{{range .State.Health.Log}}{{println .Start "exit=" .ExitCode .Output}}{{end}}' \
"$c"
查看健康检查配置:
docker inspect -f '{{json .Config.Healthcheck}}' "$c"
Healthcheck 的结果通常为:
0:健康;1:不健康;2:保留值,不应作为普通检查结果使用。
2. 在容器内手工执行同一条命令
如果容器能维持运行一段时间,可以执行:
docker exec "$c" sh -c '<健康检查命令>'
echo $?
例如:
docker exec "$c" sh -c 'curl -fsS http://127.0.0.1:8080/health'
但不能默认镜像里存在 sh、curl 或 wget。精简镜像、distroless 镜像可能没有这些工具。应根据镜像实际内容设计检查命令,或者让应用自身提供轻量的健康检查程序。
3. 常见 Healthcheck 配置错误
检查工具根本没有安装
例如配置使用了:
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
但镜像中没有 curl,检查会持续失败。
检查地址或端口错误
容器内的 localhost 指向容器自身,不是宿主机,也不是其他容器。
如果要检查同一 Compose 网络中的其他服务,应使用服务名,例如:
http://api:8080/health
不过健康检查通常应该反映当前容器自身是否可服务,不宜把所有外部依赖都设为强制条件,否则数据库短暂抖动可能导致整条服务链都变成不健康。
启动时间不足
应用需要较长时间加载数据或执行迁移时,应设置合理的启动宽限期:
services:
app:
healthcheck:
test: ["CMD", "app-healthcheck"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
各参数作用:
interval:检查间隔;timeout:单次检查超时时间;retries:连续失败次数;start_period:启动宽限期。
不要仅为了让状态变绿而无限增大 retries 或 start_period。如果应用始终无法就绪,仍应修复应用或依赖问题。
CMD 与 CMD-SHELL 使用错误
Exec 形式不会自动处理管道、变量和 ||:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/health"]
需要 shell 语法时应使用:
test:
- CMD-SHELL
- curl -fsS http://127.0.0.1:8080/health || exit 1
前提是镜像中确实存在相应 shell。
七、退出码 137 或 OOMKilled 的处理方法
1. 检查 Docker 是否记录了 OOM
docker inspect -f \
'oom={{.State.OOMKilled}} exit={{.State.ExitCode}} error={{printf "%q" .State.Error}}' \
"$c"
查看容器内存限制:
docker inspect -f \
'memory={{.HostConfig.Memory}} memory_swap={{.HostConfig.MemorySwap}}' \
"$c"
其中 memory 通常以字节表示;值为 0 一般表示未通过该项设置显式内存上限。
实时查看资源使用:
docker stats "$c"
重点关注:
MEM USAGE / LIMIT- 内存是否持续增长;
- 重启前是否逼近限制;
- CPU 是否持续满载;
- 进程数是否异常增加。
由于重启发生很快,人工观察可能来不及。生产环境最好通过监控系统持续采集容器内存、工作集、重启次数和 OOM 事件。
2. 检查宿主机内核日志
即使 .State.OOMKilled 没有给出明确结果,也要检查宿主机是否发生系统级 OOM。
使用 systemd 的 Linux:
sudo journalctl -k --since "1 hour ago" |
grep -Ei 'oom|out of memory|killed process'
或者:
sudo dmesg -T |
grep -Ei 'oom|out of memory|killed process'
可能看到:
Out of memory: Killed process ...
Memory cgroup out of memory: Killed process ...
需要区分:
- 容器达到 cgroup 内存限制:通常只影响该容器或其中的进程;
- 宿主机整体内存耗尽:可能随机杀死某个高内存进程,影响范围更大;
- 应用主动申请超大内存后崩溃:日志中可能出现运行时自己的内存错误。
3. 不要只靠提高内存限制
临时提高限制可用于验证,但不是最终修复方案:
docker update --memory 2g "$c"
使用 Compose 时,应写入服务配置,例如:
services:
app:
mem_limit: 2g
随后验证最终配置:
docker compose config
docker compose up -d
更重要的是定位内存为什么增长:
- JVM 最大堆设置是否接近甚至超过容器限制;
- Node.js 堆上限是否合理;
- PHP-FPM、Gunicorn、数据库等进程数是否过多;
- 单次请求是否读取了超大文件;
- 是否存在缓存无上限、队列堆积或内存泄漏;
- 应用堆之外是否还有直接内存、线程栈、共享内存和本地库开销。
例如,容器限制为 1 GiB 时,不应直接把应用堆也设置为 1 GiB。还必须为运行时、线程栈、本地内存和系统库保留空间。
八、日志正常但仍然退出时,还要检查这些项目
1. 环境变量与配置文件
查看容器环境变量:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$c"
注意输出中可能包含密码、令牌和连接串,不要直接粘贴到工单或公开聊天中。
重点检查:
- 必填变量是否为空;
- 变量名是否拼错;
- 布尔值和数字格式是否正确;
- 数据库地址是否仍写成
localhost; - 配置文件是否被挂载到错误路径;
- 密钥或证书文件权限是否正确。
2. 外部依赖不可用
常见依赖包括:
- 数据库;
- Redis;
- 消息队列;
- DNS;
- 对象存储;
- 第三方 API;
- 证书和时间同步服务。
典型错误是把其他容器写成:
127.0.0.1:3306
在容器中,127.0.0.1 只表示当前容器。Compose 网络内通常应使用服务名:
mysql:3306
depends_on 可以帮助控制启动顺序,但不能保证依赖服务永远可用。应用仍应实现连接重试、超时和退避机制。
3. 端口或 Unix Socket 冲突
如果日志出现:
address already in use
检查应用是否重复启动多个实例,或者旧进程、PID 文件、Socket 文件未清理。
宿主机端口映射冲突通常会让容器启动直接失败,可查看:
docker ps -a
docker inspect -f '{{printf "%q" .State.Error}}' "$c"
4. 磁盘空间或 inode 用尽
df -h
df -i
docker system df
磁盘空间不足可能导致:
- 应用无法写日志;
- 数据库无法写入;
- 临时文件创建失败;
- Docker 无法创建容器层;
- 日志驱动异常。
不要在未确认影响范围时直接执行:
docker system prune -a
该操作可能删除仍有用途的未运行容器、网络、构建缓存和镜像。应先使用 docker system df 确认占用,再有针对性地清理。
5. 文件权限或只读文件系统
检查挂载及只读设置:
docker inspect "$c" | jq '.[0] | {
ReadonlyRootfs: .HostConfig.ReadonlyRootfs,
Mounts: .Mounts
}'
还要确认容器内运行用户:
docker inspect -f 'user={{printf "%q" .Config.User}}' "$c"
应用以非 root 用户运行时,挂载目录必须允许对应 UID/GID 读写。不要为了快速解决问题就长期使用 chmod 777 或直接改为 root,应修正目录属主和最小必要权限。
九、容器重启太快,无法进入内部怎么办
docker exec 只能对正在运行的容器使用。对于启动后立即退出的容器,可以基于同一镜像启动临时调试容器,并覆盖入口:
docker run --rm -it \
--entrypoint /bin/sh \
<镜像名>
前提是镜像中存在 /bin/sh。如果没有 shell,可以:
- 使用镜像自带的调试命令;
- 重新构建临时调试镜像;
- 使用相同网络和必要挂载启动专门的工具容器;
- 从停止的容器中复制文件进行分析。
例如复制配置或崩溃文件:
docker cp "$c":/app/logs ./container-logs
查看容器可写层发生过哪些变化:
docker diff "$c"
输出标识包括:
A:新增;C:修改;D:删除。
启动调试容器时不要无条件复制生产环境的全部密钥、数据卷和网络权限。应仅添加定位问题所需的最小配置。
十、修复后的验证标准
修改配置或镜像后,不要只看到一次 Up 就认为问题已经解决。至少完成以下验证。
1. 确认状态稳定
docker ps --filter name="$c"
等待一段时间后再次检查:
docker inspect -f \
'status={{.State.Status}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}} restarts={{.RestartCount}} started={{.State.StartedAt}}' \
"$c"
2. 确认重启次数不再增加
连续执行两次:
docker inspect -f '{{.RestartCount}}' "$c"
如果容器被重新创建,应同时记录新容器 ID:
docker inspect -f 'id={{.Id}} created={{.Created}} restarts={{.RestartCount}}' "$c"
3. 检查近期日志
docker logs --since 10m --timestamps "$c"
确认没有重复的初始化、连接失败、迁移失败或内存告警。
4. 检查健康状态
docker inspect -f \
'{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' \
"$c"
5. 进行实际业务验证
健康检查成功不代表所有功能正常,还应验证:
- 核心接口是否返回正确结果;
- 数据库读写是否正常;
- 消息是否能够消费;
- 重启后数据是否保留;
- 优雅停止是否生效;
- 内存是否在合理区间内保持稳定。
十一、快速判断路径
遇到 Docker 容器一直重启时,可以按下面的顺序快速定位:
-
退出码是 0
检查是否配置了always或unless-stopped,以及该程序是否本来就是一次性任务。 -
退出码是 1 或 2
优先查看应用日志、配置、启动参数和外部依赖。 -
退出码是 126 或 127
检查入口命令、文件路径、执行权限、换行符、PATH 和卷挂载覆盖。 -
退出码是 137
同时检查OOMKilled、Dockeroom事件、内核日志和人工强制终止记录。 -
退出码是 139
检查原生程序崩溃、动态库、镜像架构,并保留 core dump 进行分析。 -
状态为 unhealthy,但进程仍在运行
检查 Healthcheck 命令、工具是否存在、端口、超时和启动宽限期;不要默认认为 Docker 会自动重启。 -
没有应用日志
检查日志驱动、应用日志文件、Docker 事件以及宿主机内核和 Docker 服务日志。 -
修复后仍周期性复发
增加持续监控,重点观察内存、磁盘、依赖延迟、重启次数和 OOM 事件,不要只依赖某一次人工检查。
Docker 的 Restarting 只是结果,不是根因。最有价值的证据通常是最近一次退出码、退出前日志、Docker 事件、Healthcheck 输出以及宿主机内核记录。按照这一顺序保留现场并逐层缩小范围,通常可以避免反复重建容器却始终找不到真正问题。
本文主题:Docker 容器一直 Restarting 怎么排查:日志、退出码、Healthcheck 与 OOM 定位
引用出处:https://isoziyuan.com/p/100108/(作者:Isoziyuan · 发布于爱搜资源网)
登录后参与讨论
注册或登录账户,即可查看并发表文章评论。