Docker HEALTHCHECK指令仅执行命令并依据退出码判断状态,真正决定健康与否的是Go程序中独立HTTP服务暴露的/health与/ready端点逻辑,需分离语义、避免中间件、匹配端口、设置合理start-period。

HEALTHCHECK 指令本身不感知 Go 语言,它只执行容器内命令并看退出码。真正决定健康与否的,是你在 Go 程序里暴露的 HTTP 端点逻辑,以及 Docker 如何调用它——配置错一个参数、复用主服务 mux、漏掉依赖检查,docker inspect 就永远卡在 starting 或反复变 unhealthy。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
Go 程序里别把 /health 挂在主 http.ServeMux 上
Docker 的HEALTHCHECK 是从宿主机 namespace 发起 curl 请求,它不经过你的 Gin/Echo 中间件、不走反向代理、也不受日志或鉴权拦截。如果你把 /health 注册到主服务的 http.ServeMux,而主服务又启用了 JWT 鉴权或 CORS 中间件,那 Docker 的请求会直接 401 或被预检拦住,返回非 200 → 退出码非 0 → 标记为 unhealthy。
实操建议:
- 单独起一个轻量 http.Server,绑定固定地址如 :8081
- 用 http.NewServeMux() 创建独立路由,只注册 /health 和 /ready
- 启动时用 go http.ListenAndServe(...),别用阻塞式的 http.ListenAndServe()
- 示例关键片段:
healthMux := http.NewServeMux()
healthMux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
go func() {
log.Println("Health server starting on :8081")
http.ListenAndServe(":8081", healthMux)
}()Dockerfile 里 curl 命令必须匹配 Go 端点地址和端口
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1 这种写法在 Go 程序监听 :8081 时必然失败。Docker 的 curl 是在容器内部执行,localhost 指向容器自身,但端口必须和 Go 健康服务实际监听的一致。
常见错误现象:
- docker inspect 显示状态长期为 starting(首次检查就超时)
- 日志里看不到健康服务启动日志(说明 go http.ListenAndServe 没跑起来,或被主线程阻塞)
实操建议:
- 确保 HEALTHCHECK 中的端口与 Go 健康服务监听端口完全一致(比如 :8081)
- Alpine 镜像默认没装 curl,要么 apk add --no-cache curl,要么改用 wget 或纯 Bash 方式(如 timeout 3 bash -c 'echo > /dev/tcp/localhost/8081' 2>/dev/null)
- 不要用 http://127.0.0.1,某些容器网络栈下它比 localhost 更慢或不可解析
/health 和 /ready 必须语义分离,不能共用一个 handler
Kubernetes 的livenessProbe 和 readinessProbe 是两个独立信号,但 Docker 的 HEALTHCHECK 只能配一个命令。所以你得靠 Go 程序内部区分:
- /health 应极轻量:只确认进程活着、HTTP 服务可 accept 连接、无 panic,**不查 DB、不连 Redis、不发 gRPC**
- /ready 必须查关键依赖:比如用带 context 超时的 db.QueryRowContext(ctx, "SELECT 1"),而不是 db.Ping();Redis 用 client.Ping(ctx).Result();下游服务用短 timeout 的 gRPC Check 方法
实操建议:
- 在健康 server 的 http.ServeMux 里明确分开两个路径
- /ready 中任一依赖失败,立刻返回 503 Service Unavailable + 文本说明(便于 debug)
- 不要在 /ready 里做耗时操作,Kubernetes 默认 timeoutSeconds: 1,Docker --timeout=5s,超时即判失败
docker-compose.yml 中 start-period 必须大于 Go 应用冷启动时间
Go 程序启动快,但如果你初始化了 gRPC client、加载了大配置文件、或做了 sync.Once 初始化 DB 连接池,前几秒可能还无法响应 HTTP。Docker 默认--start-period=0s,意味着容器一启动就开查,第一次就失败 → 计入重试 → 很快变 unhealthy。
实操建议:
- 在 docker-compose.yml 的 healthcheck 段显式设 start_period: 30s(保守起见)
- 如果用了 GORM + PostgreSQL,观察日志里 DB connection established 出现在第几秒,再加 5–10 秒缓冲
- 不要依赖 initialDelaySeconds(那是 Kubernetes 的字段,Docker 不认)
- 错误写法:test: "curl -f http://localhost:8081/health"(字符串格式,Docker 报错)
- 正确写法:test: ["CMD", "curl", "-f", "http://localhost:8081/health"]最常被忽略的是:Go 健康服务没有独立 http.Server 实例,或者 HEALTHCHECK 端口和 Go 监听端口对不上——这两点导致 90% 的 “明明写了健康接口却一直 unhealthy” 问题。其余都是细节微调。

















