liveness端点应检查内存和goroutine异常且不依赖外部服务;readiness需验证DB、Redis等关键依赖并加超时;健康接口须recover panic、绕过中间件、合理配置K8s探针参数。

liveness 端点该返回什么才不算“假活”
很多服务把 /health 写成永远返回 200 OK 的空函数,结果 Kubernetes 以为它活着,其实数据库连不上、goroutine 全卡死。liveness 必须检查进程自身是否还能正常运转,而不是“进程没崩溃”。
- 只检查内存分配是否异常(如
runtime.ReadMemStats中HeapInuse突增且不释放)、goroutine 数是否持续暴涨(runtime.NumGoroutine()超阈值) - 不碰外部依赖:不查 DB、不调其他服务、不读文件——这些属于 readiness 范畴
- 响应必须快:
liveness超时通常设为 1–3 秒,逻辑里不能有time.Sleep或阻塞 channel 操作
示例片段:
func livenessHandler(w http.ResponseWriter, r *http.Request) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
if m.HeapInuse > 500*1024*1024 { // 500MB
http.Error(w, "heap too large", http.StatusInternalServerError)
return
}
if runtime.NumGoroutine() > 1000 {
http.Error(w, "too many goroutines", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
readiness 端点必须验证哪些依赖
readiness 是告诉调度器“我现在能不能收流量”,所以它得反映真实服务能力。Kubernetes 默认用 readiness 探针决定是否把 Pod 加入 Service 的 endpoints 列表,漏检等于把请求打到一个半死的实例上。
- 必须检查关键依赖:DB 连接池
db.PingContext()、Redisclient.Ping()、核心 gRPC client 的健康状态(用conn.GetState()) - 可选检查:本地缓存是否 warm(比如检查
cache.Len() > 100),但别等预热完成再返回 true——这会让启动变慢 - 避免长耗时操作:所有依赖检查加统一 context timeout,比如
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
常见错误是只 check DB,但忘了 Redis 宕了也会导致接口 500;或者用了 db.QueryRow("SELECT 1") 却没处理 rows.Err(),导致永远返回 200。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
HTTP handler 怎么避免 panic 导致整个服务不可用
健康检查 handler 一旦 panic,整个 HTTP server 就挂了,liveness 和 readiness 全失效。Go 的 http.ServeMux 默认不 recover,这点比 Python/Java 更危险。
- 每个 handler 都要包一层 defer-recover,尤其 readiness 里调第三方库时(比如某些 Redis 客户端在连接断开时 panic)
- 不要依赖全局中间件——万一中间件自己 panic,所有路由都崩;健康端点应该独立注册、最小依赖
- 日志里必须记录 panic 堆栈,否则你根本不知道为什么探针突然失败。用
log.Printf("liveness panic: %v\n%v", err, debug.Stack())
推荐写法:
func safeHandler(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in health handler: %v", r)
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
h(w, r)
}
}
// 注册时:
http.HandleFunc("/healthz", safeHandler(livenessHandler))
http.HandleFunc("/readyz", safeHandler(readinessHandler))
如何让 probe 响应不被中间件或反向代理吞掉
有些团队在 Gin/echo 里写健康检查,结果被 JWT 中间件拦住返回 401;或者 Nginx 配置了 location / { proxy_pass ... },但没显式放行 /healthz,导致 404 —— 这类问题线上很难排查。
- 健康端点必须注册在
http.DefaultServeMux或独立的http.ServeMux上,绕过所有业务框架中间件 - 如果用了反向代理,确认其配置允许未鉴权访问:Nginx 要加
location /healthz { proxy_pass http://backend; },不要让它落入通用规则 - Kubernetes 的
initialDelaySeconds得设够:如果你的 service 启动要 5 秒连 DB,probe 就不能设initialDelaySeconds: 3,否则 readiness 一直 false,Pod 永远进不了 endpoints
最容易被忽略的是:Probe 的 periodSeconds 和 handler 实际耗时接近时,会触发并发 probe。多个 readiness 请求同时执行 DB Ping,可能压垮连接池。加个简单 mutex 或用单次检查缓存(带 TTL)更稳妥。

















