第 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。

bash 复制代码
更新前:
  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

滚动更新的节奏由两个关键参数控制,它们定义了你愿意承受多少冗余和多少风险:

默认情况下(副本数=3,maxSurge=25%,maxUnavailable=25%):

  • 最多可以临时创建 3 + roundUp(3×25%) = 4 个 Pod(多出 1 个)

  • 最多允许 1 个 Pod 处于不可用状态

更新过程大致是:先创建 1 个新 Pod → 等待它就绪 → 删除 1 个旧 Pod → 重复。这是 K8s 自动计算的最优节奏。你也可以显式指定:

bash 复制代码
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

maxUnavailable: 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.pyhello() 函数的返回行:

bash 复制代码
# 原 v2.0:return f'Hello World! I have been seen {count} times.\n'
# 新 v3.0:改为:
return f'Hello K8s! You are visitor #{count}.\n'

然后构建新镜像(第 5 篇学过的多阶段构建):

bash 复制代码
docker build -t flask-redis-counter:3.0 .
bash 复制代码
[+] Building 30.2s (15/15) FINISHED
 => exporting to image                                         2.1s
 => => naming to docker.io/library/flask-redis-counter:3.0    0.0s

在 Minikube 环境中加载镜像:由于 Minikube 运行在独立的 Docker 环境中,宿主机上的镜像不会被自动识别。需要手动加载:

bash 复制代码
minikube image load flask-redis-counter:3.0

确认 Minikube 集群中有正确版本的镜像可用:

bash 复制代码
minikube ssh docker images | grep flask-redis-counter

3.2 部署 v2.0 初始版本

bash 复制代码
kubectl apply -f flask-deployment-v2.yaml

v2.0 的 Deployment YAML 包含我们第 25 篇的配置,image: flask-redis-counter:2.0replicas: 3

bash 复制代码
kubectl get pods -l app=flask-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   30s

3.3 触发滚动更新

bash 复制代码
kubectl set image deployment/flask-deployment flask=flask-redis-counter:3.0
# deployment.apps/flask-deployment image updated

或者直接编辑 YAML:

bash 复制代码
kubectl edit deployment flask-deployment
# 找到 image: flask-redis-counter:2.0,改为 3.0,保存退出

3.4 实时观察滚动更新过程

在终端 A 中持续观察 Pod 变化:

bash 复制代码
kubectl get pods -l app=flask-counter -w

输出示例:

bash 复制代码
NAME                                READY   STATUS              AGE
flask-deployment-7b8c9d6f5f-abcde   1/1     Running             5m
flask-deployment-7b8c9d6f5f-def34   1/1     Running             5m
flask-deployment-7b8c9d6f5f-ghi56   1/1     Running             5m
# ↑ 旧 ReplicaSet 的 3 个 Pod 都在运行

flask-deployment-8f9a0b1c2d3-new1   0/1     ContainerCreating   0s
flask-deployment-8f9a0b1c2d3-new1   1/1     Running             5s
# ↑ 新 ReplicaSet 创建了第 1 个 Pod 并开始运行
flask-deployment-7b8c9d6f5f-abcde   1/1     Terminating         0s
# ↑ 旧 ReplicaSet 的一个 Pod 开始终止

flask-deployment-8f9a0b1c2d3-new2   0/1     ContainerCreating   0s
flask-deployment-8f9a0b1c2d3-new2   1/1     Running             5s
flask-deployment-7b8c9d6f5f-def34   1/1     Terminating         0s
# ↑ 继续逐对替换......

同时查看 Deployment 的更新状态:

bash 复制代码
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 持续访问:

bash 复制代码
# 在另一个终端中执行(先创建 Service 或直接转发到 Deployment)
kubectl port-forward deployment/flask-deployment 5000:5000
# 在第三个终端中循环请求
while true; do curl -s http://localhost:5000 && sleep 0.5; done

你会看到,整个更新过程中请求始终得到响应,没有一次连接被拒绝------这就是滚动更新的"零停机"能力。

3.5 验证新旧 ReplicaSet

bash 复制代码
kubectl get replicaset
# NAME                           DESIRED   CURRENT   READY   AGE
# flask-deployment-7b8c9d6f5f    0         0         0       10m   ← 旧 RS,Pod 已清零
# flask-deployment-8f9a0b1c2d3   3         3         3       2m    ← 新 RS,接管所有流量

旧 ReplicaSet 虽然 Pod 数为 0,但对象本身保留------这为回滚保留了可能。当你回滚时,Deployment Controller 不会创建全新的 ReplicaSet,而是直接恢复旧 ReplicaSet 的副本数。

3.6 验证更新效果

bash 复制代码
kubectl port-forward deployment/flask-deployment 5000:5000
bash 复制代码
curl http://localhost:5000
# Hello K8s! You are visitor #42.   ← v3.0 的新文案

3.7 发现问题,紧急回滚

假设 v3.0 上线后发现了一个严重 Bug(比如返回值没有换行符导致客户端解析错误)。我们需要立刻回滚到 v2.0。

方法一:回滚到上一个版本

bash 复制代码
kubectl rollout undo deployment/flask-deployment
# deployment.apps/flask-deployment rolled back

