/health/live 和 /health/ready 必须分离实现:前者仅检查进程级状态(如 goroutine 数、内存 limit),后者同步探测真实依赖(DB/Redis/gRPC)并返回带错误详情的 503;需复用全局客户端、控制就绪信号、合理配置 K8s 探针参数,并为 gRPC 服务启用 grpc_health_v1。

/health/live 和 /health/ready 必须分开实现,不能共用一个 handler
很多 Go 服务把 /health 当成万能探针入口,K8s 的 livenessProbe 和 readinessProbe 却被配到同一个路径——结果 DB 挂了,liveness 还在返回 200,容器不重启;而流量却照常打入,下游全崩。根本矛盾在于语义混淆:/health/live 只管“进程是否活着”,/health/ready 才管“能不能接请求”。
-
/health/live应只检查 runtime 状态:goroutine 数突增、关键 channel 是否已 close、内存是否逼近 cgroup limit -
/health/ready必须同步探测真实依赖:DB 用db.PingContext(ctx, 1500*time.Millisecond),Redis 用client.Ping(ctx).Err(),gRPC 连接查conn.GetState() == connectivity.Ready - 任一依赖失败,
/health/ready必须返回503 Service Unavailable,且响应体带具体错误项(如{"redis": "timeout"}),不能只写{"status": "down"} - 别在 handler 里 new
redis.Client或sql.DB——复用启动时初始化的全局实例,否则每次探针都建连接,fd 很快耗尽
HTTP server 启动完成前,探针就可能打进来
K8s 的 readinessProbe 默认在容器启动后立刻发起请求,但 Go 的 http.Server 可能刚调完 Listen,TLS 握手、中间件注册、gRPC client warmup 都还没做完。这时返回 200,等于主动把流量引向未就绪的服务。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 显式暴露轻量级端点,比如
/healthz,只检查 listener 是否已Accept,不查任何外部依赖 - 在
main函数中用sync.WaitGroup或atomic控制就绪信号,确保所有初始化完成后再允许/health/ready返回 200 - K8s 配置必须设
initialDelaySeconds: 15(给 DB 连接池 warmup 留时间),timeoutSeconds: 3,periodSeconds: 5,failureThreshold: 3 - 别用中间件拦截
/health路由——中间件 panic 或超时会直接让探针失败
gRPC 服务不能复用 HTTP 探针
如果 Go 服务只暴露 gRPC 接口(比如消息消费者),硬加一个 HTTP /health 是可行的,但更规范的做法是用 grpc_health_v1 实现标准健康服务,并在 Kubernetes Service YAML 中声明 appProtocol: grpc。否则 K8s 会按 HTTP 协议发 GET 请求,gRPC server 直接返回 HTTP 404 或乱码,probe 永远失败。
- HTTP 探针对纯 gRPC 服务只是权宜之计;长期应启用
grpc_health_v1.HealthCheckServer - Service YAML 中漏掉
appProtocol: grpc,K8s 就不会走 gRPC 健康协议,而是当成普通 TCP 端口做连接检查,失去语义 - exec 探针(如
curl -f http://localhost:8080/healthz)仅用于调试,生产环境避免使用——它绕过 HTTP 层,无法反映真实服务状态
最常被忽略的一点:livenessProbe 和 readinessProbe 的超时值必须比各自 handler 的实际耗时短至少 500ms,否则 probe 会先超时失败,而 handler 还在阻塞等待 DB 响应——这会让整个 HTTP server 的 goroutine 池被探针请求占满。

















