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: 40sCMD形式直接执行程序,CMD-SHELL会经过shell解释管道、重定向和变量。选择错误会造成引号或变量展开异常。不要使用|| true强行返回成功,这会让健康状态失去意义。
第五步:沿依赖链找真正的失败点
应用进程存在但健康接口失败,常见根因包括数据库迁移未完成、密码或连接串错误、数据卷权限不足、磁盘只读、容器DNS异常,以及上游证书过期。应把健康输出时间与应用、数据库和宿主机日志对齐。
如果准备重新部署,先确认数据卷和备份。可参考Docker Compose建站、更新与回滚教程核对持久化、镜像版本和回滚路径,避免把排错变成数据丢失。
unhealthy会不会自动重启容器
普通Docker或Compose环境中,健康状态变化本身通常不会让容器自动退出;restart: unless-stopped等策略主要处理容器进程退出,不应当作健康检查修复器。某些编排器或外部监控可以基于健康状态采取动作,但需要单独配置和验证。
Compose中的依赖健康条件可以控制依赖服务何时启动,并不等于运行期间自动修复所有故障。若确实需要自动恢复,应先定义安全的重试、熔断、告警和数据一致性边界。
面板环境怎样辅助查看
使用可视化面板管理容器时,可查看极跃圈收录的1Panel信息页。面板适合查看容器、日志和应用状态,但最终仍要核对实际Compose配置、容器内命令与数据卷,不能只凭绿色或红色图标判断根因。
修复后的验收顺序
- 连续观察多个检查周期,状态从
starting转为healthy。 - 健康日志退出码为0,输出不再包含超时、认证或解析错误。
- 从容器内部、宿主机和外部入口分别验证对应服务层。
- 重启容器后再次通过,证明启动缓冲和依赖顺序合理。
- 检查告警与业务请求,确认不是只把探针改成了永远成功。
docker events --filter event=health_status
docker inspect --format '{{.State.Status}} {{.State.Health.Status}}' app常见问题
网页能打开,为什么容器还是unhealthy?
网页可能经过反向代理或缓存,而健康检查在容器内请求了另一个端口、路径或协议。读取实际探针并在容器内复现,才能判断两条链路的差异。
把retries调大能解决吗?
只能容忍更长的连续失败,不能修复错误端口、缺失命令或依赖故障。启动确实较慢时优先评估start_period,并保留合理告警速度。
可以直接关闭Healthcheck吗?
临时关闭可用于确认状态是否由错误探针造成,但生产环境应修正检查逻辑。完全关闭后,主进程存活但服务失效的情况将更难被发现。
健康检查应该访问完整首页吗?
通常不建议。完整首页依赖项多、变化大,容易误报。更合适的是轻量内部端点,并另用外部监控验证域名、TLS、反向代理和真实访问链路。






