应拆分为 /health 和 /readyz 两个独立路径:/health 仅检查 HTTP server 是否监听、主 goroutine 是否 panic;/readyz 则需探测数据库、Redis、下游 gRPC 等依赖状态,确保服务真正可处理请求。

别用 /health 一个接口扛所有探针需求——Kubernetes 的 livenessProbe 和 readinessProbe 语义完全不同,混用等于主动制造雪崩风险。
为什么 c.JSON(200, gin.H{"status": "ok"}) 在生产环境会失效
这个写法只确认 Gin 进程在监听,完全不感知依赖状态。数据库连接池耗尽、Redis 实例宕机、下游 gRPC 超时,/health 仍返回 200,但所有业务请求已卡死。
- 容器进程活着 ≠ 服务能处理请求
- Kubernetes
readinessProbe若指向该端点,会持续导流到“假活”实例 - Docker 的
HEALTHCHECK指令也面临同样问题,只是失败后直接标记容器 unhealthy
必须拆成 /health 和 /readyz 两个独立路径
路径语义和探测逻辑要严格分离,不能靠 query 参数或 header 区分。
-
/health:仅检查 HTTP server 是否 Accept、主 goroutine 是否 panic 过,响应必须 livenessProbe 使用 -
/readyz:并发探测 DB、Redis、关键下游 API,每项带独立超时(如 DB 用context.WithTimeout(c.Request.Context(), 2*time.Second)),供readinessProbe使用 - 禁止用
/status或/ping—— 前者易与 Prometheus/metrics混淆,后者无法表达就绪语义
gin.HandlerFunc 里如何做带超时和聚合的依赖检查
不能裸调 db.Ping() 或 http.Get(),必须封装为可取消、可并发、可单独配置超时的函数。
- 每个检查返回
func() error,例如dbHealthCheck(db *sql.DB)内部用ctx, cancel := context.WithTimeout(...) - 用
errgroup.Group并发执行所有检查,避免串行拖慢整体响应 - 聚合结果:全成功 →
c.Status(200);任一关键依赖失败 →c.Status(503);非关键项降级 →200但 body 中含"degraded": true - 响应 body 必须含各依赖字段,如
{"db": "healthy", "redis": "unavailable"},方便人工排查
注册路由时绕过中间件是硬性要求
JWT 验证、CORS、日志中间件若作用于健康端点,会导致 401 或 403,探针直接失败。
- Gin 中必须把健康路由注册在
r.Use(...)之前,或显式跳过:r.NoRoute(func(c *gin.Context) { if c.Request.URL.Path == "/health" || c.Request.URL.Path == "/readyz" { healthHandler(c); return } }) - 不要把
/health和/metrics放进同一个gin.RouterGroup,它们语义、权限、日志格式都不同 - Kubernetes
timeoutSeconds: 5是探针层超时,你的 handler 内部超时必须更短(比如 DB 设 2s),否则探针等不到响应就判失败
最常被忽略的是路径匹配精度和中间件顺序——Gin 的 router.GET("/health", ...) 若注册在 Use() 之后,且中间件没做路径判断,健康检查就永远过不了。这不是 bug,是配置错误。


















