必须分离/livez和/readyz:/livez仅检查进程存活(HTTP accept、goroutine数、内存),禁查DB以防误杀;/readyz并行检查依赖并缓存结果,超时控制+显式503状态码,路径须与K8s配置严格一致。

健康检查接口必须分离 /livez 和 /readyz,否则 Kubernetes 会误判、雪崩重启、流量打到半死节点上。
为什么 /livez 不能查数据库
查数据库会让 /livez 变成“假活探测器”:DB 一抖动,livenessProbe 就失败,Kubernetes 立刻 kill + restart 容器,而此时进程明明活着,只是依赖暂时不可用。这会触发雪崩式重启,尤其在 GC STW 或日志刷盘卡顿几秒时更明显。
-
/livez只该确认 HTTP server 还在 accept 连接、goroutine 没泄漏、堆没爆 —— 例如runtime.NumGoroutine()> 1000 或MemStats.HeapInuse> 500MB 就返回 500 - 绝对不调
db.PingContext、不读文件、不发 HTTP 请求 - 响应必须 ≤1 秒:禁用
time.Sleep、阻塞 channel、无中间件(绕过 Gin/Echo 的路由栈) - HTTP server 初始化时务必设
ReadTimeout: 5 * time.Second和WriteTimeout: 10 * time.Second,防单次卡顿被探针误杀
如何安全写 /readyz 避免打爆 DB 连接池
高频轮询下每次执行 db.Ping() 是高危操作:底层可能新建连接,连接数飙升、超时堆积,甚至拖垮数据库。真正该复用的是连接池当前状态 + 轻量 SQL。
- 先快速过滤:
db.Stats().OpenConnections > 0,为 0 直接返回 503 - 再执行
db.QueryRowContext(ctx, "SELECT 1"),ctx必须带超时(如2 * time.Second),绝不用context.Background() - 所有依赖检查(DB、Redis、gRPC)必须并行执行,且共用同一个
ctx,任一失败就整体返回 503 - 别在 handler 里实时探测——启动一个后台 goroutine 每 5 秒检查一次,结果缓存在内存中,
/readyz只读快照
http.Error 默认返回 500,但 readiness 需要 503
Kubernetes readiness 探针只看 HTTP 状态码,不解析响应体。返回 200 但 DB 已断连,等于主动把流量引向故障节点;而误用 http.Error(w, "msg", http.StatusServiceUnavailable) 却没设状态码,Go 会默认用 500,导致探针判定失败。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须显式写
w.WriteHeader(http.StatusServiceUnavailable)或确保http.Error第三个参数是目标码(如http.StatusServiceUnavailable) - 路径名无所谓是
/health、/readyz还是/is-alive,但 K8s YAML 中的readinessProbe.httpGet.path必须与http.HandleFunc注册路径严格一致 - 返回体可以是空字符串、
{"status":"ok"}或详细 JSON,只要状态码对,效果完全一样
为什么独立起 http.Server 更可靠
复用主服务的 mux 很容易被中间件干扰:鉴权拦截、CORS 头、日志刷盘、panic recover,都会拖慢响应或改变行为。Docker/Kubernetes 只需要确定、低延迟、无副作用的响应。
- 单独起一个
http.Server,绑定固定端口(如:8081),只注册/livez和/readyz - 用
http.DefaultServeMux或自定义http.ServeMux,绕过 Gin/Echo 的整个中间件链 - 容器收到
SIGTERM时,必须先关闭这个健康 server,再关主服务,避免探针还在调用却已无响应
最易被忽略的是:所有检查项必须并行执行、受统一 context 控制,且每个依赖的超时时间不能超过探针 timeoutSeconds 的 60% —— 否则还没等你返回,探针自己就超时了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










