/livez和/readyz必须分离:/livez仅检查进程本地状态(如goroutine数、server是否accept),响应≤1秒、无日志、禁网络I/O;/readyz异步缓存依赖探测结果,仅读快照,返回200或503,且二者均须绕过中间件、设正确Content-Type。

必须暴露两个独立 HTTP 端点:/livez 和 /readyz,不能共用一个 handler,也不能只写 /health —— 否则滚动更新卡死、Pod 反复重启、流量误入故障实例都是大概率事件。
为什么 /livez 绝对不能查数据库或 Redis
/livez 的唯一职责是回答:“这个 Go 进程自己还活着吗?” 它不是业务连通性检查器。一旦在里面调用 db.PingContext() 或 redis.Ping(),DB 网络抖动 1 秒就会触发 livenessProbe 失败,Kubernetes 立即杀 Pod 并重建,新 Pod 启动后照样连不上 DB,陷入无限 CrashLoopBackOff。
- 只检查本地状态:比如
http.Server是否仍在Accept()、runtime.NumGoroutine()是否突增(建议阈值 ≤ 5000)、关键信号 channel 是否阻塞 - 禁止任何网络 I/O、磁盘读写、
time.Sleep、context.WithTimeout—— 本意就是“快”,耗时超 200ms 就该怀疑进程本身出问题 - 响应必须 ≤ 1 秒,且不记录日志(避免高频探针撑爆日志卷)
/readyz 怎么安全检查依赖而不打爆连接池
/readyz 决定“能不能收流量”,所以必须验证 DB、Redis、gRPC 等真实连通性,但绝不能每次请求都新建连接 —— 每 2 秒一次探针,db.Ping() 可能瞬间建立数百空闲连接,压垮数据库。
- 用
time.Ticker启后台 goroutine,每 5 秒执行一次全量依赖探测(不是每次/readyz请求都查) - 结果缓存在
sync.RWMutex保护的结构体里,/readyzhandler 只读快照,不发起新连接 - 每个依赖必须并行探测、独立超时:
db.PingContext(ctx, 2*time.Second)、redis.Ping(ctx).Err()、grpcConn.WaitForStateChange(ctx, connectivity.Ready) - 快速过滤优先:
db.Stats().OpenConnections > 0为 false 直接返回 503,不进后续查询
路由注册和中间件怎么绕开才不翻车
健康端点一旦被日志、鉴权、CORS 或 panic recover 中间件拦截,就可能因中间件延迟、header 注入或 panic 导致探针误判 —— Kubernetes 不关心你有没有 X-Request-ID,只看状态码和响应是否及时。
- 用标准
net/http时,直接http.HandleFunc("/livez", livezHandler),不要挂任何中间件 - 用 Gin 时,必须在
r.Use(...)之前注册:r.GET("/readyz", readyzHandler);或者显式跳过:r.NoRoute(func(c *gin.Context) { if c.Request.URL.Path == "/readyz" { readyzHandler(c); return } }) - 用 Echo 时,加
.SkipMiddleware = true:e.GET("/livez", livezHandler).SkipMiddleware = true - 必须设置
w.Header().Set("Content-Type", "application/json; charset=utf-8"),否则某些 Ingress(如 Traefik)会截断响应
最易被忽略的一点:两个端点返回的状态码必须严格区分——/livez 和 /readyz 都只能返回 200 或 503,且 503 仅用于 /readyz 表示“暂时不可用”;/livez 返回 503 就等于主动要求 Kubernetes 杀掉自己。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











