2305 字
约 7 分钟
12
S17-A28 · 健康检查:为什么"探针通过"的实例一个请求都接不了

一个服务滚动发布,10 个新实例全部探针通过,流量切过去之后错误率飙到 60 %。回滚、再发、再切,同样的现象。查日志发现新实例不是"挂了",而是探针只检查了"进程活着",没检查"依赖可用"——它们连不上数据库,所有真实请求都在 500。

这类故障有个共同特征:探针的定义与"能不能服务"是两件事。而 K8s 的三种探针各自管一件不同的事,混用或只用一个,就会出现"要么永远不切流量、要么切了等于没切"。


1. 三种探针,三个不同的问题

探针 回答的问题 失败后 K8s 做什么
liveness 进程是不是该被重启 杀掉容器,重启
readiness 现在能不能接流量 从 Service 摘除
startup 启动完了没有 在它成功前不跑另外两个

第一行的动作最危险:liveness 失败会重启容器。所以它只能检查"不可恢复的僵死"(死锁、事件循环卡住),绝不能检查"依赖是否可用"——否则数据库抖动会被放大成"所有实例一起重启"。

第二行才是"能不能服务"的判定点:依赖不可用时摘流量而非重启,这是唯一的正确动作。

// 三种设计的后果对比
liveness 检查 DB 连接:DB 抖动 30s → 全部实例重启 → 启动风暴 → 事故扩大 10 倍
readiness 检查 DB 连接:DB 抖动 30s → 实例被摘流 → 前端 503 但实例还在 → 恢复即好
只有 liveness 没 readiness:坏实例一直收流量 → 用户看到 60% 错误

2. 探针要检查"最小的可服务集合"

检查项 该放哪个探针 理由
进程/端口存活 liveness 僵死才需要重启
依赖连通(DB/Redis/MQ) readiness 不可用时摘流即可恢复
本地缓存预热完成 startup 或 readiness 未预热就接流量会拖慢全部
磁盘可用 / 队列积压 readiness(有条件) 过载时摘流能保护自己
外部第三方接口可达 都不要 第三方故障不该影响自己的存活判定

第五行是实践中最容易过度的:把"第三方支付接口可达"放进 readiness,结果是对方一次抖动把你所有实例摘光,你的服务本身明明是健康的。


3. 超时与阈值:默认值几乎都是错的

参数 默认 默认值的问题
timeoutSeconds 1 高负载下探针自己超时,误判
periodSeconds 10 故障发现延迟 10s
failureThreshold 3 连续 3 次才动作 = 30s 窗口
initialDelaySeconds 0 冷启动期被误杀

第二行与第三行叠加起来是**"最坏 30 秒才摘流量"**——对高 QPS 服务,30 秒意味着几十万次失败请求。所以生产上常用 periodSeconds: 3 + failureThreshold: 2(约 6 秒响应),代价是探针请求更频繁。

第四行提示:有了 startupProbe 之后,initialDelaySeconds 就该退场——startup 探针用宽松阈值覆盖启动期,liveness 用严格阈值覆盖稳态,两者分工比调一个大数字更清晰。

// 一个可用的三段配置(Spring Boot / Java 服务)
startupProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }
  periodSeconds: 5
  failureThreshold: 30          # 最多等 150s,覆盖冷启动
livenessProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }
  periodSeconds: 10
  failureThreshold: 3
readinessProbe:
  httpGet: { path: /actuator/health/readiness, port: 8080 }
  periodSeconds: 3
  failureThreshold: 2           # 约 6s 摘流
  # 注意:readiness 的端点内部必须包含 DB/Redis 检查,liveness 的不包含

4. 摘流量的两个隐藏延迟

即使 readiness 正确,流量也不会"瞬间"消失:

环节 延迟来源
kube-proxy / iptables 规则更新 秒级,节点越多越慢
客户端连接池复用 已建立的连接继续发请求
LB 健康检查周期 各云厂商不同,常 5–30s
优雅停机宽限期 决定在途请求能否跑完

第二行是最容易被忽略的:被摘除的实例,连接池里的旧连接仍在工作。所以"先把 readiness 置 false、等 5–10 秒、再停进程"是标准顺序;反过来(先停进程)会让那部分连接直接被重置。

