gin的/healthz被中间件拦住返回401不是bug,而是注册顺序错误:健康路由必须在r.use()之前注册,或用r.noroute()兜底拦截并跳过中间件,否则jwt等全局中间件会无差别拦截。

为什么 Gin 的 /healthz 总被中间件拦住返回 401
不是框架 bug,是注册顺序和中间件作用域没理清。Gin 默认对所有路由应用全局中间件,而 JWT、BasicAuth、CORS 等中间件会无差别拦截 /healthz,导致探针收到 401 而非预期的 200。
必须让健康端点绕过鉴权和日志等非必要链路:
-
r.GET("/healthz", healthHandler)必须写在r.Use(...)之前;否则中间件已生效 - 若无法调整顺序,改用
r.NoRoute()拦截兜底:判断c.Request.URL.Path == "/healthz"时直接调 handler 并return - 别依赖
gin.BasicAuth()或自定义 auth 中间件去“放行”路径——它仍会执行解析逻辑,徒增开销 - 避免把
/healthz和/metrics注册到同一组路由(如r.Group("/api")),否则共享中间件必然误伤
livenessProbe 和 readinessProbe 该用哪条路由、查什么
Kubernetes 不关心你叫它 /health 还是 /healthz,只认探针配置里的 path。但语义错位会导致雪崩:
-
livenessProbe应指向轻量级/healthz:只检查http.Server是否仍在 Accept、主 goroutine 是否 panic 过,例如读取一个atomic.Value标志位;绝不碰 DB、Redis、HTTP client -
readinessProbe应指向/readyz:检查关键依赖是否可用,比如db.PingContext(ctx)、redis.Ping()、下游 gRPCCheck(),超时严格控制在 1–2 秒内 - 两个 handler 必须分离,不能共用同一函数——DB 挂了,
/healthz仍应返回 200(进程活着),但/readyz必须返回 503 - 别在
/readyz里做全量缓存加载或配置重载,那是启动阶段的事,不是每次 probe 都该干的
Go HTTP handler 怎么写才不被 probe 超时杀死
Kubernetes 默认 probe timeout 是 1 秒,而 Go 原生 http.HandleFunc 不感知 context 超时,一次慢查询就能让整个 Pod 反复重启。
必须显式用 context.WithTimeout 包裹所有外部调用:
- 用
ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond),留出缓冲给网络往返 - 数据库检查必须用
db.PingContext(ctx),而非db.Ping()(后者会无限阻塞) - handler 内禁止启动 goroutine 后立即返回——探针只看当前请求响应,后台任务结果不影响状态码
- 加
defer cancel(),防止 context 泄漏;不要用log.Fatal或未 recover 的 panic,改用recover()+ 返回 500
func healthz(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond)
defer cancel()
select {
case
<h3>为什么 curl /readyz 返回 200,K8s 却不把 Pod 加进 Service</h3>
<p>返回 200 只是必要条件,不是充分条件。Kubernetes 要求 endpoint 必须连续通过 <code>successThreshold</code> 次 probe(默认 1),且 <code>initialDelaySeconds</code> 后才开始计数。</p>
<p>常见断点不在代码,而在节奏错配:</p>
- Go 应用启动快,但 gRPC server、消息消费者、本地缓存加载可能还在初始化中——此时
/readyz就返回了 200,但实际没准备好 - 确认
kubectl get endpoints <svc-name></svc-name>输出中 IP 列是否及时出现;若为空,大概率是 readiness 检查太早或 kube-proxy 同步延迟 - 关键依赖检查建议用「异步预热 + 同步读状态」模式:启动时 goroutine 异步 Ping DB,probe 时只读内存变量
atomic.LoadUint32(&dbReady) - 别忽略
periodSeconds和failureThreshold的组合效应——设得太激进(如 period=1s, failureThreshold=1)会让短暂抖动直接摘流量
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











