网页能打开,Docker 却一直 unhealthy:从检查输出找原因
读取 Health.Log 的退出码与输出,按工具缺失、端口、HTTP 响应、变量展开和超时排查,避免用关闭检查掩盖问题。
网页能打开,容器也显示 Up,但状态一直是 unhealthy。这时先查看健康检查最近一次的退出码和输出。浏览器访问成功,只说明你访问的入口可用;Docker 的健康状态由容器内部那条检查命令决定,检查路径、工具、权限或超时不对,都会出现两边结果不一致。
看 Health.Log,比反复访问网页更有用
以下命令只读取容器状态;把 web 换成你的服务名:
docker compose ps
docker inspect --format '{{json .Config.Healthcheck}}' \
"$(docker compose ps -q web)"
docker inspect --format '{{json .State.Health}}' \
"$(docker compose ps -q web)"
第一条看运行状态,第二条看实际检查配置,第三条看 Status、FailingStreak 和最近的 Log。若没有 Health 数据,要先确认选中的容器是否正确,以及镜像或 Compose 是否真的配置了检查。镜像自带的检查也可能被 Compose 覆盖。
健康检查的退出码 0 表示成功,1 表示失败,2 是保留值,不应用作自定义状态。失败日志里的输出往往比 unhealthy 这个标签更能说明问题:没有 curl、找不到可执行文件、连接被拒绝、HTTP 错误,处理办法都不同。
按检查输出选择下一步
| 最近检查的现象 | 优先检查的位置 | 处理方向 |
|---|---|---|
curl: not found 或找不到程序 |
最终运行镜像,而非构建阶段镜像 | 在运行镜像安装检查工具,或改用已有运行时 |
| 连接被拒绝 | 容器内监听地址、端口、启动时间 | 使用容器内端口,确认服务已监听 |
| HTTP 404、401 或重定向 | 检查 URL、认证要求和协议 | 选择明确可用的健康端点,不机械沿用首页 |
| 命令在超时之前未结束 | 端点响应时间及依赖调用 | 分清服务变慢与检查时间设置过短 |
| 配置中的变量变成空字符串 | Compose 展开与容器环境 | 检查 ${VAR}、$$VAR 和 shell 形式 |
按最近的检查输出选择分支,不能仅凭 unhealthy 确定根因。检查命令在容器内执行,localhost 指当前容器;例如宿主机映射 8080:3000,检查通常访问容器内的 3000,而不是 8080。若实际采用 host 网络或共享网络命名空间,需要按那种网络模式重新判断。
检查命令是否真的反映应用状态
下面是一个 HTTP 检查示例,前提是运行镜像中已经有 curl,应用监听容器内的 3000,且 /health 无需认证:
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "--show-error", "--max-time", "3", "http://127.0.0.1:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
这不是可以直接用于所有镜像的固定配置。--fail 让 HTTP 400 及以上响应成为失败;否则某些 HTTP 错误也可能让 curl 正常退出。3xx 重定向仍需按应用要求处理,不能只凭这个参数断言返回的是健康内容。对接口语义更严格的应用,应使用能核对期望状态码或响应体的检查程序。
CMD 是直接执行参数,不提供 shell 的变量展开。如果要用 $PORT、管道或 &&,使用 CMD-SHELL,并确认镜像存在对应 shell。Compose 文件中需要留给容器展开的美元符号要写成 $$。
start_period 给启动阶段留出宽限期,期间失败不计入通常的失败次数;它不能修复始终访问错误端口的命令。若服务启动较慢,用启动日志决定宽限期,不要把超时和重试次数无限拉长来掩盖失败。
不要靠关闭检查让状态变绿
关闭健康检查会让 Docker 不再报告这项状态,并不会修复错误端点或丢失的依赖。同样,普通容器变成 unhealthy 不等于 Docker 必然自动重启它;重启策略与进程退出有关,不能把健康检查当成所有环境都适用的自动恢复机制。
修改检查前保存原配置。如果需要添加工具,应重新构建最终运行镜像,避免只在正在运行的容器里临时安装,下一次重建又丢失。部署新的配置或镜像会影响服务,安排维护窗口;失败时恢复原镜像和原检查配置。
什么时候可以认为检查与服务一致了
让容器经历数个正常检查周期,查看 Health.Log 中的新记录确实退出为 0,同时确认实际业务入口能访问。检查端点应符合你的目标:只检查进程是否活着,还是检查数据库等必要依赖是否就绪,这两种设计会产生不同结果。
如果网页可用但检查始终失败,把检查命令、最近输出、实际监听端口和最终镜像信息放在一起看。先解释失败的是哪一步,再决定修改应用还是修改检查。