// 一次正确的下线时序
t=0    readiness 置 false(依赖不可用 / 准备停机)
t=0..10 等 kube-proxy 与 LB 收敛;在途请求正常返回
t=10  收到 SIGTERM → 停止接受新连接
t=10..30 等在途请求完成(宽限期)
t=30  进程退出
// 关键:那 10 秒等待是必须的,跳过它就会有"零星的 connection reset"

5. "假死":探针通过但服务不可用

假死形态 为什么探针看不出来
线程池被慢请求占满 端口能连、探针线程能跑
事件循环被 CPU 任务阻塞 探针 endpoint 走另一个线程
所有连接都卡在锁上 探针不需要那把锁
队列积压到内存极限 探针只看"活着"
// 现场取证:探针通过但服务不健康时,先看这三处
$ curl -s localhost:8080/actuator/health/readiness | head -c 300
$ curl -s -o /dev/null -w "real_request=%{http_code}\n" localhost:8080/api/ping
$ curl -s localhost:8080/actuator/metrics/executor.queue.remaining | head -c 200
// 判据:readiness 200 而 real_request 500 → 探针没覆盖真实路径

第二种在 Node/Go 服务里最常见:探针线程与业务线程不是同一个,业务卡死而探针依旧 200。修法是把"业务健康信号"接进 readiness——例如"最近 1 分钟成功处理的请求数 > 0"或"线程池队列深度 < 阈值"。

// 一个可用的 readiness 组合条件(三条都要满足)
1 依赖连通:DB / Redis 各一次 ping(超时 300ms)
2 自身未过载:线程池队列使用率 < 80%
3 最近有成功:近 60s 内成功请求数 > 0
// 第三条专治"假死":业务全卡住时它必然为 false

6. 排错:五种"探针正常但服务异常"

现象 八成原因 处理方向
新实例探针过、请求 500 readiness 没查依赖 readiness 加依赖检查
依赖抖动导致全体重启 依赖检查放在 liveness 移到 readiness
第三方故障把自己摘光 第三方检查进了 readiness 移除,改降级
发布后零星 connection reset 摘流量后没等收敛 加 5–10s 等待
高负载时实例反复重启 探针 timeout 太短 调到 3s + 阈值 2

第一行与第二行是一对:同一个检查项放错探针,会产生完全相反的事故。放 readiness 是"摘流自保",放 liveness 是"重启风暴"。


7. 边界:什么情况不需要复杂探针

场景 为什么 做法
无状态纯计算服务 无依赖可查 端口探测足够
单实例/单机部署 没有"摘流量"的概念 只做存活监控
批处理任务(Job) 不接流量 看退出码即可
依赖本身就是弱依赖 不可用时应降级而非摘流 移除该项检查

判断口径一句话:探针要回答"这个实例现在该不该接流量",而不是"这个实例有没有问题"——前者是摘流的依据,后者是重启的依据,两者混淆就是事故来源。而检查项的取舍标准是:这项失败时,摘流能不能让用户更好;不能,就别放进 readiness。


8. 自测

  1. 三种探针各自回答什么问题?哪个失败会导致容器重启?
  2. 为什么"依赖检查"该放 readiness 而不是 liveness?放错会放大成什么事故?
  3. 第三方接口可达性为什么两个探针都不该查?
  4. periodSeconds: 10 + failureThreshold: 3 意味着最坏多久才摘流量?生产上怎么调?
  5. 摘流量之后为什么还要等 5–10 秒才停进程?
  6. "假死"为什么探针看不出来?readiness 里该加什么来判断?

9. 一句话总结

探针回答的是"该不该接流量",不是"有没有问题":readiness 失败摘流、liveness 失败重启——把依赖检查放进 liveness,会把数据库抖动放大成重启风暴。三件纪律:readiness 查最小可服务集合(依赖 + 过载 + 最近有成功)、liveness 只查僵死、第三方故障一律不查;摘流之后要等 5–10 秒让 kube-proxy 与连接池收敛,再发 SIGTERM。

S17-A28 · 健康检查:为什么"探针通过"的实例一个请求都接不了
https://www.clxhxhhr.top/posts/6761/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。