Docker API 网关显示 healthy 却不能用?健康检查、就绪与依赖排查
区分容器存活、网关就绪和业务探针,核对健康检查退出码、连续失败阈值与 Compose 依赖顺序,避免把 healthy、数据库接受连接或重启策略误当成完整可用性证明。
容器状态显示 healthy,API 网关仍然返回 502、超时或模型不可用,通常不是 Docker “失灵”,而是健康检查只验证了错误的层。一个命令可能只确认进程还活着,却没有确认网关能连接数据库、读取路由或访问上游。
本文把检查拆成存活、就绪和业务探针三层,适用于 Docker Engine 与 Compose。示例只做离线配置和本地函数验证,没有连接真实网关、模型服务或付费 API。
1. 先分清三种“正常”
存活(liveness)回答“进程是否还在运行”;就绪(readiness)回答“当前实例是否可以接收流量”;业务探针则回答“用一条受控请求能否走完关键链路”。Docker 的 HEALTHCHECK 只能根据你写入容器的命令返回值改变容器健康状态,不会自动理解 API 网关的数据库、路由或上游模型。
因此,healthy 只说明该命令曾检查成功,当前尚未达到连续失败阈值;最新一次检查可能已经失败。要同时看最近检查时间、退出码和连续失败次数。它不能证明:
- 账户、余额和路由配置已经加载;
- 上游 DNS、TLS、权限和模型映射可用;
- 流式响应能正常结束;
- 真实请求不会被限流、超时或重复扣费。
先查看网关备份与恢复演练,确认探针不会把恢复中的数据库或旧配置误判为可用。探针要短、可重复、无副作用,并且不要把 API Key 写入 URL、日志或错误正文。
2. 检查现有 HEALTHCHECK,而不是凭状态猜原因
在目标环境执行只读检查,替换容器名:
docker inspect --type container --format '{{json .State.Health}}' gateway-container
docker inspect --type container --format '{{json .Config.Healthcheck}}' gateway-container
State.Health 用来观察最近的状态、失败次数和输出;Config.Healthcheck 反映容器创建时采用的命令、间隔、超时、重试次数和启动宽限期。没有配置检查时,相关字段可能缺失或为空,这不代表健康。输出可能包含探针打印的错误或秘密,分享前先脱敏。只看 docker ps 的一列状态,会丢掉最有价值的失败上下文。
常见误判包括:
| 现象 | 实际可能检查到的内容 | 没有检查到的内容 |
|---|---|---|
curl 127.0.0.1:8080/health 成功 | Web 进程能接受本地连接 | 数据库、上游和鉴权 |
只运行 test -f config | 文件存在 | 文件可解析、密钥有效、路由可用 |
| 探针带真实 POST | 可能走完整链路 | 可能产生费用、写入记录或触发工具 |
没有 start_period | 容器很快被标记失败 | 启动迁移和缓存加载时间 |
Dockerfile 中的默认检查、Compose 中的服务级覆盖和运行时参数可能来自不同文件。改完配置后要确认新容器真的被重建;只重启旧容器不等于采用了新检查。
3. 设计不会产生副作用的分层探针
第一层可以只检查进程监听;第二层检查网关自身已经加载的只读状态;第三层才在隔离环境用固定、无敏感内容的小请求验证上游。不要把“查询余额”“创建会话”或“发送真实用户提示词”放进定时探针。
以下只是合并到现有服务的 Compose 片段,不是完整部署文件;保留镜像、卷、网络和其他配置。它假设镜像已经包含 Node.js 及你实现并验证过的 /app/healthcheck.js,本节不提供这个脚本。第 5 节的离线分类函数不能直接当作此脚本使用:它没有网络探测,也不设置进程退出码。数值是示例,不是所有部署的推荐值:
services:
gateway:
healthcheck:
test: ["CMD", "node", "/app/healthcheck.js"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
start_period 内的失败通常不计入连续失败阈值;一旦这段时间内检查成功,随后的失败就会计数,它不是无条件等待到期。探针脚本应在成功时退出 0、失败时退出 1;Docker 保留退出码 2,不应主动使用。把简短、脱敏的原因写到标准错误,设置脚本自身的连接和总耗时上限,留出比 Docker timeout 更短的结束余量。不要让健康检查跟随重定向到未知主机,也不要无限等待。
对普通 Docker Engine 容器,unhealthy 是健康状态;restart: always 等重启策略针对进程退出或容器停止,不会仅因健康检查失败就自动重启。摘除流量、重启或告警需要你使用的代理、编排器或监控系统明确配置。不要让外部上游的短暂故障反复触发本地重启;应根据依赖是否影响全部请求,决定标记整个实例未就绪,还是只停用受影响路由。
如果网关依赖数据库或本地模型,优先读取已经存在的只读版本/连接状态接口。宿主机、网关容器和模型容器的地址边界可参考本地模型接入统一网关的排查,但不要把“端口能连通”当成模型生成成功。
4. 用依赖顺序避免“网关先 healthy、上游后启动”
Compose 的 depends_on 可以配合 condition: service_healthy,让依赖服务先达到健康状态;这只影响启动顺序,不会替你定义正确的健康语义。若数据库探针只检查 TCP 端口,网关仍可能在迁移未完成时启动。
下面是合并到已有 PostgreSQL 服务 db 和网关服务的示意片段;不是独立可运行的数据库部署:
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d gateway -t 2 || exit 1"]
interval: 5s
timeout: 3s
retries: 10
gateway:
depends_on:
db:
condition: service_healthy
app 和 gateway 是示例账户与库名,需要替换;镜像要包含 pg_isready 和可运行此命令的 Shell。|| exit 1 把工具的非零退出码统一为 Docker 健康检查的失败码。它确认 PostgreSQL 是否接受连接,不证明这组账号能鉴权或业务表迁移已完成;官方说明,即使用户名、密码或库名不正确,也可能获得服务器状态,错误值还可能产生失败连接日志。[6] 保留实际镜像版本、密码、网络和卷,不做升级或删除。运行中数据库再次失效时,depends_on 不会持续替网关摘除流量;网关仍需重连和报告就绪变化。
正式上线前的环境变量、限流、日志与成本告警,还应按API 正式上线前检查清单逐项验收。健康检查是其中一项证据,不是整份清单的替代品。
5. 用本地函数检查探针结果边界
下面的 Node.js 函数不访问网络,只把探针收集的状态归类。它拒绝空对象、异常退出码和不受信的重定向;返回 ready 也只表示这组字段满足本地规则。
function classifyProbe(result) {
if (result === null || typeof result !== 'object' || Array.isArray(result)) {
return 'invalid-input'
}
if (result.redirected === true) return 'reject-redirect'
if (result.redirected !== false) return 'invalid-redirect-flag'
if (!Number.isInteger(result.exitCode)) return 'missing-exit-code'
if (result.exitCode !== 0) return 'probe-failed'
if (!Number.isFinite(result.elapsedMs) || result.elapsedMs < 0 ||
result.elapsedMs > 5000) return 'timeout-window'
if (result.httpStatus !== 200) return 'unexpected-status'
if (result.body !== 'gateway-ready') return 'unexpected-body'
return 'ready'
}
const samples = [
{ exitCode: 0, httpStatus: 200, elapsedMs: 120, body: 'gateway-ready', redirected: false },
{ exitCode: 1, httpStatus: 200, elapsedMs: 80, body: 'gateway-ready', redirected: false },
{ exitCode: 0, httpStatus: 200, elapsedMs: 80, body: 'gateway-ready', redirected: true }
]
console.log(samples.map(classifyProbe))
正常样例得到三个结果:ready、probe-failed、reject-redirect。gateway-ready 是本文约定的示例正文,不是任何网关都提供的端点;200 和精确正文都需按实际接口契约定义。缺失耗时、NaN、字符串耗时、错误 HTTP 状态和缺失跳转标记都不会判为就绪。即使返回 redirected: false,仍须由请求端禁止跳转,事后标记不能阻止已经发生的转发。这段代码没有模拟 Docker 调度、网络或上游计费;它只验证分类规则。真实探针还要记录检查时间、容器 ID、依赖版本和失败摘要,避免多个副本的状态混在一起。
6. 从 healthy 到可用要做一次完整验收
修改探针后按顺序验收:
- 在隔离环境让进程未监听、数据库未就绪、上游拒绝和正常响应分别出现,确认状态转移符合预期。
- 检查失败次数和重试窗口;若额外配置了自动恢复组件,避免短暂启动抖动导致不必要的重启。
- 让网关返回一个固定、无敏感内容的业务结果,确认响应结束、错误码和日志关联字段都可追踪。
- 在真实部署前确认高频存活/就绪探针不消耗余额、不写入业务数据、不暴露内部错误。独立的真实模型验收可能收费,必须有单独预算、低频调度和停止条件,不能混进容器健康检查;本文未执行此类调用。
- 重建目标服务后重新读取
State.Health与Config.Healthcheck,保存容器 ID 和创建时间。
若页面显示 healthy 而请求仍失败,先把问题定位到进程、网关内部、依赖服务、上游或客户端层;不要反复重启并把偶尔成功当成恢复。健康状态是时间窗口内的一条信号,不能代替请求级日志、账单核对和回滚方案。
7. 官方资料与核对日期
资料核对日期:2026-11-03。以下为 Docker 官方文档;参数含义以安装版本为准。
- Dockerfile HEALTHCHECK:检查命令、退出码、间隔、超时、重试和启动宽限期。
- Compose startup order:
depends_on与service_healthy的启动顺序边界。 - Docker inspect:读取容器状态和创建时配置。
- Start containers automatically:重启策略与健康状态不是同一件事。
- Compose healthcheck:服务级覆盖、CMD 与 CMD-SHELL。
- PostgreSQL pg_isready:服务器状态、退出码与鉴权证明边界。