背景
很多团队第一次把服务上到 Kubernetes 时,会觉得滚动发布已经帮我们解决了“不中断发布”的问题。
实际上,RollingUpdate 只是开始,不是答案。
线上真正导致发布抖动的,往往是这些细节:
- 新 Pod 还没准备好就被加进流量
- 老 Pod 收到
SIGTERM后立刻退出 - 长连接请求被中途切断
- readiness 和 liveness 配置混乱
- ingress、service、应用本身三层状态不同步
想做到真正意义上的零中断,得把整条链路串起来看。
先理解滚动发布到底做了什么
Deployment 默认使用 RollingUpdate 策略,大致过程是:
- 拉起新 Pod
- 等新 Pod Ready
- 逐步减少旧 Pod
- 直到新版本全部替换完成
一个常见配置如下:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
这表示:
- 发布期间不允许可用实例减少
- 每次最多多起一个新 Pod
它能降低抖动概率,但不能保证应用层面的请求一定不受影响。
Readiness Probe 决定流量什么时候进来
如果只配了 liveness,没有配 readiness,基本等于告诉集群:
“只要进程还活着,就可以接流量。”
这在很多服务里是错的。
比如一个 API 服务启动后还要做这些事情:
- 加载配置
- 预热缓存
- 建立数据库连接池
- 初始化路由和依赖客户端
在这些动作完成之前,进程虽然活着,但根本不适合接请求。
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
这两个探针不要混用:
/health关注“进程是不是还活着”/ready关注“服务现在能不能接流量”
PreStop 和优雅停机必须配套
另一个常见误区是:Pod 删除时,Kubernetes 会帮你优雅处理一切。
实际上 Pod 被终止时,只会做标准动作:
- 发送
SIGTERM - 等待宽限时间
- 超时后
SIGKILL
如果你的应用收到 SIGTERM 立刻退出,正在处理的请求照样会断。
可以在 Pod 里先加一个 preStop,给流量切走留一点时间窗口:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 30
但只 sleep 还不够。应用本身也必须支持优雅停机:
- 停止接收新请求
- 继续处理当前请求
- 关闭连接池和后台任务
- 在超时前主动退出
否则 sleep 5 只是把问题往后推了 5 秒。
长连接场景更容易出问题
如果服务有下面这些能力,发布时要特别小心:
- WebSocket
- SSE
- gRPC stream
- 大文件上传下载
这类连接天然更长。只要下线动作太激进,就会直接把业务打断。
我的做法通常是:
- Readiness 先失败,停止新流量进入
- 服务进入 draining 状态
- 给已有连接一段收尾时间
- 到达上限再强制退出
这和普通短请求服务的发布策略不应该完全一样。
一个更稳的 Deployment 配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
image: example/user-service:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
这份 YAML 不复杂,但它至少把几件关键事情补齐了:
- 发布期间不减可用实例
- 新 Pod Ready 前不接流量
- 旧 Pod 下线前预留缓冲时间
监控发布过程,而不是“点完就走”
很多发布事故不是配置错,而是没人看发布过程。
建议至少盯这几类指标:
- 发布期间 5xx 是否升高
- p95/p99 延迟是否突增
- readiness 失败次数
- Pod 重启次数
- Ingress / 网关层 upstream error
如果这些指标在发布窗口里波动明显,就说明你的“零中断”大概率只是看起来没挂。
常见坑点
1. 就绪接口依赖太重
/ready 最好只检查当前实例是否可接流量,不要把所有下游依赖都塞进去,否则一个依赖抖动就会把整批 Pod 标成 not ready。
2. terminationGracePeriodSeconds 配太短
应用要 20 秒才能把长请求收干净,你只给 5 秒,那最终还是硬杀。
3. maxUnavailable 配错
副本本来就不多的服务,如果发布时还允许实例减少,容量会马上变得很紧。
4. 只测功能,不测发布
很多服务功能测试都过了,真正出问题的是发布阶段。滚动升级、故障注入、优雅停机都应该单独验证。
总结
Kubernetes 想做到零中断发布,核心不只是 Deployment 策略,而是整条生命周期是否一致:
- 新实例什么时候算 ready
- 旧实例什么时候停止接流量
- 现有请求如何收尾
- 平台给了多长退出时间
这些环节有一个没对齐,发布就可能抖。
真正稳定的线上发布,不靠运气,靠的是应用和平台在细节上的配合。
发布系统最忌讳“平时不测,出事再猜”。零中断本身也应该被当作一个可验证的能力。