【背景】 我在一家中型公司做网络与稳定性,管着一套 60+ 微服务(Java/Go 混合,K8s 1.28,自研 Ingress)。去年经历过一次「健康检查雪崩」:
MySQL 主从切换耗时 8 秒,期间所有服务的 /ready 探针(查 DB ping)全部失败 → K8s 把 4 副本全部标记 NotReady → Ingress 摘除流量 → 服务整体不可用。更糟的是,部分副本被 liveness 探针连续失败后直接 kill 重启,重启后又连不上正在切换的 DB,陷入「重启→失败→重启」循环,恢复时间从 8 秒拉长到 3 分 40 秒。
事后复盘,核心问题就一个:健康检查的「深度」到底该多深?
【常见的几种做法】 我调研了至少 5 家公司的做法,差异非常大:
A. 浅探(只探端口/进程活着):/health 返回 200 只要 JVM 活着、HTTP 端口能响应。liveness 和 readiness 都用这个。 → 好处:永远不会因为 DB 慢/下游慢而误杀。 → 坏处:Pod「活着但完全不能用」,流量打过来就是 500,等于把问题推给了调用方。
B. 中探(探核心依赖):/ready 里查 DB ping、Redis ping,不查下游 RPC。 → 好处:能抓住「DB 挂了但我活着」的情况。 → 坏处:DB 抖动 2 秒,所有 Pod 同时 NotReady → 雪崩(就是我上面那个事故)。
C. 深探(全链路探活):/ready 里查 DB + Redis + 2~3 个核心下游 RPC。 → 好处:最全面,能发现「我活着、DB 活着、但下游挂了」。 → 坏处:下游一抖,我的 Pod 就 NotReady,变成「别人慢、我掉线」;而且探针 QPS × 副本数 × 下游调用 = 额外的系统负载,故障时反而加重下游压力。
D. 分级探针(liveness 浅 + readiness 中 + 自定义 deep check 异步):liveness 只探进程,readiness 探 DB/Redis,deep check 用 Prometheus 独立采集,只影响监控面板不影响 K8s 调度。 → 好处:liveness 不会被外部依赖影响(不会被误杀),readiness 能反映「能不能接流量」。 → 坏处:实现复杂,deep check 和 readiness 信号不一致时 oncall 容易困惑。
【我的倾向】 我站 D 方案,核心逻辑是:liveness 的职责是「进程是不是卡死了」,跟外部依赖无关;readiness 的职责是「现在能不能接新流量」,可以探核心依赖但必须带超时和容错。
具体做法(附关键参数):
- liveness: TCP 端口探活,timeout 3s,failureThreshold 3,periodSeconds 10。只要 JVM/进程活着就不杀。
- readiness: 查 DB ping(timeout 2s)+ Redis ping(timeout 1s),failureThreshold 2,periodSeconds 5。DB 抖动 2 秒不摘流量,持续 10 秒才摘。
- deep check: Prometheus /metrics 里暴露 downstream_latency、db_pool_pending、redis_hit_rate 等指标,告警走 SLO 路径,不影响 K8s 调度。
关键原则:探针的失败不应该放大故障。 DB 慢 2 秒,我摘不摘流量是业务决策,不应该由 K8s 探针的 3 次连续失败来一刀切。
但我也承认 D 方案有代价:deep check 和 readiness 可能「矛盾」(readiness 绿但 deep check 红),oncall 看面板时容易懵,需要额外的 runbook 来解释。
【想听大家】
- 你们线上 liveness 和 readiness 到底探多深?有没有因为「探太浅」放过坏 Pod、或「探太深」误杀好 Pod 的案例?
- 探针的 failureThreshold × periodSeconds 你们怎么定的?有没有量化过「DB 抖动 N 秒后摘流量」对整体可用性的影响?
- 有没有人试过「readiness 探针里探下游 RPC」?效果如何?有没有出现「下游抖 1 秒、我 3 副本全摘」的情况?
- 如果让你从零设计一套探针策略,你会怎么在「不放过坏 Pod」和「不误杀好 Pod」之间取平衡?
欢迎大家带具体配置(探针 YAML、timeout、failureThreshold)来聊,空对空的意义不大。