maxunavailable设为0可确保零停机:k8s先启动新pod(由maxsurge控制),待其就绪后再删除旧pod;若设25%则4副本服务会瞬间删1个,而新pod因jvm/spring启动慢未就绪,导致流量打空、延迟飙升。

maxSurge 设为正数,maxUnavailable 设为 0
Java 应用启动慢,不能容忍旧 Pod 被删而新 Pod 还没就绪。所以必须让 K8s 先“加人”,再“换人”。maxSurge 控制能多跑几个 Pod,maxUnavailable 控制最多能少几个可用 Pod。设 maxUnavailable: 0 就是告诉 K8s:“一个旧 Pod 都别动,等新 Pod 真正 ready 了再说”。
常见错误是沿用默认值 maxUnavailable: 25%,结果 4 副本的服务更新时直接干掉 1 个,剩下 3 个扛全部流量——而新 Pod 还在 JVM 预热、Spring 初始化、连接池填充,这几十秒里 P99 延迟飙升。
- 推荐配置:
maxSurge: 1(绝对值更可控),maxUnavailable: 0 - 如果资源紧张,可改用
maxSurge: 25%+maxUnavailable: 0,但需确保集群有足够 CPU/Mem 预留 - 不要用百分比配
replicas: 1——25%向下取整为 0,更新会卡住
readinessProbe 必须配对 JVM 启动节奏
readinessProbe 不是摆设。它决定 K8s 什么时候把流量切给新 Pod。Spring Boot 应用常用 /actuator/health,但默认响应快于实际就绪:JVM 可能刚加载完类,连接池还是空的,第一个 DB 请求照样超时。
典型表现是滚动更新期间偶发 503 或长尾延迟,日志里却看不到报错——因为 Pod 已被标记 ready,但业务层根本没准备好。
-
initialDelaySeconds至少设为 JVM + Spring 启动耗时的 1.5 倍(实测 40 秒启动?那就设 60) -
periodSeconds别太密(5秒探一次容易误判),10~15更稳妥 - 健康检查端点最好带业务语义,比如
/actuator/health?show-details=always或自定义/health/ready,检查连接池 size > 0、缓存预热完成等
镜像基础层选 openjdk:*-slim,但得关掉 JFR 和调试代理
OpenJDK 镜像体积和启动速度直接影响滚动更新效率。虽然 openjdk:17-jdk-slim 是常见选择,但如果镜像里开着 Java Flight Recorder(JFR)或远程调试参数(-agentlib:jdwp),会拖慢启动、增加内存抖动,甚至导致 readinessProbe 失败。
更隐蔽的问题是:某些构建流程会把本地 .m2 缓存或 IDE 生成的调试配置打进镜像,这些在容器里完全无用,还可能触发安全扫描告警。
- Dockerfile 里显式关闭 JFR:
JAVA_TOOL_OPTIONS=-XX:+DisableJFR - 移除所有
-Xdebug、-agentlib类 JVM 参数(CI 构建环境里保留,运行时镜像里删掉) - 优先用
openjdk:17-jre-slim而非jdk-slim,除非真需要jps/jstack在容器内诊断
滚动更新卡在 “Waiting for rollout to finish” 怎么查
Kubectl 等待超时不是因为网络或权限,大概率是新 Pod 卡在 pending 或 crashloopbackoff,但你没注意到。K8s 的 rollout 状态只看 ReplicaSet 扩容进度,不校验 Pod 是否真 ready。
最常漏掉的三件事:资源 request/limit 设置不合理、ServiceAccount 权限缺失、ConfigMap/Secret 挂载失败——它们都导致 Pod 启不来,但错误日志藏在 kubectl describe pod 的 Events 里,而不是 kubectl logs。
- 先看
kubectl get pods -w,确认新 Pod 是 Running 还是 Pending/CrashLoopBackOff - 如果是 Pending,立刻
kubectl describe pod <name></name>查 Events,重点看FailedScheduling或FailedMount - 如果是 CrashLoopBackOff,别急着看 logs,先
kubectl logs <pod> --previous</pod>,很多初始化失败只在上一轮容器里留痕











