go服务在kubernetes中挂而不重启或流量打进来直接500,根本原因是/healthz和/readyz路径未严格匹配探针配置或handler逻辑错误:必须显式注册大小写敏感的固定路径、/healthz仅做轻量进程级检查(100ms内返回200,禁调外部依赖),/readyz同步探测关键依赖并设≤3s超时,且yaml中timeoutseconds、initialdelayseconds等参数须与handler实际耗时精准对齐。

Go服务在Kubernetes里挂了却不重启,或者流量打进来直接500——大概率是/healthz和/readyz没对上探针配置,或者handler逻辑踩了坑。
Go里必须显式注册/healthz和/readyz路径
Kubernetes的livenessProbe和readinessProbe只会GET固定路径,不会猜、不重定向、不 fallback。如果你用http.ServeMux但没注册这两个路径,或者注册成了/health、/api/healthz,探针就永远收404,然后根据failureThreshold杀Pod或摘流量。
- 用
http.HandleFunc直接注册,别依赖中间件自动注入(比如某些框架的“健康路由”插件可能默认关掉或路径不对) - 路径名大小写敏感:
/healthz≠/Healthz≠/healthZ - 不要在handler里调
http.Redirect——探针不跟随302,直接判失败 - 确保监听端口和
containerPort一致,且Service的targetPort指向它
/healthz只做进程级快检,别碰外部依赖
/healthz是liveness目标,设计初衷就是“这个goroutine还活着吗?内存炸了吗?”,不是“数据库连上了吗?”。一旦在里面加db.Ping()或HTTP call,就等于把存活探针降级成伪就绪检查——服务卡在DB超时里,Kubernetes却以为它“活着”,不会重启,形成僵尸Pod。
- 推荐检查项:
runtime.NumGoroutine()是否异常飙升、runtime.ReadMemStats中Alloc是否接近limit、本地channel是否满载阻塞 - 绝对避免:调
db.Ping()、redis.Ping()、http.Get()、写文件、加锁等待 - 响应必须在100ms内完成;超时设置
timeoutSeconds: 1时,handler本身就得≤300ms返回 - 返回200即可,响应体可空,或只带时间戳和版本号——Kubernetes只看状态码
/readyz必须同步探测关键依赖,且设≤3s总超时
/readyz是readiness目标,核心任务是回答:“现在能收请求吗?”答案取决于下游是否可用。硬返回{"status":"ok"}等于告诉Kubernetes“请把所有流量打过来”,哪怕Redis已断开、MySQL连接池耗尽。
- 每个依赖检查必须包在
context.WithTimeout(ctx, 2*time.Second)里(总超时建议≤3s,留缓冲) - 典型检查顺序:
db.PingContext()→redis.Ping()→grpcConn.Invoke(...);任一失败立即return 503 - 响应体要写明失败项,比如
{"error":"failed to ping redis", "component":"redis"},方便快速定位 - 别在这里加载缓存、预热模型、读大配置文件——这些该在
main()启动阶段做完,不是每次探针都跑一遍
Kubernetes Probe参数必须和Go handler行为对齐
YAML里写的timeoutSeconds、initialDelaySeconds、periodSeconds不是摆设。它们和Go handler的实际耗时共同决定探针行为。常见错配:handler平均耗时800ms,但timeoutSeconds: 1看似够用,实际网络抖动+GC暂停就超时;或initialDelaySeconds设太小,应用还没初始化完探针就开始打,反复失败触发重启。
-
timeoutSeconds应 ≥ handler最坏情况耗时(含网络抖动),建议比handler超时多留200–500ms余量 -
initialDelaySeconds必须 ≥ 应用冷启动时间(DB连接池建满、gRPC连接建立、配置加载完毕) -
periodSeconds不宜过密(如设为1s):高频探针会放大handler性能问题,也增加kubelet压力 - 如果用了
startupProbe,确保它成功前livenessProbe和readinessProbe不生效——否则可能在启动中途误杀
最容易被忽略的是/healthz和/readyz的职责边界:前者是“心跳”,后者是“营业许可”。混用或偷懒复用同一段逻辑,会让Kubernetes失去判断依据——既不能及时重启故障进程,也无法安全切流。写handler时,先问自己一句:这个检查,是让kubelet杀我,还是不让它发流量?答案不同,代码就该完全隔离。











