K8s 滚动更新无损发布误区
RollingUpdate 解决的是"Pod 替换问题",从来不是"业务无感知问题"。 K8s 只保证"容器层可用"——Pod 启动、健康探针返回正常,它就认为没问题。 业务真正关心的"用户无感知",需要一整套流量治理体系来支撑。
一、RollingUpdate 的本质
Kubernetes 默认发布策略:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
核心逻辑只有两步,循环执行直到替换完成:
- 创建一部分新 Pod(
maxSurge控制最多多创建多少) - 删除一部分旧 Pod(
maxUnavailable控制最多能宕机多少)
它能保证的 3 个"基础底线":
- 不会一次性把所有 Pod 都删掉
- 集群中始终有 Pod 存活
- 最终 Deployment 收敛到新版本
但它不保证的"业务层面问题":
- ❌ 用户请求不丢失、不报错
- ❌ 流量能均匀切换到新旧 Pod
- ❌ 长连接、Session 不中断、不丢失
- ❌ 新旧版本接口、数据兼容
- ❌ 数据一致性,不会出现污染、错乱
二、最危险的时刻:新旧版本共存期
滚动更新真正的风险不是"升级完成后",而是"新旧版本同时在线"的那段时间。
2.1 数据库兼容翻车(最经典、最隐蔽)
v2 版本新增数据库字段后,v2 Pod 写入带新字段的数据,但 v1 Pod 不认识:
- JSON 反序列化失败,接口报错
- ORM 框架抛异常,无法处理新字段
- v1 Pod 处理数据时覆盖 v2 写入的字段
- 数据回写污染,旧逻辑破坏新数据
最可怕的是: 系统没挂、Pod 正常运行,但数据已悄悄坏了。等发现时回滚都救不回来。
2.2 接口协议不兼容(半故障状态)
新旧版本共存时,返回格式不同(如 {"code":0} vs {"success":true}),导致:
- 网关缓存、微服务调用、MQ 消费、RPC 接口全乱套
- "部分请求成功、部分失败",用户反复刷新才能访问
- 日志无明显报错,Pod 全是 Ready,排查极其困难
2.3 Session / 缓存不兼容
更新涉及缓存、登录态相关改动时:
- Redis Key 格式变化、JWT 结构调整
- Token 加密方式改变
- 部分用户频繁掉线,需要反复登录
- 部分请求报 401 权限错误,部分正常
三、Ready ≠ 真正可用
很多团队"自欺欺人"的 readinessProbe:
readinessProbe:
httpGet:
path: /healthz # 只检查 HTTP 200
应用启动后往往还需要一段时间才能真正"准备好":
| 问题 | 说明 |
|---|---|
| JVM 预热 | 刚启动时响应极慢,需要预热过程 |
| 本地缓存/配置 | 还没加载完成 |
| 依赖连接 | MQ、Elasticsearch、数据库等还没建立 |
| 数据预热 | 查询直接超时 |
结果: Pod 显示 Ready,流量瞬间打进来,应用直接超时、报错。
正确做法: 使用真正能反映业务就绪状态的探针。
| 探针类型 | 检查内容 | 建议 |
|---|---|---|
| 基础存活 | 进程存活 | /healthz 或 TCP 端口 |
| 业务就绪 | 依赖服务可用 + 缓存加载完成 + 数据预热完毕 | 自定义端点,聚合所有依赖状态 |
详见 k8s-probes-guide(探针完整配置指南)以及 pod-troubleshooting(三探针配合建议)。
四、最隐蔽的坑:"假灰度"陷阱
很多人以为滚动更新时流量均匀分配——这只是理想状态。
在以下场景中,流量分配严重不均:
| 场景 | 问题 |
|---|---|
| HTTP KeepAlive | 部分旧 Pod 持续占用老连接,新 Pod 几乎没流量 |
| HTTP/2 / gRPC / WebSocket | 长连接场景流量更难重新分配 |
后果: 你以为完成了"灰度验证"新版本没问题,但实际上新版本根本没被充分测试。直到最后一个旧 Pod 被删除,全部流量瞬间打到新版本——如果新版本有 bug,系统直接炸掉。
五、更安全的发布策略
5.1 蓝绿发布(Blue-Green Deployment)
Blue(旧版本)→ 验证 Green(新版本)→ 一次性切流
| 维度 | 说明 |
|---|---|
| 原理 | 两套完全独立的环境,验证无误后一次性切流 |
| 回滚 | 极其迅速,直接切回 Blue |
| 资源成本 | 高(需两套环境) |
| 数据同步 | 复杂 |
| 适合场景 | 核心业务、金融系统、支付系统、高 SLA 要求 |
5.2 金丝雀发布(Canary)
真正的灰度发布——按流量比例逐步放量,而非按 Pod 数量:
1% → 5% → 10% → 50% → 100%
更精细的放量策略:
- 先给内部员工放量 → 验证无问题再对外
- 按地域放量(北京先更、上海后更)
- 按用户等级放量(普通用户先更、VIP 最后更)
支撑工具: Istio / Linkerd(Service Mesh)、Argo Rollouts、Flagger、Nginx Ingress Canary
六、真正的"无感发布"体系
6.1 优雅终止(Graceful Shutdown)
不配置优雅终止的后果:Pod 删除瞬间 TCP 连接被强断,请求失败、长连接中断。
apiVersion: v1
kind: Pod
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 30 # 等待 LB 摘除流量
原理: Pod 进入 Terminating 状态 → preStop 执行 → 等待流量排空 → SIGTERM 发向主进程 → 进程优雅退出。
6.2 连接排空(Connection Draining)
Ingress、SLB、Service Mesh(如 Istio)必须配置连接排空,等待旧 Pod 上的连接自然结束后再删除 Pod。否则旧 Pod 已退出但 LB 还在转发流量 → 502 报错。
K8s Service 层面:Readiness Gate + terminationGracePeriodSeconds 配合。
6.3 真正的 Readiness 探针
不只是 /healthz 200 OK,要让探针真正检查业务就绪状态:
- 缓存是否加载完成
- 数据库、MQ 等依赖是否就绪
- 核心配置是否同步完成
6.4 可观测性驱动发布
不靠 kubectl rollout status,而靠实时监控数据判断:
- 接口错误率、RT、P99 延迟
- CPU、内存、GC 等资源指标
- 用户投诉、核心接口 SLA
一旦出现异常 → 自动回滚。这才是"可控发布"。
七、总结
RollingUpdate 不是最安全的发布策略,它只是 K8s 提供的最低保底方案。
真正成熟的生产发布,是一套组合拳:
RollingUpdate(基础能力)
+ 蓝绿/金丝雀(可控灰度)
+ 优雅终止 + 连接排空(流量治理)
+ 业务级 Readiness 探针(真实就绪)
+ 可观测性 + 自动回滚(安全网)
发布的核心不是"Pod 替换成功",而是"用户无感知、业务不中断"。
关联页面
| 页面 | 关联点 |
|---|---|
| k8s-probes-guide | 探针基础配置(readinessProbe 在滚动更新中的应用) |
| pod-troubleshooting | lifecycle hooks / preStop / 三探针配合建议 |
| k8s-service-access-troubleshooting | 连接排空与流量治理 |
| k8s-statefulset-guide | StatefulSet 有序滚动更新策略对比 |
| k8s-resource-limits-configuration | 资源限制与 JVM 预热配合 |
| service-troubleshooting | Service Endpoints 与滚动更新的关系 |
| k8s-cicd-architecture-guide | K8s CI/CD 全链路(Argo CD / Helm),滚动更新在 CI/CD 中的应用 |
| k8s-production-incident-case-studies | K8s 生产 10 大故障复盘(PDB/滚动更新/ConfigMap/HPA 等实战案例) |
| k8s-multicluster-istio-canary | K8s 多集群 + Istio 灰度发布与流量治理生产指南 — 全球多活架构、五层治理设计、Cana |
| k8s-load-balancing-deep-practice | K8s 负载均衡深度实践(连接排空/流量治理/无损发布场景互补) |