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

📅 2026/7/25 15:52:03
Kubernetes 滚动更新与回滚实战:maxSurge、maxUnavailable 与卡住的排查
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:5replicas: 4、maxSurge: 25%、maxUnavailable: 25%时:25% 向上取整是 1,所以更新过程中最多存在 5 个 Pod(41),最少有 3 个可用(4-1)。K8s 会先起 1 个新 Pod,等它 ready 再干掉 1 个旧的,如此滚动。这里最容易被忽略的是readinessProbe。没有它,K8s 认为容器一启动就「可用」,于是刚拉起还没加载完的新 Pod 立刻接流量,旧 Pod 同时被删——用户直接撞上一批 502。滚动更新的平滑,完全依赖 readinessProbe 如实反映「我准备好了没」。maxSurge 和 maxUnavailable 怎么配这两个参数决定了「更新速度」和「更新期间的容量」之间的权衡:要零容量损失(更新时始终有 4 个可用):maxUnavailable: 0maxSurge: 1。先扩出新 Pod 再缩旧的,任何时刻可用数不低于 4。代价是更新时集群要多扛 1 份资源。要省资源、能接受短暂降容:maxSurge: 0maxUnavailable: 1。先删 1 个旧的腾出位置再起新的,总数不超过 4,但可用数会掉到 3。# 生产推荐:更新期间容量不打折rollingUpdate:maxSurge:1maxUnavailable:0注意两者不能同时为 0,否则谁都不能动,更新永远无法推进,apply会直接被拒绝。更新卡住了怎么查敲完kubectl apply后,标准动作是盯住 rollout 状态:# 实时观察滚动进度,卡住时会一直停在某一步kubectl rollout status deployment/web--timeout120s如果它迟迟不完成,按这个顺序排查:# 1. 看新旧 ReplicaSet 各自的副本情况,定位卡在哪kubectl get rs-lappweb# 新 RS 的 READY 一直上不去,就是新 Pod 起不来# 2. 直接看新 Pod 状态和事件kubectl get pods-lappweb kubectl describe pod卡住的新pod# 重点看 Events:镜像拉不下来(ImagePullBackOff)、readiness 一直失败、资源不足 Pending最常见的三个卡点:镜像标签写错 / 私有仓库没配 imagePullSecret→ Pod 卡在ImagePullBackOff。新 RS 永远起不来,但因为maxUnavailable保护,旧 Pod 不会被删,所以服务其实还在跑,你有时间从容修。readinessProbe 永远不通过(健康检查路径写错、端口不对)→ 新 Pod 一直Running但READY 0/1,K8s 不敢删旧的,卡死。资源不够,新 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-revision3回滚本质也是一次滚动更新——K8s 把旧 ReplicaSet 重新扩容、把当前的缩容,同样遵守 maxSurge/maxUnavailable,所以回滚过程也是平滑的。想让 history 里每个版本都有意义的说明,apply 时带上变更原因注解:kubectl annotate deployment/web\kubernetes.io/change-causeupgrade to v2, add cache layer--overwrite这样rollout history每一行都能看到干了什么,而不是一堆none。一个实战技巧:更新前先暂停,批量改完再一次放行要同时改镜像、环境变量、副本数,每次 apply 都会触发一次滚动,滚三遍很浪费。用 pause 把更新冻住,改完再 resume:kubectl rollout pause deployment/web kubectlsetimage deployment/webwebmyapp:v2 kubectlsetenvdeployment/webLOG_LEVELdebug kubectl scale deployment/web--replicas6kubectl 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 决定退得快不快。