节点维护 drain 后 Pod 被调度到「冷节点」:readiness=Ready 但 JVM 预热/本地缓存空窗 P99 飙 10 倍,有没有可度量的 warmup 门禁方案?
该问题的正文、回答与讨论。
背景
我维护一个 20 节点(16C64G,Kubernetes 1.28.9,containerd 1.7.13,cgroup v2,Calico 路由模式)的生产集群,核心服务是 Java 17 无状态 API(12 副本,JIT + Caffeine 本地缓存 + 连接池,单请求 P99 稳态约 18ms)。该服务对就绪时间敏感:readiness 只探活 /healthz(200 即 Ready),不校验「能扛住真实流量」。
现象
最近一次节点内核升级维护窗口(02:00–03:30),我们按流程 kubectl cordon + kubectl drain --delete-emptydir-data --grace-period=60 依次维护 4 个节点。被 drain 的 Pod 被调度到另外 16 个节点上已有副本的「热节点」,但其中有 3 个 Pod 恰好落到了近期没跑过该镜像的 3 个节点(本地无镜像 + JVM 冷启动 + 本地缓存空 + 连接池未建)。
结果:维护窗口内 12 分钟,网关侧观测到该服务 P99 从 18ms 飙到 210ms(约 10 倍),错误率没涨(readiness 全程 Ready,502 为 0),但用户侧能感知到「变慢了 2 分钟」。事后 kubectl get pods -o wide 对账,那 3 个慢 Pod 的 nodeName 正好是那 3 个冷节点。
已尝试的排查与结果
- 看
terminationGracePeriodSeconds+preStop:配置本身没错(sleep 10s + grace 45s),摘流量和优雅退出都正常,这不是本次问题; - 看 PDB:
minAvailable=10,drain 期间没有触发 PDB 阻塞,调度器按约束正常放置; - 看
kubectl describe pod的 events:3 个慢 Pod 的Scheduled → Pulling → Pulled → Created → Started → Ready全链路约 38s,其中Ready到「P99 回落稳态」之间还有 约 90s 的纯应用层 warmup 窗口(JIT + 缓存 + 连接池),这段没有任何 K8s 事件能观测到; - 试过把
readinessProbe换成「跑一次真实业务查询」的接口,但这个接口本身在冷启动时也会慢,没法用来判定「已经 warm」。
期望得到的答案形态 希望社区能给一个可度量的「warmup 完成」门禁方案,具体想听到:
- ① 有没有一种不依赖业务接口 RT、但能反映「JIT/缓存/连接池已就绪」的 readiness 探针设计(比如主动打一次预热请求、检查 Caffeine hit rate 阈值、JIT 编译计数等),以及它跟
startupProbe的边界; - ② 节点维护排程上,怎么预判哪些节点是「冷节点」(本地 image store 无该镜像 + 近期无该服务 Pod 历史),drain 前主动把该服务的副本「预热」到这些节点上(
topologySpreadConstraints/nodeAffinity/ 临时扩容占位 Pod),避免 drain 后的调度落到冷节点; - ③ 有没有人做过**「维护窗口 + HPA 提前扩容 + 发布后 warmup 门禁」三件套**的组合方案,P99 抖动能压到多少秒、代价(多占多少节点)?
我倾向的答案是「readiness 要分两档:活 vs 能扛」,但具体怎么把「能扛」做成一个可观测、可门禁的指标,我还没想清楚,想听听大家踩过坑的做法。
引用社区已有讨论作为背景:
- 《HPA 扩容后那 30 秒冷启动窗口,P99 抖动到底该谁兜底》(id=189) 里提到的「就绪≠能扛」正是我这次撞到的坑,但 HPA 场景是扩容触发,我这里是节点维护触发,触发路径不同、需要不同的预判手段,想听听维护场景的实操。