健康检查接口必须返回标准HTTP状态码:/healthz校验下游依赖失败时返回503,/livez仅确认进程存活返回200;pprof需鉴权访问;推荐用OpenTelemetry healthcheck替代手写逻辑;K8s探针配置须匹配服务实际行为。

健康检查接口必须返回标准 HTTP 状态码
Go 服务的健康检查不是“能访问就行”,客户端(如 Kubernetes、Nginx、Consul)依赖 HTTP 200 表示“就绪且可流量”,HTTP 503 表示“暂时不可用”。返回 200 但 body 写 {"status":"down"} 没用——多数探针只看状态码,不解析 JSON。
实操建议:
- 用
http.HandleFunc或chi.Router注册/healthz(就绪)和/livez(存活),避免混用 - 就绪检查(
/healthz)应校验下游依赖:数据库连接、Redis 可写、关键 gRPC 服务连通性;失败则返回http.StatusServiceUnavailable (503) - 存活检查(
/livez)只确认进程未卡死:不查外部依赖,仅return http.StatusOK - 禁止在健康接口中做耗时操作(如查 10 张表、调三次第三方 API),超时会触发反复重启
使用 net/http/pprof 前必须限制访问来源
net/http/pprof 提供 /debug/pprof/ 下的运行时指标(goroutine、heap、trace),是健康监控的事实标准,但它本身不是健康检查接口,且暴露后有安全风险。
常见错误现象:
立即学习“go语言免费学习笔记(深入)”;
- 直接
http.Handle("/debug/pprof/", http.DefaultServeMux)→ 任意公网 IP 都能 dump 堆栈或 CPU profile - 在生产环境启用
pprof但没加中间件鉴权 → 被扫描工具批量抓取,拖慢服务
实操建议:
- 只在
debug构建标签下注册:if os.Getenv("DEBUG") == "true" { mux := http.NewServeMux() mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) http.ListenAndServe(":6060", mux) } - 若必须开放,用反向代理(如 Nginx)限制 IP 段,或在 Go 中加简单 IP 白名单中间件:
func pprofAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ip, _, _ := net.SplitHostPort(r.RemoteAddr) if !slices.Contains([]string{"127.0.0.1", "10.0.0.0"}, ip) { http.Error(w, "Forbidden", http.StatusForbidden) return } next.ServeHTTP(w, r) }) }
用 go.opentelemetry.io/otel/healthcheck 替代手写逻辑
手动拼接 /healthz 的 JSON、管理依赖状态、加锁防并发竞争,容易出错。OpenTelemetry 官方维护的 healthcheck 包提供可组合、可观察的健康检查抽象。
使用场景:
- 多个组件(DB、Kafka、S3 client)需独立上报状态,且整体健康 = 全部 OK
- 需要将健康状态导出到 Prometheus(如
otel_health_check_up{component="postgres"} 1) - 想统一记录健康检查耗时、失败原因(自动打 log + metric)
参数差异与注意点:
-
healthcheck.NewChecker()默认超时是3s,可通过WithTimeout(5 * time.Second)调整 - 每个检查项必须实现
healthcheck.CheckerFunc,返回error即表示失败(不要自己转成字符串) - 它不自动注册 HTTP handler,需手动绑定:
hc := healthcheck.NewChecker() hc.Add("postgres", healthcheck.CheckerFunc(func(ctx context.Context) error { return db.PingContext(ctx) })) http.HandleFunc("/healthz", hc.Handler())
Kubernetes readiness/liveness 探针配置必须匹配 Go 服务实际行为
很多团队把 livenessProbe 设成 5 秒超时、3 次失败就重启,结果因一次 DB 临时抖动(持续 8 秒),Pod 被反复 kill/restart,雪崩式影响更大。
关键判断依据:
-
readinessProbe失败 → 从 Service Endpoints 移除,不再接收新流量;适合配短周期(periodSeconds: 5)、低失败阈值(failureThreshold: 2) -
livenessProbe失败 → 触发容器重启;必须比业务最长处理时间长,且只用于检测“进程假死”(如 goroutine 泄漏卡住 HTTP server),不是网络抖动兜底方案 - Go 服务默认
http.Server.ReadTimeout是 0(无限制),若健康接口阻塞,K8s 探针会等满timeoutSeconds才判定失败,期间所有请求 hang 住
实操建议:
- 在
http.Server中显式设ReadTimeout: 30 * time.Second,避免单个慢请求拖垮整个探针 - 为
/livez单独起一个轻量http.Server(监听:8081),完全不走主路由中间件,确保即使主服务卡死也能响应存活检查 - 日志里打健康检查的入参和耗时:
log.Printf("healthz called, elapsed: %v", time.Since(start)),便于定位是代码慢还是依赖慢
健康检查不是加个路由就完事,真正难的是定义“什么算健康”——数据库连得上但慢十倍,算健康吗?消息队列积压 10 万条,算健康吗?这些边界必须结合业务 SLA 明确,然后才轮到 Go 怎么写。










