用心打造
VPS知识分享网站

Docker容器显示unhealthy怎么办?健康检查排查方法

Docker容器明明还在运行,docker ps 却显示 unhealthy,这是很容易让人误判的一类故障。它不代表容器主进程已经停止,只说明镜像或Compose配置里的健康检查连续失败。业务可能真的不可用,也可能只是探测命令、地址或等待时间设置得不合理。

我处理这类问题时,不会先重启容器。重启会清掉部分健康检查历史,还可能让偶发故障暂时消失。更有效的顺序是先保存失败输出,确认Docker实际执行了什么命令,再用完全相同的用户、网络和环境在容器内复现。

容器不健康

先读取健康检查失败记录

先确认容器状态和健康检查详情:

docker ps --filter health=unhealthy
docker inspect CONTAINER --format '{{json .State.Health}}'
docker inspect CONTAINER --format '{{json .Config.Healthcheck}}'

.State.Health.Log 会保存最近几次探测的开始时间、结束时间、退出码和输出。Docker当前只保留有限长度的输出,因此应用返回大段HTML时,关键错误可能被截断。应同时读取容器日志和应用日志。

docker logs --since 30m --timestamps CONTAINER

健康检查退出码0表示成功,1表示失败,2为保留值,不应拿来表达业务状态。容器处于 starting 时探测失败未必立即计入重试次数,要结合 start_period 和Docker版本判断。

确认探测命令在镜像里真的存在

很多镜像更新后移除了 curlwgetbashnc,健康检查仍沿用旧命令,容器就会持续unhealthy。进入容器执行相同命令:

docker exec -it CONTAINER sh
command -v curl
command -v wget

不要只在宿主机测试。健康检查发生在容器内部,宿主机有curl不代表镜像里也有。精简镜像还可能没有 /bin/bash,Compose中写 bash -c 会直接失败。

使用 CMD 数组时不会自动经过Shell,环境变量、管道和 || 也不会按Shell语法解释。需要Shell功能时,应明确使用 CMD-SHELL,同时确认镜像里存在 /bin/sh

healthcheck:
  test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/health || exit 1"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 40s

配置中的地址、端口和路径都应替换成真实值,不能直接复制示例投入生产。

检查localhost、端口与监听地址

容器里的 127.0.0.1 只指向容器自身。应用监听在另一个容器时,健康检查不能用localhost访问。先查看监听:

docker exec CONTAINER sh -c 'ss -lntp || netstat -lntp'
docker port CONTAINER
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}'

健康检查在容器内执行时,通常应访问容器内部端口,而不是宿主机映射端口。例如应用在容器内监听8080,宿主机映射为18080,探测仍应请求8080。

应用只监听特定容器IP,而检查请求127.0.0.1,也会失败。反过来,应用只监听localhost时,其他容器无法访问,健康检查却可能成功。因此健康检查成功只能证明它覆盖的那条路径正常,不等于外部访问链路完整。

区分启动慢与运行中故障

数据库迁移、缓存预热、模型加载和大目录扫描会让应用启动时间超过默认探测容忍范围。查看容器启动时间与首次成功时间:

docker inspect CONTAINER --format '{{.State.StartedAt}}'
docker events --since 30m --filter container=CONTAINER

启动阶段连续失败,随后稳定健康,通常应调整 start_period,而不是简单增加 retriesstart_period 给应用初始化留出时间;interval 控制常规探测间隔;timeout 是单次探测最长时间;retries 是连续失败次数。

start_interval 只在较新的Docker Engine中可用。把新参数写进旧环境可能导致配置不被接受,部署前应核对版本并执行 docker compose config

检查探测接口本身是否合理

健康检查接口应快速、稳定、无副作用。直接请求首页可能触发模板渲染、数据库查询、第三方接口和大量静态资源逻辑,任何一环变慢都会把容器标成unhealthy。

更合理的做法是区分存活与就绪。存活检查只确认进程没有陷入不可恢复状态;就绪检查再判断是否能接收流量。Docker原生只有一套健康状态,编排平台对两者的支持方式不同,不能把Kubernetes配置原样套到Docker Compose。

探测接口返回200也未必代表依赖正常,返回非200也可能只是认证、Host头或HTTPS证书不匹配。用健康检查相同的命令完整复现:

docker exec CONTAINER curl -v --max-time 5 http://127.0.0.1:8080/health

关注DNS、连接、TLS、首字节和HTTP状态,不要只看最后一行。

排查依赖服务和资源压力

应用进程仍在,但数据库、Redis、DNS或磁盘不可用时,健康检查可能失败。先检查依赖连接和宿主机资源:

docker stats --no-stream
docker inspect CONTAINER --format '{{json .HostConfig}}'
df -h
df -i

CPU配额太低、内存接近限制或磁盘I/O拥堵,会让5秒内本可完成的探测超时。此时把 timeout 改成60秒只会掩盖性能问题。应把探测耗时与业务接口延迟、容器限制和宿主机负载放在同一时间线中。

健康检查依赖外部公共接口也不稳妥。外部网络短暂波动会让本地容器退出服务,甚至引发编排系统反复替换实例。优先探测应用自身和必要的本地依赖。

检查Compose覆盖与镜像继承

镜像可以在Dockerfile中声明 HEALTHCHECK,Compose也可以覆盖或禁用。先看最终结果:

docker compose config
docker inspect IMAGE:TAG --format '{{json .Config.Healthcheck}}'
docker inspect CONTAINER --format '{{json .Config.Healthcheck}}'

基础镜像升级后可能新增健康检查,业务镜像没有注意就继承了不适用的路径。一个Dockerfile中只有最后一条 HEALTHCHECK 生效。Compose的覆盖值也可能因为环境变量为空而生成错误地址。

当生产环境变量过多,难以分清镜像问题还是部署问题,可以在 萤光云 创建同版本Docker的隔离环境,或在按小时计费的 LightNode 上做最小镜像对照。只复制脱敏后的Compose结构和空数据目录,不要导入生产密钥。

修改后怎样验收

修改Dockerfile需要重新构建镜像,修改Compose健康检查通常需要重新创建容器。生产环境先处理单个非关键实例:

docker compose config
docker compose up -d --no-deps --force-recreate SERVICE
docker inspect CONTAINER --format '{{json .State.Health}}'
docker events --since 10m --filter container=CONTAINER

至少观察多个完整探测周期,并覆盖启动、稳定运行、依赖短暂失败和恢复。健康状态恢复后,还要验证真实入口流量,而不是只看容器内localhost。

合格的修复是探测命令能准确反映业务可用性、失败输出可解释,并且容器重建后仍能稳定从starting进入healthy。

常见问题

unhealthy会让Docker自动重启容器吗?

Docker Engine本身通常不会仅因unhealthy自动重启容器。是否替换或重启取决于Compose之外的编排、监控或自愈策略。

可以直接删除健康检查吗?

可以用于短暂对照,但不应把隐藏故障当成修复。先确认探测设计不合理,再调整或禁用,并为业务补上其他可观测性。

健康检查越频繁越好吗?

不是。过于频繁会增加应用和依赖负载,也更容易把瞬时抖动放大成状态变化。间隔、超时和重试应符合恢复目标与应用响应时间。

赞(0)
未经允许不得转载;国外VPS测评网 » Docker容器显示unhealthy怎么办?健康检查排查方法
分享到