rollout undo 会将 Deployment 回滚到上一次 Revision 的 Pod 模板------包括镜像、环境变量、资源限制等全部回退。这比手动 set image 更安全,因为它恢复了完整的配置快照,而不仅仅是镜像版本。

方法二:回滚到指定历史版本

bash 复制代码
# 查看版本历史
kubectl rollout history deployment/flask-deployment
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         <none>

# 回滚到 Revision 1
kubectl rollout undo deployment/flask-deployment --to-revision=1

查看回滚效果:

bash 复制代码
kubectl rollout history deployment/flask-deployment
# REVISION  CHANGE-CAUSE
# 2         <none>
# 3         <none>    ← 回滚操作本身也会产生一个新 Revision

kubectl get replicaset
# 旧 ReplicaSet 重新获得副本,新 ReplicaSet 的 Pod 逐渐缩减到 0

验证服务恢复:

bash 复制代码
curl http://localhost:5000
# Hello World! I have been seen 43 times.   ← 恢复 v2.0 的原文案,计数器持续递增!

计数器没有归零------这是因为 Redis 的数据通过 Volume 持久化,Deployment 更新或回滚不影响数据层的状态。这正是第 34 篇要讲的 PV/PVC 机制的实际价值。

3.8 查看 Deployment 事件日志

bash 复制代码
kubectl describe deployment flask-deployment | grep -A10 "Events:"

Events 部分记录了从创建到每次扩容、更新、回滚的完整时间线------Scaled up replica setScaled down replica set,每一步都清晰可审计。

四、暂停与恢复

在某些场景下(比如想执行多个配置修改后再统一触发更新,或验证新 Pod 行为但不确定是否全部推广),你需要暂停 Deployment 的自动调和:

bash 复制代码
# 暂停
kubectl rollout pause deployment/flask-deployment
# deployment.apps/flask-deployment paused

# 此时修改镜像版本或其他配置
kubectl set image deployment/flask-deployment flask=flask-redis-counter:3.0
# 命令执行成功,但 Pod 不会被更新(Deployment 已暂停)

# 恢复
kubectl rollout resume deployment/flask-deployment
# deployment.apps/flask-deployment resumed
# 此时之前累积的所有修改会一次性触发滚动更新

暂停机制对金丝雀发布(Canary Deployment)非常有用:你可以先暂停 Deployment,手动调高一个 ReplicaSet 的副本数做小范围验证,确认无误后再恢复自动调和。

五、完整发布流程总结

经过这一篇的学习,一个标准的发布流程现在已经在你手中成型:

bash 复制代码
1. docker build + minikube image load     ← 构建并加载新版本镜像
2. kubectl set image ...                  ← 触发滚动更新
3. kubectl rollout status ...             ← 实时观察更新进度
4. curl 验证                              ← 确认新版本行为正常
5. 发现问题 → kubectl rollout undo ...    ← 一键回滚
6. kubectl rollout history ...            ← 事后审计版本历史

六、命令速查表

七、本篇总结

  • 滚动更新的机制 :Deployment 创建新 ReplicaSet,逐步增加新 Pod、减少旧 Pod,通过 maxSurgemaxUnavailable 控制节奏。

  • 零停机更新maxUnavailable: 0 确保始终有足够 Pod 处理流量,更新过程中服务不中断。

  • 回滚能力 :每次更新生成新 Revision,kubectl rollout undo 一键回滚到历史版本,旧 ReplicaSet 保留完整配置快照。

  • 暂停机制pauseresume 提供手动控制更新时机的窗口,适合金丝雀发布等高级策略。

  • 从 Compose 到 Deployment :Compose 的 docker compose down && docker compose up -d 必然中断服务,而 Deployment 滚动更新实现了真正的平滑过渡。

至此,Deployment 的核心能力你已经掌握。但控制器不止 Deployment 一种------有些场景需要每个节点都运行一个 Pod,有些需要执行一次性批处理任务。下一篇------第 27 篇:更多控制器:DaemonSet、Job 与 CronJob,我们将解锁这三类控制器,完善你的控制器知识版图。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
老毛肚1 小时前
Docker Desktop 介绍
运维·docker·容器
张忠琳1 小时前
【kubernetes v1.21】(kubelet 2)容器运行时与CRI
云原生·架构·kubernetes·kubelet
张忠琳1 小时前
【kubernetes v1.21】(kubelet 3)PLEG、健康检查、Eviction 与状态管理
云原生·架构·kubernetes·kubelet
秋漓2 小时前
Kubernetes了解与应用
云原生·容器·kubernetes
IT策士2 小时前
第28篇 k8s之Service:为 Pod 提供稳定的访问入口
云原生·容器·kubernetes
张忠琳2 小时前
【kubernetes v1.21】(kube-scheduler 4)kube-scheduler 内部缓存、队列与抢占机制
云原生·架构·kubernetes
苏渡苇2 小时前
Seata 番外篇:使用 docker-compose 部署 Seata Server(TC)及 K8S 部署 Seata 高可用
spring boot·docker·微服务·容器·kubernetes·seata·springcloud
JP-Destiny2 小时前
docker报错-无法解析 registry-1.docker.io
运维·docker·容器
IT策士3 小时前
第29篇 k8s之Service 与 Endpoints 深入:服务发现原理
容器·kubernetes·服务发现