前言
kubectl set image 一敲,服务就完成了一次"无感升级"。这篇解剖滚动更新的完整机制——参数怎么调、探针怎么配合、出事怎么秒回滚,以及发布卡住的 N 种原因。
一、滚动更新是怎么转的
初始: [v1][v1][v1] replicas=3, 假设 maxSurge=1, maxUnavailable=0
步骤1: [v1][v1][v1][v2↑] 起1个v2(总数4=3+maxSurge), 等它 ready
步骤2: [v1][v1]↓[v2][v2↑] v2 ready 后删1个v1, 再起1个v2...
步骤3: [v1]↓[v2][v2][v2↑]
终态: [v2][v2][v2] 全部替换完成
两个参数控制节奏(Deployment 篇出现过,这里是深水区):
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多超出期望副本数几个(百分比也可: "25%")
maxUnavailable: 0 # 最多允许几个不可用
四种典型组合:
| 组合 | 行为 | 适用 |
|---|---|---|
| surge=1, unavailable=0 | 多起一个再删 → 容量不减 | 在线业务标配 ✅ |
| surge=0, unavailable=1 | 先删再起 → 资源省 | 内部服务/资源紧张 |
| surge=0, unavailable=0 | ❌ 死锁(无法开始) | 别这么写 |
| surge=50%, unavailable=0 | 批量多起 | 副本数大时加速 |
二、探针是滚动的"裁判"
替换的时机完全由就绪探针裁决:
readinessProbe:
httpGet: {path: /ready, port: 8080}
periodSeconds: 5
failureThreshold: 3
新 Pod: Running ≠ Ready
└─ ready 探针通过 → Endpoints 加入该 Pod → Deployment 才删下一个旧 Pod
└─ 一直不 ready → 滚动停滞(卡住时先查它!)
启动慢的应用(JVM/Spring)加 startupProbe,避免 liveness 误杀:
startupProbe:
httpGet: {path: /healthz, port: 8080}
failureThreshold: 60 # 60 × 2s = 允许 2 分钟启动
periodSeconds: 2
三、发布实操全流程
# 1. 触发(三种等价方式)
kubectl set image deploy/api api=reg.local/api:v1.5.0
kubectl edit deploy/api # 改 image 字段
kubectl apply -f api.yaml # 声明式(推荐, 走 CI)
# 2. 盯进度
kubectl rollout status deploy/api
# Waiting for deployment "api" rollout to finish: 2 of 3 updated...
# 3. 实时看 Pod 更替
kubectl get pods -w
# 4.(可选)金丝雀起手: 先只放一小部分
kubectl patch deploy api -p '{"spec":{"replicas":5}}' # 扩到5
kubectl set image deploy/api api=reg.local/api:v1.5.0
# 观察 v1.5.0 的 2/5 流量正常后再全量
四、回滚:历史与时光机
# 历史版本(revisionHistoryLimit 控制保留数, 默认10)
kubectl rollout history deploy/api
# deployment.apps/api
# REVISION CHANGE-CAUSE
# 3 kubectl set image api=v1.5.0
# 4 kubectl set image api=v1.6.0
# 看某版详情
kubectl rollout history deploy/api --revision=3
# 回上一个版本
kubectl rollout undo deploy/api
# 回指定版本
kubectl rollout undo deploy/api --to-revision=3
# 回滚也是一次"滚动"(零中断地滚回去)
kubectl rollout status deploy/api
--to-revision超过保留数的版本已不可回——重大配置变更前先备份 YAML:kubectl get deploy api -o yaml > api-v1.6.0.yaml
CHANGE-CAUSE 哪来的:kubectl apply 时加注解:
kubectl annotate deploy/api kubernetes.io/change-cause="release v1.6.0: fix payment bug"
五、暂停与恢复:一次改多个东西
改镜像又改环境变量又改资源——不想触发三轮滚动:
kubectl rollout pause deploy/api
kubectl set image deploy/api api=reg.local/api:v1.6.0
kubectl set env deploy/api LOG_LEVEL=debug
kubectl set resources deploy/api -c=api --limits=memory=512Mi
kubectl rollout resume deploy/api # 恢复后一轮滚动应用全部变更
注意 pause 期间 HPA 扩容仍会生效,但 Deployment 的 spec 更新不触发——用完记得 resume。
六、发布卡住排查地图
# 症状: rollout status 停在 "x of y updated"
kubectl get pods # 找到非 Ready 的 Pod
kubectl describe pod <pod> # 看 Events(九成答案在这)
| 卡住形态 | 原因 | 处理 |
|---|---|---|
| Pending | 资源不够/调度约束 | 加资源、改 requests、看节点余量 |
| ImagePullBackOff | 镜像拉不下来 | 检查 tag/仓库认证; 国内集群配镜像加速 |
| CrashLoopBackOff | 起来就崩 | logs --previous;环境变量/配置错误 |
| Running 但不 Ready | 就绪探针不过 | 容器内 curl 探针路径;依赖没起 |
| 卡在 Terminating | 旧 Pod 删不掉 | preStop 卡死/finalizer;优雅关停超时 |
优雅关停配置(加快滚动、减少请求失败):
spec:
terminationGracePeriodSeconds: 30
containers:
- name: api
lifecycle:
preStop:
exec: {command: ["sleep", "5"]} # 等 LB/IPVS 规则摘除流量
七、把发布做成"可审计"的
# 谁在什么时候发的什么
kubectl get events --field-selector involvedObject.name=api | tail
# 发布后自动验证(smoke test)
kubectl run smoke --rm -it --image=curlimages/curl --restart=Never -- \
curl -fsS http://api:8080/healthz
# CI 里的标准段(呼应 CI/CD 篇)
kubectl set image deploy/api api=$IMAGE
kubectl rollout status deploy/api --timeout=180s || \
(kubectl rollout undo deploy/api && exit 1) # 失败自动回滚!
小结
| 需求 | 命令/配置 |
|---|---|
| 零中断 | maxUnavailable=0 + readinessProbe |
| 慢启动 | startupProbe |
| 回滚 | rollout undo [–to-revision=N] |
| 批量改 | pause → 改 → resume |
| 卡住 | describe 看 Events 分诊 |
| 兜底 | rollout status 超时 → 自动 undo |
本文是「云原生」系列第 14 篇。