/livez中调用db.PingContext()等外部依赖会导致liveness探针误判为进程死亡,触发反复重启;正确做法是仅检查进程内状态(如serverRunning标记、goroutine数、内存使用),响应须≤200ms。

/livez里调db.PingContext()就是重启触发器
只要/livez路径里出现任何外部依赖调用,Kubernetes 就可能把它当成“进程已死”反复杀 Pod。这不是配置问题,是语义错配——liveness 探针只该回答“goroutine 还在跑吗”,不是“DB 连得上吗”。
- 常见错误现象:
CrashLoopBackOff状态持续,kubectl logs看不到 panic,但kubectl describe pod显示 liveness probe failed - 典型错误写法:
db.PingContext(ctx, 1*time.Second)、http.Get("http://redis:6379")、甚至os.ReadFile("/tmp/healthy.flag") - 真正该检查的只有三类:HTTP server 是否仍在
Accept()(可用atomic.LoadInt32(&serverRunning)标记)、runtime.NumGoroutine()是否突增(如 > 5000)、runtime.ReadMemStats().HeapInuse是否超 cgroup 限制(比如容器内存上限 512MB 时设阈值 450MB) - 响应必须 ≤ 200ms;超时就说明进程本身有问题,别等 1 秒——K8s 默认探针 timeoutSeconds 是 1,但你代码里卡住 800ms,三次失败就 kill
/readyz每次请求都新建 redis.Client 就是在打爆连接池
高频轮询下,每个 /readyz 请求都 new 一个 redis.Client 或 sql.DB,等于每秒新建几十个连接,不等复用就断开,fd 耗尽、TIME_WAIT 堆积、Redis 拒绝新连接全都会发生。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:启动时初始化全局
redisClient和db实例,/readyz handler 只复用它们 - 探测逻辑必须并行 + 带统一 ctx:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second),然后go checkDB(ctx)、go checkRedis(ctx)、go checkGRPC(ctx) - 缓存结果,不是每次查:用
sync.RWMutex包裹一个结构体,后台time.Ticker每 5 秒刷一次,/readyz 只读快照 - 失败必须返回
503 Service Unavailable,且响应体非空——哪怕只写w.Write([]byte("db: failed")),某些 Ingress(如 Traefik)会截断空 body 导致探针判为失败
用 Gin 注册 /healthz 却被 JWT 中间件拦住,根本不是鉴权问题
框架中间件链是全局生效的,/healthz 被 Use(authMiddleware) 拦住后,连 header 解析、JWT 解码、远程校验都走一遍,延迟抖动、panic recover 捕获、日志刷屏全来了——K8s 不关心你有没有 X-Request-ID,它只看状态码和 body 长度。
- Gin 正确注册方式:健康路由必须放在
r.Use(...)之前,或显式跳过:r.GET("/healthz", healthHandler).SkipAuth = true(注意不是SkipMiddleware,Gin v1.9+ 用的是.SkipAuth) - 更稳妥做法:用独立
http.ServeMux启另一个监听端口(如 :8081),只挂http.HandleFunc("/healthz", ...),彻底隔离业务逻辑 - 别设
Content-Type为text/html或漏掉 charset——必须是application/json; charset=utf-8或text/plain; charset=utf-8,否则某些 Ingress 会丢响应 - 绝对不要在 handler 里调
os.Getenv或读配置文件——这些 IO 可能阻塞,且环境变量加载失败时 panic 会直接让整个 HTTP server 挂掉
最常被忽略的点是:/readyz 缓存结果的更新频率和探针周期不匹配。比如后台每 30 秒检查一次 DB,但 K8s readinessProbe 设置 periodSeconds: 2,那有 28 秒时间里探针看到的都是过期状态——流量可能打入一个已断连的实例。缓存刷新间隔必须 ≤ 探针周期的一半,且要加去抖(连续两次失败才标 Down)。


















