开放问答
Go 服务 GC 长 STW 撞穿 liveness 探针 timeout,滚动窗口里连环重启,探针参数到底该怎么定?
该问题的正文、回答与评论。
提问者 程亦舟-167 · 状态 open
KubernetesGoGCliveness探针SRE
背景 10 年 SRE,最近接手一个 K8s 1.28(containerd 1.7,节点 16C64G)上的 Go 1.21 网关服务,4 副本、单副本峰值 QPS 约 2k,主要工作是转发 + 一些带 30~60s 长连接的任务。
现象
近两周凌晨(02:00–04:00)出现过 3 次连环重启:一个 Pod 被 kubelet 判 liveness 失败杀掉后,另外 3 个副本 QPS 立刻被拉高,几分钟后又出现下一个 liveness 失败,最终 4 个副本在 6 分钟内被滚动重启完,网关侧 P99 从 80ms 飙到 2.4s。看 dmesg 没有 OOM,kubectl describe pod 里 liveness 失败的日志形如 HTTP probe failed with statuscode: 500,kubectl get events 里能看到连续的 Unhealthy。
已尝试的排查与结果
- 怀疑 GC STW:在容器里加
GODEBUG=gctrace=1,凌晨复现时看到一次gc 87 @0.234s 2%: 0.021+18.4+0.041 ms clock, 0.17+22/55+0.33 ms cpu, 12->14->11 MB, 14 MB goal, 10 P——STW 本身只有 18ms,但assist阶段把 CPU 打满、goroutine 调度被拖慢,HTTP handler 里context.WithTimeout全链路超时,最终 500。所以问题不是 GC 本身,而是「探针窗口 < 一次完整调度抖动窗口」。 - 把 liveness
timeoutSeconds从 1s 调到 5s、failureThreshold从 3 调到 5,复现时仍然失败——因为 handler 里http.Server.ReadHeaderTimeout=10s,抖动叠加后还是 500。 - 怀疑是探针打到了业务端口:改成一个独立的
/livez端口(不经过业务 router,直接返回 200),复现时不再 500,但仍然出现过一次context deadline exceeded(探针 client 默认 1s),说明探针 client 的超时和 server 的 ReadHeaderTimeout 是两个独立坑。 - 怀疑是 CPU throttle:
cgroup v2下cpu.stat的nr_throttled在抖动窗口内从 120 涨到 480,但throttled_usec只占 ~3%,看起来不是主因,但也不能完全排除和 GC 抢占叠加。
期望得到的答案形态 想请有类似踩坑经历的同事给一份可落地的清单,最好包含:
- liveness / readiness / startup 三件套在「长 STW 抖动」场景下的具体参数(timeoutSeconds / periodSeconds / failureThreshold / successThreshold 的取值与理由),以及 startupProbe 的
failureThreshold × periodSeconds该给多大窗口; - 一个独立
/livez端口的最小实现要点:要不要绕过业务 router、要不要绕开全局ReadHeaderTimeout、探针 client 侧http.Client.Timeout该设多少; - 如何把「GC 抖动导致的探针失败」和「真故障导致的探针失败」在告警里区分开(指标名 / 日志关键字 / kubectl 命令),避免每次都是 4 副本连环重启;
- 如果最终选择放宽探针窗口,
failureThreshold放大到多大才会开始掩盖真故障?有没有经验阈值或判断方法。
欢迎贴你集群里的具体参数与事故时间线,最好带 dmesg / cpu.stat / GODEBUG=gctrace 的关键片段,方便对比。
评论
暂无评论。