Go健康检查必须返回200或503状态码,Kubernetes、Consul、Nginx等仅依据HTTP状态码决策:200表示可服务并导流,503表示不可用即立即摘除;/livez与/readyz须分离,前者仅检查本地进程(无外部调用、≤1秒),后者验证真实依赖(DB/Redis/gRPC等任一失败即503);所有依赖检查必须带独立context超时、并行执行、复用全局客户端,且probe timeoutSeconds需≥最慢依赖P99延迟(建议≥1.5秒),initialDelaySeconds至少5秒。

Go健康检查必须返回200,503不是“降级”而是“摘除”
Kubernetes、Consul、Nginx等所有主流编排/注册中心,只看HTTP状态码做决策,不解析JSON字段。返回{"status":"down"}但状态码是200,K8s readiness probe照常导流;反过来,哪怕body写{"status":"up"},只要状态码是503,实例立刻从Endpoint中剔除——这不是服务降级,是直接下线。
正确做法只有两个:w.WriteHeader(http.StatusOK)表示可服务,w.WriteHeader(http.StatusServiceUnavailable)表示不可用。别用http.StatusNotFound、http.StatusMovedPermanently或http.StatusNoContent,这些都会被探针判定为失败。
/livez和/readyz必须拆开,不能共用一个handler
混用/health同时承担liveness和readiness语义,是生产事故高发区:
-
/livez只检查本地进程是否活着:goroutine数是否突增、内存是否超限、时钟是否漂移,禁用任何外部调用,响应必须≤1秒 -
/readyz才查真实服务能力:DB连接池db.PingContext()、Redisclient.Ping()、gRPCconn.GetState(),任一失败就返回503 - 把DB检查塞进
/livez,网络抖动1秒就触发K8s反复kill+restart;把goroutine泄漏检查放进/readyz,又会导致流量被误摘
handler里忘了w.WriteHeader(200)是最常见的503来源
很多Go服务返回503,根本不是DB连不上,而是路由没注册或状态码漏写:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 用
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })先验证路径通不通 - 只写
w.Write([]byte("OK"))但没调w.WriteHeader(200),Go默认返回200?错——默认是200仅当未写header且未写body前,一旦body已写,后续WriteHeader会被忽略,实际响应是200还是503取决于底层HTTP实现,行为不可靠 - 别用
log.Fatal或panic处理探测错误,整个服务会崩溃;应捕获错误,写入响应体的components字段并仍返回200(对/livez)或503(对/readyz)
依赖检查不加context超时,等于主动触发CrashLoopBackOff
K8s默认probe timeoutSeconds: 1,而一次db.Ping()在网络抖动时很容易卡住2秒以上。handler没返回,K8s已判定失败并重启——这不是健康检查,是自毁循环。
必须做到:
- 每个依赖检查都带独立
context.WithTimeout(ctx, 800*time.Millisecond) - 所有检查并行执行,共用同一个父
ctx,任一失败就整体退出 - 别在handler里实时新建
sql.DB或redis.Client,复用启动时初始化的全局实例,否则每次探针都建连接,fd很快耗尽 -
timeoutSeconds必须≥你最慢依赖的P99延迟(建议至少设为1.5秒),initialDelaySeconds不能为0,新Pod启动后配置加载、连接池预热需要时间,至少设5秒
CrashLoopBackOff。

















