Kubernetes 滚动更新与回滚实战:maxSurge、maxUnavailable 与卡住的排查
2026/7/25 15:52:02 网站建设 项目流程

Kubernetes 滚动更新与回滚实战:maxSurge、maxUnavailable 与卡住的排查

发新版本时kubectl apply一敲,Deployment 就开始滚动更新。顺利时你没感觉,出问题时才发现:更新卡在一半不动、旧 Pod 全没了导致瞬间不可用、或者滚完才发现新版本有 bug 想退回去却手忙脚乱。这篇把滚动更新的两个核心参数、卡住怎么查、怎么干净回滚,一次讲清楚。

默认滚动更新到底做了什么

先看一个最小 Deployment:

apiVersion:apps/v1kind:Deploymentmetadata:name:webspec:replicas:4strategy:type:RollingUpdaterollingUpdate:maxSurge:25%# 更新时最多可以超出期望副本数多少maxUnavailable:25%# 更新时最多允许多少副本不可用selector:matchLabels:{app:web}template:metadata:labels:{app:web}spec:containers:-name:webimage:myapp:v1readinessProbe:# 关键:没有它滚动更新等于裸奔httpGet:{path:/healthz,port:8080}initialDelaySeconds:3periodSeconds:5

replicas: 4maxSurge: 25%maxUnavailable: 25%时:25% 向上取整是 1,所以更新过程中最多存在 5 个 Pod(4+1),最少有 3 个可用(4-1)。K8s 会先起 1 个新 Pod,等它 ready 再干掉 1 个旧的,如此滚动。

这里最容易被忽略的是readinessProbe。没有它,K8s 认为容器一启动就「可用」,于是刚拉起还没加载完的新 Pod 立刻接流量,旧 Pod 同时被删——用户直接撞上一批 502。滚动更新的平滑,完全依赖 readinessProbe 如实反映「我准备好了没」

maxSurge 和 maxUnavailable 怎么配

这两个参数决定了「更新速度」和「更新期间的容量」之间的权衡:

  • 要零容量损失(更新时始终有 4 个可用):maxUnavailable: 0+maxSurge: 1。先扩出新 Pod 再缩旧的,任何时刻可用数不低于 4。代价是更新时集群要多扛 1 份资源。
  • 要省资源、能接受短暂降容:maxSurge: 0+maxUnavailable: 1。先删 1 个旧的腾出位置再起新的,总数不超过 4,但可用数会掉到 3。
# 生产推荐:更新期间容量不打折rollingUpdate:maxSurge:1maxUnavailable:0

注意两者不能同时为 0,否则谁都不能动,更新永远无法推进,apply会直接被拒绝。

更新卡住了怎么查

敲完kubectl apply后,标准动作是盯住 rollout 状态:

# 实时观察滚动进度,卡住时会一直停在某一步kubectl rollout status deployment/web--timeout=120s

如果它迟迟不完成,按这个顺序排查:

# 1. 看新旧 ReplicaSet 各自的副本情况,定位卡在哪kubectl get rs-lapp=web# 新 RS 的 READY 一直上不去,就是新 Pod 起不来# 2. 直接看新 Pod 状态和事件kubectl get pods-lapp=web kubectl describe pod<卡住的新pod># 重点看 Events:镜像拉不下来(ImagePullBackOff)、readiness 一直失败、资源不足 Pending

最常见的三个卡点:

  1. 镜像标签写错 / 私有仓库没配 imagePullSecret→ Pod 卡在ImagePullBackOff。新 RS 永远起不来,但因为maxUnavailable保护,旧 Pod 不会被删,所以服务其实还在跑,你有时间从容修。
  2. readinessProbe 永远不通过(健康检查路径写错、端口不对)→ 新 Pod 一直RunningREADY 0/1,K8s 不敢删旧的,卡死。
  3. 资源不够,新 Pod 一直 Pending→ 节点没有足够 CPU/内存满足 requests。

progressDeadlineSeconds(默认 600s)到了还没滚完,Deployment 会被标记为ProgressDeadlineExceeded,但它不会自动回滚,只是把状态告诉你。回滚得你自己来。

回滚:一条命令退回上个版本

发现新版本有 bug,别去手改 image 重新 apply(慢且容易错),直接用 rollout 回滚:

# 看历史版本kubectl rollouthistorydeployment/web# 回滚到上一个版本kubectl rollout undo deployment/web# 回滚到指定版本号kubectl rollout undo deployment/web --to-revision=3

回滚本质也是一次滚动更新——K8s 把旧 ReplicaSet 重新扩容、把当前的缩容,同样遵守 maxSurge/maxUnavailable,所以回滚过程也是平滑的。

想让 history 里每个版本都有意义的说明,apply 时带上变更原因注解:

kubectl annotate deployment/web\kubernetes.io/change-cause="upgrade to v2, add cache layer"--overwrite

这样rollout history每一行都能看到干了什么,而不是一堆<none>

一个实战技巧:更新前先暂停,批量改完再一次放行

要同时改镜像、环境变量、副本数,每次 apply 都会触发一次滚动,滚三遍很浪费。用 pause 把更新冻住,改完再 resume:

kubectl rollout pause deployment/web kubectlsetimage deployment/webweb=myapp:v2 kubectlsetenvdeployment/webLOG_LEVEL=debug kubectl scale deployment/web--replicas=6kubectl rollout resume deployment/web# 这时才开始滚动,只滚一遍

小结

  • 滚动更新的平滑完全依赖 readinessProbe,没配它更新期间必然掉流量。
  • maxSurge管「能多几个」,maxUnavailable管「能少几个」;要零降容用maxSurge:1 + maxUnavailable:0,两者不能同时为 0。
  • 卡住排查三板斧:rollout status看进度 →get rs/pods定位 →describe pod看 Events;八成是拉镜像、readiness、资源不足其一。
  • 超时(progressDeadlineSeconds)不会自动回滚,得手动rollout undo
  • 回滚用kubectl rollout undo,它也是平滑滚动;配change-cause注解让历史可读。

一句话记忆点:readiness 决定滚得平不平,maxUnavailable 决定崩不崩,undo 决定退得快不快

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询