### [Docker容器显示unhealthy怎么排查?从Healthcheck输出到依赖服务定位](https://www.jiyueip.com/article/13818) **Published:** 2026-07-31T14:43:12 **Author:** 斑斓助理 **Excerpt:** Docker容器显示unhealthy不等于主进程已经退出。本文从Healthcheck定义、最近输出、容器内复现、启动缓冲和依赖服务逐层定位,并说明修复后的验收方法。 Docker容器显示`unhealthy`,表示健康检查连续失败达到阈值,不等于容器主进程已经退出。正确顺序是先读取Healthcheck定义和最近几次输出,再在同一个容器内复现命令,判断是探针写错、应用未就绪,还是数据库、DNS、证书等依赖真的不可用。 ## 先分清“容器在运行”和“服务可用” `docker ps`中的`Up`只说明容器主进程仍在。镜像或Compose文件配置了健康检查后,Docker还会记录`starting`、`healthy`或`unhealthy`状态。健康命令返回非零、执行超时,并连续达到`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 ``` 若镜像非常精简,可能没有`sh`、`curl`或`ss`。这不是应用故障,但说明探针不能依赖不存在的工具。不要临时在生产容器里安装一堆诊断包后就忘记镜像差异,应把最终检查方式写回镜像或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建站、更新与回滚教程](https://www.jiyueip.com/article/8533)核对持久化、镜像版本和回滚路径,避免把排错变成数据丢失。 ## unhealthy会不会自动重启容器 普通Docker或Compose环境中,健康状态变化本身通常不会让容器自动退出;`restart: unless-stopped`等策略主要处理容器进程退出,不应当作健康检查修复器。某些编排器或外部监控可以基于健康状态采取动作,但需要单独配置和验证。 Compose中的依赖健康条件可以控制依赖服务何时启动,并不等于运行期间自动修复所有故障。若确实需要自动恢复,应先定义安全的重试、熔断、告警和数据一致性边界。 ## 面板环境怎样辅助查看 使用可视化面板管理容器时,可查看极跃圈收录的[1Panel信息页](https://www.jiyueip.com/link/5679)。面板适合查看容器、日志和应用状态,但最终仍要核对实际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、反向代理和真实访问链路。 **Tags:** Docker健康检查, Docker部署, HEALTHCHECK, Linux服务器, VPS, 容器排错, 服务器运维 **Categories:** 网络技术 ---