第 26 篇 k8s之Deployment 进阶:滚动更新、回滚与暂停 IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章助你少走弯路。大家好我是 IT 策士。在第 25 篇中我们通过 Deployment 把 Flask 应用从裸 Pod 升级到了声明式副本控制——有了自愈能力删掉 Pod 会自动补充。但这只解决了“运行态”的问题。现实中更频繁的场景是代码改了新镜像打好了怎么让集群里的 Pod 平滑切换到新版本在 Docker Compose 时代更新意味着docker compose down docker compose up -d——服务中断是必然的。一个完整的部署周期不仅要“跑起来”更要在新版本上线、紧急回滚、灰度验证等场景中做到可控和安全。Kubernetes Deployment 为此提供了三个核心能力滚动更新、版本回滚、暂停恢复。今天这篇就来拆解这些机制并带着贯穿案例的 Flask 应用完整体验一次“从 v2.0 升级到 v3.0发现问题后回滚”的真实流程。一、为什么需要滚动更新先看一个典型场景你的 Flask 计数器应用 v2.0 正在生产环境中运行 3 个副本。现在要发布 v3.0比如改了返回值文案。你有两种最直观的做法做法 A先删光旧 Pod再建新 Pod。删光期间服务全挂——生产事故。做法 B新建 3 个新 Pod等它们就绪后再删旧的。这期间同时运行 6 个 Pod需要双倍资源。如果你的集群资源不够新 Pod 调度失败更新就卡住了。Kubernetes 提供了第三种做法滚动更新Rolling Update。它通过逐步替换的方式——一次只替换一小部分 Pod保证服务始终在线也不需要额外资源。整个过程由 Deployment Controller 自动执行你只需要改一行镜像版本。对比 Compose 时代docker compose down docker compose up -d必然导致短暂停机即使加上健康检查也无法做到零中断。而 Deployment 滚动更新是 K8s 声明式管理在生产环境中最直接的体现——你声明“我要用新镜像”控制器自动编排替换过程。二、滚动更新机制详解2.1 ReplicaSet 的版本管理滚动更新的秘密在于 Deployment 如何管理 ReplicaSet。每次你修改 Pod 模板主要是镜像版本Deployment 都会创建一个新的 ReplicaSet然后逐步将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet。更新前 Deployment(3副本, v2.0)└── ReplicaSet-A(3个 Pod: v2.0)更新中触发镜像变更后 Deployment(3副本, v3.0)├── ReplicaSet-A(2个 Pod: v2.0)← 逐渐减少 └── ReplicaSet-B(1个 Pod: v3.0)← 逐渐增加 更新完成 Deployment(3副本, v3.0)└── ReplicaSet-B(3个 Pod: v3.0)2.2 maxSurge 与 maxUnavailable滚动更新的节奏由两个关键参数控制它们定义了你愿意承受多少冗余和多少风险默认情况下副本数3maxSurge25%maxUnavailable25%最多可以临时创建 3 roundUp(3×25%) 4 个 Pod多出 1 个最多允许 1 个 Pod 处于不可用状态更新过程大致是先创建 1 个新 Pod → 等待它就绪 → 删除 1 个旧 Pod → 重复。这是 K8s 自动计算的最优节奏。你也可以显式指定spec: strategy: type: RollingUpdate rollingUpdate: maxSurge:1maxUnavailable:0maxUnavailable: 0表示零停机——在任何一个旧 Pod 被删除之前必须先有一个新 Pod 就绪。这是对可用性要求极高的生产环境常用配置但代价是更新速度会变慢。2.3 minReadySeconds 与 progressDeadlineSeconds除了滚动节奏Deployment 还提供了两个时间控制参数来定义“就绪”和“超时”minReadySeconds新 Pod 启动后必须稳定运行多长时间才被视为“就绪”。这可以防止 Pod 刚通过 readiness probe 就被立即接入流量随后又因为瞬时负载崩溃。默认值为 0适合大多数场景如果你的应用启动后有预热期建议设为 10-30 秒。progressDeadlineSeconds整个更新过程的最大时长。如果超过这个时间仍未完成Deployment 会被标记为Failed。默认值为 600 秒10 分钟。如果你的应用启动时间较长如 Java 应用可能需要 60-90 秒初始化应适当增大此值。三、实战从 v2.0 升级到 v3.0 并回滚下面把理论落地用一个真实流程演示滚动更新和回滚。3.1 准备工作构建 v3.0 镜像首先给 Flask 应用换一个返回值用于区分新旧版本。修改app.py中hello()函数的返回行# 原 v2.0return fHello World! I have been seen {count} times.\n# 新 v3.0改为returnfHello K8s! You are visitor #{count}.\n然后构建新镜像第 5 篇学过的多阶段构建dockerbuild-tflask-redis-counter:3.0.[]Building30.2s(15/15)FINISHEDexporting to image2.1snaming to docker.io/library/flask-redis-counter:3.00.0s在 Minikube 环境中加载镜像由于 Minikube 运行在独立的 Docker 环境中宿主机上的镜像不会被自动识别。需要手动加载minikube image load flask-redis-counter:3.0确认 Minikube 集群中有正确版本的镜像可用minikubesshdockerimages|grepflask-redis-counter3.2 部署 v2.0 初始版本kubectl apply-fflask-deployment-v2.yamlv2.0 的 Deployment YAML 包含我们第 25 篇的配置image: flask-redis-counter:2.0replicas: 3。kubectl get pods-lappflask-counter# NAME READY STATUS AGE# flask-deployment-7b8c9d6f5f-abcde 1/1 Running 30s# flask-deployment-7b8c9d6f5f-def34 1/1 Running 30s# flask-deployment-7b8c9d6f5f-ghi56 1/1 Running 30s3.3 触发滚动更新kubectlsetimage deployment/flask-deploymentflaskflask-redis-counter:3.0# deployment.apps/flask-deployment image updated或者直接编辑 YAMLkubectl edit deployment flask-deployment# 找到 image: flask-redis-counter:2.0改为 3.0保存退出3.4 实时观察滚动更新过程在终端 A 中持续观察 Pod 变化kubectl get pods-lappflask-counter-w输出示例NAME READY STATUS AGE flask-deployment-7b8c9d6f5f-abcde1/1 Running 5m flask-deployment-7b8c9d6f5f-def341/1 Running 5m flask-deployment-7b8c9d6f5f-ghi561/1 Running 5m# ↑ 旧 ReplicaSet 的 3 个 Pod 都在运行flask-deployment-8f9a0b1c2d3-new10/1 ContainerCreating 0s flask-deployment-8f9a0b1c2d3-new11/1 Running 5s# ↑ 新 ReplicaSet 创建了第 1 个 Pod 并开始运行flask-deployment-7b8c9d6f5f-abcde1/1 Terminating 0s# ↑ 旧 ReplicaSet 的一个 Pod 开始终止flask-deployment-8f9a0b1c2d3-new20/1 ContainerCreating 0s flask-deployment-8f9a0b1c2d3-new21/1 Running 5s flask-deployment-7b8c9d6f5f-def341/1 Terminating 0s# ↑ 继续逐对替换……同时查看 Deployment 的更新状态kubectl rollout status deployment/flask-deployment# Waiting for rollout to finish: 1 old replicas are pending termination...# deployment flask-deployment successfully rolled out验证服务不中断在更新过程中通过kubectl port-forward持续访问# 在另一个终端中执行先创建 Service 或直接转发到 Deploymentkubectl port-forward deployment/flask-deployment5000:5000# 在第三个终端中循环请求whiletrue;docurl-shttp://localhost:5000sleep0.5;done你会看到整个更新过程中请求始终得到响应没有一次连接被拒绝——这就是滚动更新的“零停机”能力。3.5 验证新旧 ReplicaSetkubectl get replicaset# NAME DESIRED CURRENT READY AGE# flask-deployment-7b8c9d6f5f 0 0 0 10m ← 旧 RSPod 已清零# flask-deployment-8f9a0b1c2d3 3 3 3 2m ← 新 RS接管所有流量旧 ReplicaSet 虽然 Pod 数为 0但对象本身保留——这为回滚保留了可能。当你回滚时Deployment Controller 不会创建全新的 ReplicaSet而是直接恢复旧 ReplicaSet 的副本数。3.6 验证更新效果kubectl port-forward deployment/flask-deployment5000:5000curlhttp://localhost:5000# Hello K8s! You are visitor #42. ← v3.0 的新文案3.7 发现问题紧急回滚假设 v3.0 上线后发现了一个严重 Bug比如返回值没有换行符导致客户端解析错误。我们需要立刻回滚到 v2.0。方法一回滚到上一个版本kubectl rollout undo deployment/flask-deployment# deployment.apps/flask-deployment rolled backrollout undo会将 Deployment 回滚到上一次 Revision 的 Pod 模板——包括镜像、环境变量、资源限制等全部回退。这比手动set image更安全因为它恢复了完整的配置快照而不仅仅是镜像版本。方法二回滚到指定历史版本# 查看版本历史kubectl rollouthistorydeployment/flask-deployment# REVISION CHANGE-CAUSE# 1 none# 2 none# 回滚到 Revision 1kubectl rollout undo deployment/flask-deployment --to-revision1查看回滚效果kubectl rollouthistorydeployment/flask-deployment# REVISION CHANGE-CAUSE# 2 none# 3 none ← 回滚操作本身也会产生一个新 Revisionkubectl get replicaset# 旧 ReplicaSet 重新获得副本新 ReplicaSet 的 Pod 逐渐缩减到 0验证服务恢复curlhttp://localhost:5000# Hello World! I have been seen 43 times. ← 恢复 v2.0 的原文案计数器持续递增计数器没有归零——这是因为 Redis 的数据通过 Volume 持久化Deployment 更新或回滚不影响数据层的状态。这正是第 34 篇要讲的 PV/PVC 机制的实际价值。3.8 查看 Deployment 事件日志kubectl describe deployment flask-deployment|grep-A10Events:Events 部分记录了从创建到每次扩容、更新、回滚的完整时间线——Scaled up replica set、Scaled down replica set每一步都清晰可审计。四、暂停与恢复在某些场景下比如想执行多个配置修改后再统一触发更新或验证新 Pod 行为但不确定是否全部推广你需要暂停 Deployment 的自动调和# 暂停kubectl rollout pause deployment/flask-deployment# deployment.apps/flask-deployment paused# 此时修改镜像版本或其他配置kubectlsetimage deployment/flask-deploymentflaskflask-redis-counter:3.0# 命令执行成功但 Pod 不会被更新Deployment 已暂停# 恢复kubectl rollout resume deployment/flask-deployment# deployment.apps/flask-deployment resumed# 此时之前累积的所有修改会一次性触发滚动更新暂停机制对金丝雀发布Canary Deployment非常有用你可以先暂停 Deployment手动调高一个 ReplicaSet 的副本数做小范围验证确认无误后再恢复自动调和。五、完整发布流程总结经过这一篇的学习一个标准的发布流程现在已经在你手中成型1.dockerbuild minikube image load ← 构建并加载新版本镜像2. kubectlsetimage... ← 触发滚动更新3. kubectl rollout status... ← 实时观察更新进度4.curl验证 ← 确认新版本行为正常5. 发现问题 → kubectl rollout undo... ← 一键回滚6. kubectl rollouthistory... ← 事后审计版本历史六、命令速查表七、本篇总结滚动更新的机制Deployment 创建新 ReplicaSet逐步增加新 Pod、减少旧 Pod通过maxSurge和maxUnavailable控制节奏。零停机更新maxUnavailable: 0确保始终有足够 Pod 处理流量更新过程中服务不中断。回滚能力每次更新生成新 Revisionkubectl rollout undo一键回滚到历史版本旧 ReplicaSet 保留完整配置快照。暂停机制pause和resume提供手动控制更新时机的窗口适合金丝雀发布等高级策略。从 Compose 到 DeploymentCompose 的docker compose down docker compose up -d必然中断服务而 Deployment 滚动更新实现了真正的平滑过渡。至此Deployment 的核心能力你已经掌握。但控制器不止 Deployment 一种——有些场景需要每个节点都运行一个 Pod有些需要执行一次性批处理任务。下一篇——第 27 篇更多控制器DaemonSet、Job 与 CronJob我们将解锁这三类控制器完善你的控制器知识版图。想了解更多还可以去各个平台搜索「IT策士」一起升级 IT 思维