必须拆开/healthz和/readyz:前者仅检查进程存活(本地状态、≤1秒响应),后者验证真实就绪(DB/Redis等依赖,并行+超时+缓存结果),混用会导致误杀Pod或流量打入未就绪实例。

直接暴露 /healthz 和 /readyz 两个独立 HTTP 端点,别合并在一个 handler 里——这是避免探针语义混淆、防止误杀或流量误导的底线。
为什么必须拆开 /healthz 和 /readyz
两者回答的问题完全不同:/healthz 只回答“进程自己还活着吗”,/readyz 回答“我能处理请求吗”。混用会导致:
- K8s 把一次 DB 临时抖动当成进程崩溃,反复
kill+ 重建 Pod(CrashLoopBackOff) - 滚动更新时新 Pod 还没连上 Redis 就被标记为 Ready,流量进来直接失败
- 就绪探针失败却没摘流量,因为 livenessProbe 和 readinessProbe 指向同一个路径,kubelet 认为“既然能返回 200,那肯定能收请求”
Go 代码里必须注册两个独立路由,且不共享任何中间件逻辑。
/healthz 的实现要点:只查本地、快、静
这个端点必须在 ≤1 秒内完成,不能有任何网络 I/O 或阻塞调用。它只反映 Go 进程自身状态:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
atomic.LoadInt32(&isHealthy)检查主 goroutine 是否 panic 后未恢复 - 可选加
runtime.NumGoroutine()预警(如 > 500),但别做time.Sleep或db.Ping - 响应体必须非空:
w.Write([]byte("ok")),某些旧版 kubelet 对空 body 判为失败 - 显式设 header:
w.Header().Set("Content-Type", "text/plain; charset=utf-8") - 绝对不要走 Gin/Echo 的中间件链——裸
http.HandleFunc最安全
/readyz 怎么查依赖才不拖垮服务
DB、Redis、gRPC 等依赖必须并行检查、带超时、复用连接池状态,否则高频探针会打爆下游:
- 先快速兜底:
db.Stats().OpenConnections > 0,为 0 直接返回 503 - 再并行执行轻量探测:
db.QueryRowContext(ctx, "SELECT 1")、redis.Ping(),共用同一个ctx(超时设为2 * time.Second) - 所有检查失败任一即整体失败,不重试、不降级
- 别在每次请求里实时调用——启动后台 goroutine 每 5 秒跑一次检查,结果缓存在
sync.RWMutex保护的 struct 中,/readyz只读快照
Kubernetes 配置容易踩的坑
YAML 里几个参数配错,探针就形同虚设:
-
initialDelaySeconds必须 ≥ Go 服务真正就绪时间(比如加载大配置、初始化 gRPC client),建议 liveness 设 30s,readiness 设 15s -
timeoutSeconds必须比探针实际耗时多留缓冲,但不能超过periodSeconds;例如periodSeconds: 10,则timeoutSeconds最大设 8 -
failureThreshold要防抖动:DB 临时不可用常持续 2–3 秒,若periodSeconds: 5+failureThreshold: 1,一次失败就摘流量;应设为 3 - 路径必须严格一致:
httpGet.path: /readyz对应代码里http.HandleFunc("/readyz", ...),大小写、尾部斜杠都不能错
最常被忽略的是:Go 进程收到 SIGTERM 后,要主动把 isReady 设为 0,并等 drain 完再退出——否则 kubelet 已经从 Endpoints 删掉实例,但老连接还在往里打,日志里全是 connection refused。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










