HTTP健康检查返回503主因是路由未注册或handler未返回200且缺Content-Type;K8s/Consul探针仅认200 OK且要求响应体可读;/livez查进程存活,/readyz查服务就绪,二者不可混用。

HTTP 健康检查端点为什么返回 503?
Go 微服务健康检查最常见的失败不是代码没写,而是 http.ServeMux 没注册路径,或 handler 返回了非 200 状态但没设 Content-Type。Kubernetes 或 Consul 的探针默认只认 200 OK,且要求响应体可读(哪怕只是空字符串),否则直接判定为失败。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })最简验证是否路由通 - 避免在 handler 里调用阻塞操作(如未设 timeout 的数据库 ping),超时会触发探针失败
- 别依赖
log.Fatal或 panic 捕获逻辑——探针请求崩溃会导致整个服务不可用,应改用结构化错误返回 - K8s 默认探针超时是 1 秒,若你用了
net.DialTimeout检查下游服务,务必把 timeout 设为800 * time.Millisecond以内
如何区分 liveness 和 readiness?
这两个探针语义不同:/livez 判断进程是否还活着(比如 goroutine 泄漏、死锁);/readyz 判断能否接收流量(比如依赖的 Redis 是否连得上、配置是否加载完成)。混用会导致滚动更新卡住或误杀健康实例。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
-
/livez只做内存/协程数快照检查,例如runtime.NumGoroutine() ,不碰外部依赖 -
/readyz可包含 DB 连接池状态、gRPC 连接就绪、etcd session 是否活跃等,但每项必须有独立超时和降级逻辑 - 不要在
/readyz里做耗时初始化(如 reload config 文件),应提前在 main 启动阶段完成 - 返回 JSON 时统一加
w.Header().Set("Content-Type", "application/json; charset=utf-8"),否则某些 ingress 会截断响应
用第三方库(如 go-health)反而更难调试?
像 github.com/InVisionApp/go-health 提供了组合检查能力,但默认启用 health.DefaultCheckers 会悄悄注册 CPU/Mem 检查,而这些检查在容器环境常因 cgroup 限制报错(如 "failed to read /sys/fs/cgroup/memory/memory.usage_in_bytes"),导致整个健康端点 500。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 禁用所有默认 checker:
h := health.New(health.WithCheckers()) - 自己封装每个依赖检查,例如 Redis 检查用
client.Ping(ctx).Err()而非client.Do(ctx, "PING").Err()(后者不走连接池健康检测) - 避免在 checker 里 new client 实例——每次探针都建连接会打爆 fd,应复用服务已初始化的 client
- 如果用了
go-health,记得调用h.Ready()而非h.Check()对应 readiness,两者返回码不同
为什么本地 curl 正常,但 K8s 里一直 failing?
常见原因是服务监听地址绑定了 127.0.0.1:8080,而 Kubernetes 的探针是从 pod 网络 namespace 发起请求,目标是 0.0.0.0:8080,绑定 localhost 就收不到包。另一个隐蔽问题是 HTTP/2 协商失败——某些 ingress controller(如 nginx-ingress v1.0+)对健康检查强制走 HTTP/1.1,但你的 server 启用了 http2.ConfigureServer 却没关掉 h2c 支持。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 启动 server 时明确监听
":8080",而非"127.0.0.1:8080" - 在
http.Server配置里加IdleTimeout: 30 * time.Second,防止探针长连接被中间设备断开 - 禁用 HTTP/2:移除
http2.ConfigureServer调用,或确保NextProtos不含"h2" - 用
kubectl exec -it <pod> -- curl -v http://localhost:8080/healthz在 pod 内验证,比本地 curl 更真实
健康检查真正难的不是写几行代码,而是让每个环节的失败都能被清晰归因——网络层、应用层、编排层的错误信号必须互不干扰,否则一次部署故障要花半小时才能定位到是 readiness 检查里少写了 context.WithTimeout。










