Docker容器显示unhealthy怎么排查?从Healthcheck输出到依赖服务定位

容器进程存活不代表应用可用,先读探针输出,再复现命令和检查依赖链
发布于
3

Docker容器显示unhealthy,表示健康检查连续失败达到阈值,不等于容器主进程已经退出。正确顺序是先读取Healthcheck定义和最近几次输出,再在同一个容器内复现命令,判断是探针写错、应用未就绪,还是数据库、DNS、证书等依赖真的不可用。

先分清“容器在运行”和“服务可用”

docker ps中的Up只说明容器主进程仍在。镜像或Compose文件配置了健康检查后,Docker还会记录startinghealthyunhealthy状态。健康命令返回非零、执行超时,并连续达到retries次数后,状态才会变为不健康。

不要看到unhealthy就立刻重建容器。重建会覆盖最近的健康输出,若故障来自数据库未初始化、只读文件系统或错误环境变量,新容器还会重复失败。

第一步:读取实际生效的健康检查

docker inspect --format '{{json .Config.Healthcheck}}' app
docker inspect --format '{{json .State.Health}}' app
docker inspect --format '{{range .State.Health.Log}}{{.End}} exit={{.ExitCode}} {{printf "%q" .Output}}{{println}}{{end}}' app

app替换为容器名。重点记录检查命令、间隔、超时、重试次数、启动缓冲期,以及每次失败的退出码和输出。镜像自带的Healthcheck可能被Compose覆盖,因此不能只看Dockerfile或网上示例。

输出特征 常见含义 下一步
exit=127 探针所需命令不存在 确认镜像内是否有curl、wget或shell
连接被拒绝 应用尚未监听或端口写错 核对进程、监听地址和容器内端口
超时 应用阻塞、依赖慢或阈值过短 对照应用日志与依赖响应时间
401或403 健康路径需要认证或被访问规则拦截 改用只返回最小状态的内部端点
域名解析失败 容器DNS或上游名称异常 检查容器内解析与Compose网络
证书错误 探针访问HTTPS时信任链或域名不匹配 核对证书、主机名和容器CA

第二步:在容器里原样复现

宿主机能访问接口,不代表容器内部也能。进入同一个网络命名空间,运行与Healthcheck相同的命令:

docker exec app sh -lc 'command -v curl; curl -fsS http://127.0.0.1:8080/health'
docker exec app sh -lc 'ss -lnt 2>/dev/null || netstat -lnt 2>/dev/null'
docker logs --since 15m app

若镜像非常精简,可能没有shcurlss。这不是应用故障,但说明探针不能依赖不存在的工具。不要临时在生产容器里安装一堆诊断包后就忘记镜像差异,应把最终检查方式写回镜像或Compose配置。

第三步:检查地址、端口和路径是不是同一层

健康检查在容器内部执行时,127.0.0.1指向当前容器,而不是宿主机,也不是另一个服务。数据库或缓存应使用Compose服务名;反向代理对外使用的域名、路径和认证规则,也未必适合内部健康探针。

  • 应用监听8080,探针却请求宿主机映射端口18080
  • 服务只监听特定容器地址,探针固定请求localhost
  • 探针请求完整业务页面,页面又依赖数据库、对象存储和外部接口。
  • 反向代理路径为/app/,容器内部实际健康路径为/health
  • 健康接口需要登录,未认证请求始终返回401或跳转。

健康端点应回答“当前实例能否接收基本流量”,不要把所有外部依赖都塞进一次检查。否则第三方接口抖动会把本地应用标记为故障。

第四步:给启动过程合理缓冲

数据库迁移、缓存预热和Java应用启动可能超过普通Web进程。此时应先测量正常启动耗时,再设置start_period,而不是无限增加retries。下面的数值只是示例:

services:
  app:
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/health >/dev/null || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 40s

CMD形式直接执行程序,CMD-SHELL会经过shell解释管道、重定向和变量。选择错误会造成引号或变量展开异常。不要使用|| true强行返回成功,这会让健康状态失去意义。

第五步:沿依赖链找真正的失败点

应用进程存在但健康接口失败,常见根因包括数据库迁移未完成、密码或连接串错误、数据卷权限不足、磁盘只读、容器DNS异常,以及上游证书过期。应把健康输出时间与应用、数据库和宿主机日志对齐。

如果准备重新部署,先确认数据卷和备份。可参考Docker Compose建站、更新与回滚教程核对持久化、镜像版本和回滚路径,避免把排错变成数据丢失。

unhealthy会不会自动重启容器

普通Docker或Compose环境中,健康状态变化本身通常不会让容器自动退出;restart: unless-stopped等策略主要处理容器进程退出,不应当作健康检查修复器。某些编排器或外部监控可以基于健康状态采取动作,但需要单独配置和验证。

Compose中的依赖健康条件可以控制依赖服务何时启动,并不等于运行期间自动修复所有故障。若确实需要自动恢复,应先定义安全的重试、熔断、告警和数据一致性边界。

面板环境怎样辅助查看

使用可视化面板管理容器时,可查看极跃圈收录的1Panel信息页。面板适合查看容器、日志和应用状态,但最终仍要核对实际Compose配置、容器内命令与数据卷,不能只凭绿色或红色图标判断根因。

修复后的验收顺序

  1. 连续观察多个检查周期,状态从starting转为healthy
  2. 健康日志退出码为0,输出不再包含超时、认证或解析错误。
  3. 从容器内部、宿主机和外部入口分别验证对应服务层。
  4. 重启容器后再次通过,证明启动缓冲和依赖顺序合理。
  5. 检查告警与业务请求,确认不是只把探针改成了永远成功。
docker events --filter event=health_status
docker inspect --format '{{.State.Status}} {{.State.Health.Status}}' app

常见问题

网页能打开,为什么容器还是unhealthy?

网页可能经过反向代理或缓存,而健康检查在容器内请求了另一个端口、路径或协议。读取实际探针并在容器内复现,才能判断两条链路的差异。

把retries调大能解决吗?

只能容忍更长的连续失败,不能修复错误端口、缺失命令或依赖故障。启动确实较慢时优先评估start_period,并保留合理告警速度。

可以直接关闭Healthcheck吗?

临时关闭可用于确认状态是否由错误探针造成,但生产环境应修正检查逻辑。完全关闭后,主进程存活但服务失效的情况将更难被发现。

健康检查应该访问完整首页吗?

通常不建议。完整首页依赖项多、变化大,容易误报。更合适的是轻量内部端点,并另用外部监控验证域名、TLS、反向代理和真实访问链路。

常见问题(FAQ)

网页能打开,为什么容器还是unhealthy?
网页可能经过反向代理或缓存,而健康检查请求的是容器内部另一个端口、路径或协议,需要读取实际探针并在容器内复现。
把retries调大能解决吗?
只能容忍更长的连续失败,不能修复错误端口、缺失命令或依赖故障;启动较慢时应评估start_period。
可以直接关闭Healthcheck吗?
可临时关闭以确认是否为错误探针,但生产环境应修正检查逻辑,否则主进程存活而服务失效的情况更难发现。
健康检查应该访问完整首页吗?
通常不建议。完整首页依赖项多且容易误报,更适合使用轻量内部端点,并另用外部监控验证域名和TLS。

本文由作者原创/授权发布于极跃圈(jiyueip.com)未经许可,禁止转载。题图来自Unsplash,基于CC0协议。

声明:极跃圈(JIYUEIP.com)内网友所发表的所有内容及言论仅代表其本人,并不反映任何极跃圈(JIYUEIP.com)之意见及观点。

0 讨论
热门最新
总结
暂无总结
0 / 600