Liveness Probe不能仅用空/health响应,因其无法检测goroutine泄漏、内存爆满或主线程卡死;必须绕过中间件、避免日志与序列化开销,仅检查进程内存态标志和goroutine数量,确保响应快于1秒超时。

为什么不能直接用 app.Get("/health", ...) 写一个空响应
因为 K8s liveness probe 会反复调用这个接口,而空响应无法反映进程真实状态:goroutine 泄漏、内存爆满、主线程卡死都检测不到。更糟的是,如果框架启用了日志中间件或 panic recover,每次探针都会触发日志写入或额外开销,导致响应变慢甚至超时。K8s 默认 timeout 是 1 秒,一旦响应超过这个时间,就会重启 Pod——形成无限 CrashLoopBackOff。
/healthz 必须绕过所有中间件,怎么注册裸路由
Fiber 不提供“跳过中间件”的原生路由标记,但可以用 app.Use 的路径前缀隔离 + 手动终止中间件链:
- 在初始化阶段,**最先注册**健康端点,比如
app.Get("/healthz", healthzHandler) - 确保它不在任何
app.Use(...)中间件作用域内(不要包在app.Use(logger.New())后面) - handler 内部用
c.Status(200).SendString("ok"),不走 JSON 序列化、不记录日志、不调用c.Next() - 别用
c.JSON或c.SendStatus—— 前者可能触发 JSON 编码 panic,后者不带 body,某些旧版 LB 会拒绝
怎么检查进程自身状态而不碰数据库
真正的 liveness 只回答一个问题:“这个 Go 进程还在跑吗?” 它不该依赖网络、磁盘或连接池。推荐用内存态标志 + goroutine 数量快照:
- 启动时用
atomic.StoreInt64(&healthy, 1)设置初始健康标志 - 在关键 goroutine(如消息监听、定时任务)panic defer 里设为 0
- 在
/healthzhandler 中只读:if atomic.LoadInt64(&healthy) == 1 && runtime.NumGoroutine() - 避免调
time.Now()或uuid.New()—— 它们可能触发 syscalls,在极端负载下有风险
/readyz 查 DB 时为什么总超时或误判
readiness 探针可以查依赖,但必须控制副作用和失败容忍度:
- 别用
db.Ping()—— 它没有上下文超时,且会阻塞整个 handler;改用db.PingContext(ctx),ctx := context.WithTimeout(c.Context(), 1*time.Second) - PostgreSQL 优先用
pgxpool.Pool.Stat().AcquiredConns,比 Ping 更快、不发 SQL - MySQL 可执行
SELECT 1,但要用db.QueryRowContext(ctx, "SELECT 1").Scan(&dummy),并捕获context.DeadlineExceeded单独处理 - 失败时不返回 503 —— readiness 失败只是摘流量,不是杀进程;应统一返回 200 +
{"status": "degraded", "db": "unreachable"}
Fiber 没有自动聚合健康项的机制,每个检查都要你亲手加超时、设 fallback、选响应格式。最容易被忽略的是:/healthz 和 /readyz 必须用不同路由、不同逻辑、不同超时策略,混在一起写,等于把存活判断和就绪判断绑上同一根绳子——一端断了,整条服务就掉下去。











