健康检查端点必须返回200状态码,但不应仅依赖状态码:/health用于liveness(进程存活),返回200且响应体为空或固定结构;/health/ready用于readiness(依赖就绪),检查db等关键依赖,失败时返回503,且需设超时与降级。

健康检查端点必须返回 200 且无 body?不是,但要严格控制响应结构
微服务健康检查(如 /health)的核心目标不是“返回 200 就完事”,而是让调用方(Kubernetes、Consul、Envoy 等)能快速、无歧义地判断服务是否**就绪(ready)**和**存活(live)**。Golang 中常见错误是直接 http.Error(w, "OK", http.StatusOK) 或返回任意 JSON,这会导致 Sidecar 或 LB 误判。
正确做法:HTTP 状态码必须为 200,响应体应为轻量、确定性结构(通常为空或固定字段),且不能包含动态字段(如时间戳、随机 ID)。Kubernetes 的 livenessProbe 和 readinessProbe 默认只校验状态码;若启用 httpGet 的 responseBody 匹配,则需确保内容稳定。
- 推荐返回空响应体(
w.WriteHeader(http.StatusOK),不写任何内容),最简、最兼容 - 若需携带简单状态(如
{"status": "UP"}),必须固定字段、无嵌套、无可变值 - 避免返回
Content-Type: application/json除非明确要求——很多探针不解析 body,设 header 反而增加开销 - 不要在健康检查里调用数据库、Redis 或下游服务——这会让 probe 失去“快速失败”意义,应单独做依赖检查(如
/health/ready)
用 net/http 实现零依赖健康检查端点
不需要引入 Gin、Echo 等框架也能写出生产级健康检查。标准库 net/http 足够,且更可控、无隐藏行为。
关键点在于:注册 handler 时避免与主路由冲突,且 handler 必须无 panic、无阻塞、无 context timeout 依赖(probe 本身超时由基础设施控制)。
- 直接使用
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { ... }),不要包裹在中间件链中 - 禁止在 handler 内调用
r.Context().Done()或等待 channel——probe 请求生命周期极短,自行管理 timeout 可能导致误判 - 如果服务启用了 HTTPS,健康端点也必须支持(Kubernetes 默认走 HTTP 探针,但 Istio 等可能走 mTLS)
- 示例最小实现:
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusOK) })
区分 /health 和 /health/ready:存活 vs 就绪不能混用
存活(liveness)表示进程是否还在运行;就绪(readiness)表示服务是否能接收流量。Golang 微服务里常犯的错是只暴露一个端点,或把 DB 连接检查塞进 /health,结果导致 Kubernetes 频繁重启实例。
正确拆分方式:
-
/health:纯进程级检查,仅确认 HTTP server 正常监听,返回 200 即可(如上例) -
/health/ready:检查关键依赖(DB 连接池、gRPC 后端、本地缓存加载状态),任一失败则返回 503 - 注意:Kubernetes 中
livenessProbe应指向/health,readinessProbe指向/health/ready - 就绪检查必须有超时保护(例如用
context.WithTimeout(context.Background(), 2*time.Second)),否则 probe 卡住会拖垮整个集群
Probe 配置与 Go 服务启动顺序的隐性耦合
Kubernetes 的 probe 默认在容器启动后立即开始,但 Go 服务启动(尤其是 gRPC server、DB 连接池初始化)需要时间。如果 readinessProbe 过早触发,会反复失败,导致服务永远无法进入 Ready 状态。
解决办法不是调大 initialDelaySeconds,而是让 Go 主程序主动控制就绪信号:
- 定义全局
var ready = sync.Once{}和var isReady bool - 在所有初始化完成后(DB ping 成功、gRPC client 建连成功等),调用
ready.Do(func() { isReady = true }) -
/health/readyhandler 中只检查isReady,不重复执行初始化逻辑 - 这样 probe 响应快、确定,且避免竞态——比靠延时更可靠
真正容易被忽略的是:Go 的 http.Serve 是阻塞的,所以初始化逻辑必须在 http.ListenAndServe 之前完成;否则 readiness 永远不会置 true。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











