文章目录
- Deployment 滚动更新机制详解(企业级面试与运维实战)
-
- 一、Deployment 如何管理 Pod?(三层级联关系)
- 二、滚动更新完整流程(以 3 副本为例)
-
- 第一阶段:创建新 RS
- 第二阶段:逐步滚动(以默认策略 maxSurge=25%, maxUnavailable=25% 为例)
- 第三阶段:完成
- 三、滚动更新核心参数(面试必考)
-
- 1. maxUnavailable(最大不可用数)
- 2. maxSurge(最大峰值数)
- 3. 两个参数如何配合?
- 四、滚动更新的状态与控制(运维核心技能)
-
- 1. 查看更新进度
- 2. 查看历史版本(每个版本对应一个 RS)
- 3. 暂停与恢复(金丝雀发布的核心手法)
- 4. 手动控制更新比例(精细灰度)
- 5. 回滚
- 五、滚动更新卡住的排查(生产环境最高频问题)
-
- 现象
- 排查思路(四步定位)
- 六、滚动更新故障与应对(运维实战)
-
- 场景 1:新版本有严重 Bug,需要立即止损
- 场景 2:读不到新版本日志,想先全部切过去再排查
- 场景 3:更新过程中想终止并保持现状
- 场景 4:Deployment 更新超时自动标记失败
- 七、面试高频追问(加分要点)
-
- 1. 滚动更新期间,服务真的不中断吗?
- 2. 为什么回滚能"一键完成"?
- 3. 如何控制回滚历史的保留数量?
- 4. 滚动更新与 Recreate 策略的区别?
- 5. 滚动更新对 etcd 和 apiserver 的压力?
- 八、一个完整的生产级滚动更新示例
Deployment 滚动更新机制详解(企业级面试与运维实战)
Deployment 的滚动更新是 Kubernetes 生产环境中最核心的发布机制。它解决的问题是:更新应用时,如何做到服务不中断、流量不丢失、故障可回滚。下面我从原理到实操、从面试到排障,完整拆解。
一、Deployment 如何管理 Pod?(三层级联关系)
在深入滚动更新之前,先理解 Deployment 的层级结构——这是整个滚动更新的地基:
Deployment(用户操作入口)
│ 管理
▼
ReplicaSet(版本快照,每个版本对应一个 RS)
│ 管理
▼
Pod(实际运行容器)
核心结论:
- Deployment 不直接管理 Pod,它只管理 ReplicaSet
- 每次修改 Deployment 的 Pod 模板(镜像、环境变量等),都会创建一个新的 ReplicaSet
- 旧版本对应旧 RS,新版本对应新 RS,滚动更新就是在新旧两个 RS 之间"搬"副本数
关键标志:kubectl get deploy 输出的 DESIRED 是 Pod 总数,而每个 RS 的 DESIRED 是各自的副本数,两者之和恒等于 Deployment 的期望副本数(更新过程中)。
二、滚动更新完整流程(以 3 副本为例)
假设当前 Deployment 有 3 个副本,镜像版本为 nginx:1.19,现在要更新到 nginx:1.21:
初始状态:
Deployment (replicas=3)
├── RS-v1 (nginx:1.19, replicas=3) ← 当前版本
└── RS-v2 (nginx:1.21, replicas=0) ← 尚未创建
执行 kubectl set image deploy/nginx nginx=nginx:1.21
第一阶段:创建新 RS
Deployment (replicas=3)
├── RS-v1 (nginx:1.19, replicas=0 → 3, maxUnavailable=1)
└── RS-v2 (nginx:1.21, replicas=0 → 1) ← 新 RS 创建,先扩容 1 个
- 创建 RS-v2,replicas=0(初始)
- 根据 maxUnavailable 和 maxSurge 计算可扩缩容的幅度(见下文参数详解)
- RS-v2 先扩容到 1(因为 maxSurge=1,允许超出期望 1 个)
第二阶段:逐步滚动(以默认策略 maxSurge=25%, maxUnavailable=25% 为例)
第 1 轮:RS-v2: 1, RS-v1: 2(总 3 个,均在服务中)
第 2 轮:RS-v2: 2, RS-v1: 1
第 3 轮:RS-v2: 3, RS-v1: 0
每轮动作:
第三阶段:完成
Deployment (replicas=3)
├── RS-v1 (nginx:1.19, replicas=0) ← 保留但缩容为 0(用于回滚)
└── RS-v2 (nginx:1.21, replicas=3) ← 当前版本
关键点:
- 旧 RS 不会删除,只是缩容到 0。这是为了支持一键回滚
- 新 Pod 必须通过 Readiness Probe 才会被视为"就绪"
- 如果新 Pod 一直不就绪,滚动更新会卡住(不会继续缩容旧 RS)
三、滚动更新核心参数(面试必考)
滚动更新的节奏由 strategy.rollingUpdate 下的两个参数控制:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25% # 更新过程中允许最多 25% 的副本不可用
maxSurge: 25% # 更新过程中允许超出期望副本数最多 25%
1. maxUnavailable(最大不可用数)
- 含义:滚动更新过程中,允许同时"不可用"(正在被替换)的 Pod 最大数量
- 作用:保证服务有足够的可用副本,避免全部 Pod 同时被替换导致服务中断
- 默认 25%(向上取整):3 副本 → 允许 1 个不可用(3 × 25% = 0.75 → 向上取整 = 1)
2. maxSurge(最大峰值数)
- 含义:滚动更新过程中,允许超出期望副本数的最大 Pod 数量
- 作用:决定新版本 Pod 可以"提前"创建多少个,加快更新速度
- 默认 25%(向上取整):3 副本 → 允许超出 1 个(3 × 25% = 0.75 → 向上取整 = 1)
3. 两个参数如何配合?
滚动更新的节奏可以理解为:
每轮可缩容旧 Pod 数 = maxUnavailable
每轮可扩容新 Pod 数 = maxSurge
极端场景举例:
| maxUnavailable: 1, maxSurge: 1 | 一次只更新 1 个 Pod,更新速度慢但最安全 |
| maxUnavailable: 100%, maxSurge: 0% | 先缩容所有旧 Pod(全部不可用),再创建新 Pod → 等价于 Recreate 策略(先删后建) |
| maxUnavailable: 0%, maxSurge: 100% | 先创建全部新 Pod(峰值翻倍,消耗双倍资源),全部就绪后再删旧 Pod → 零停机但资源占用高(蓝绿发布思想) |
四、滚动更新的状态与控制(运维核心技能)
1. 查看更新进度
# 查看更新状态(会阻塞直到完成或超时)
kubectl rollout status deploy/nginx
# 输出示例:
# Waiting for deployment "nginx" rollout to finish: 1 out of 3 new replicas have been updated…
# deployment "nginx" successfully rolled out
2. 查看历史版本(每个版本对应一个 RS)
kubectl rollout history deploy/nginx
# 输出示例:
# REVISION CHANGE-CAUSE
# 1 <none> # 可通过 –record 记录变更原因
# 2 kubectl set image deploy/nginx nginx=nginx:1.21
注意:CHANGE-CAUSE 来自 Deployment 的 metadata.annotations.kubernetes.io/change-cause。K8s v1.26+ 已移除 –record 参数,需手动添加 annotation:
kubectl annotate deploy/nginx kubernetes.io/change-cause="升级nginx到1.21"
3. 暂停与恢复(金丝雀发布的核心手法)
暂停:更新过程中暂停,让新旧版本并存一段时间,验证新版本稳定性后再继续:
# 触发更新后立即暂停
kubectl set image deploy/nginx nginx=nginx:1.21
kubectl rollout pause deploy/nginx
# 此时:RS-v2 创建了 1 个新 Pod 并停止,其余 2 个还在旧版本
# 相当于手动控制的金丝雀发布
恢复:
kubectl rollout resume deploy/nginx
# 继续完成剩余的滚动更新
使用场景:先更新 1 个 Pod 验证日志/监控无异常,再恢复继续全量更新。
4. 手动控制更新比例(精细灰度)
通过直接修改 maxSurge 和 maxUnavailable,可以控制每次更新的 Pod 数量:
# 每次只更新 1 个 Pod
kubectl patch deploy/nginx -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
# 或
kubectl edit deploy/nginx # 修改后保存,会自动触发一次滚动更新
5. 回滚
# 回滚到上一个版本
kubectl rollout undo deploy/nginx
# 回滚到指定版本
kubectl rollout undo deploy/nginx –to-revision=1
# 查看回滚进度
kubectl rollout status deploy/nginx
回滚原理:让旧 RS 重新扩容、新 RS 缩容,和正向更新的过程完全对称。
五、滚动更新卡住的排查(生产环境最高频问题)
现象
kubectl rollout status 长时间停留在:
Waiting for deployment "nginx" rollout to finish: 1 out of 3 new replicas have been updated…
排查思路(四步定位)
Step 1:查看 Deployment 状态与事件
kubectl describe deploy/nginx
重点看 Conditions 字段中的 Progressing 和 Available:
Conditions:
Type Status Reason
—– —— ——
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded # ← 更新超时失败
Step 2:查看新旧 RS 的副本分布与状态
kubectl get rs -l app=nginx
# NAME DESIRED CURRENT READY AGE
# nginx-abc 3 3 3 1h # 旧 RS
# nginx-def 2 2 0 5m # 新 RS,READY=0 → 问题在这里
Step 3:查看新 RS 的 Pod 状态与事件
kubectl get pods -l app=nginx | grep nginx-def
kubectl describe pod/nginx-def-xxxxx
Step 4:定位根因
| Pod 一直 ContainerCreating | 镜像拉取失败(私有仓库认证)、存储挂载失败(PVC 未绑) |
| Pod Running 但 READY 0/1 | Readiness Probe 失败(最常见)—— 端口探测不通、健康检查路径返回非 200 |
| Pod CrashLoopBackOff | 新版本启动即崩溃(环境变量缺失、配置错误、依赖服务未就绪) |
| 新 Pod 一直 Pending | 资源不足、节点亲和性不满足 |
最典型场景:新版本镜像有 bug,启动后 Readiness Probe 一直失败 → 新 Pod 永远不就绪 → 滚动更新永远不会继续缩容旧 RS → 服务保持旧版本可用(这是滚动更新的设计优点:宁可卡住也不中断)。
六、滚动更新故障与应对(运维实战)
场景 1:新版本有严重 Bug,需要立即止损
# 紧急回滚(最快速)
kubectl rollout undo deploy/nginx
# 或直接改回旧镜像
kubectl set image deploy/nginx nginx=nginx:1.19
场景 2:读不到新版本日志,想先全部切过去再排查
# 将 maxUnavailable 设为 100%(等价于先删后建)
kubectl patch deploy/nginx –type='json' \\
-p='[{"op": "replace", "path": "/spec/strategy/rollingUpdate/maxUnavailable", "value": "100%"}]'
# 此时会立即缩容所有旧 Pod,全部切换到新版本
# 注意:这会短暂中断服务(等价于 Recreate 策略)
场景 3:更新过程中想终止并保持现状
# 暂停滚动更新
kubectl rollout pause deploy/nginx
# 此时新旧版本并存,保持当前状态不再变化
# 适合"先停下来观察一下新版本表现"的场景
场景 4:Deployment 更新超时自动标记失败
默认 progressDeadlineSeconds=600(10分钟),超过后 Deployment 被标记为 Progressing=False,但不会自动回滚,只是停止更新。需要手动介入:
# 查看失败原因
kubectl describe deploy/nginx | grep -A5 Conditions
# 手动回滚
kubectl rollout undo deploy/nginx
七、面试高频追问(加分要点)
1. 滚动更新期间,服务真的不中断吗?
不完全是。关键取决于:
- Readiness Probe 是否配置正确——新 Pod 就绪后才加入 Service 的 Endpoints,流量才会切过去
- Pod 终止时是否优雅——旧 Pod 收到 SIGTERM 后是否能在宽限期内完成"排空"(处理完当前请求再退出)
面试表述:“滚动更新不中断服务的前提是配置了正确的 Readiness Probe 和 preStop Hook。如果没有 Readiness Probe,新 Pod 刚启动就会被加入 Endpoints,此时容器可能还没真正就绪,会短暂返回 5xx。”
2. 为什么回滚能"一键完成"?
因为每次更新都会留下一个对应的 RS(保留完整的历史 Pod 模板)。回滚只是把目标 RS 重新扩容、当前 RS 缩容——本质也是一次滚动更新,只是方向相反。
3. 如何控制回滚历史的保留数量?
通过 spec.revisionHistoryLimit(默认 10)。超过数量的旧 RS 会被 GC 清理,但注意:缩容为 0 的 RS 不会被删除(保留用于回滚),只有超过 revisionHistoryLimit 的 RS 才会被彻底删除。
4. 滚动更新与 Recreate 策略的区别?
| 服务中断 | 通常无(前提是配置正确) | 有(先删后建) |
| 资源消耗 | 可能瞬时超出期望副本数(maxSurge) | 无额外消耗 |
| 适用场景 | 无状态服务、微服务 | 有状态服务(数据库)等无法并存的场景 |
| 更新速度 | 较慢(受 maxSurge/maxUnavailable 控制) | 快 |
5. 滚动更新对 etcd 和 apiserver 的压力?
每次滚动更新会触发大量 Pod 创建/删除事件,这些事件会写入 etcd 并广播给所有控制器。大规模集群(数百节点、数千 Pod)同时滚动更新时,需要控制更新速率,否则可能压垮 apiserver。
优化手段:
- 分批更新(先更新部分节点或部分 Deployment)
- 使用 maxSurge: 1, maxUnavailable: 0 保守配置
- 结合 PDB(PodDisruptionBudget) 保护关键服务的副本可用性
八、一个完整的生产级滚动更新示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
annotations:
kubernetes.io/change-cause: "升级nginx到1.21,修复安全漏洞"
spec:
replicas: 5
revisionHistoryLimit: 5 # 保留最近 5 个历史版本
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 最多 1 个副本不可用(保守)
maxSurge: 2 # 最多超出 2 个副本(加快速度)
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 30 # 优雅终止等待时间
containers:
– name: nginx
image: nginx:1.21
ports:
– containerPort: 80
readinessProbe: # 就绪探针(关键!)
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop: # 优雅下线:先摘流量再停止
exec:
command: ["/bin/sh", "-c", "sleep 5"]
执行更新:
# 1. 触发更新
kubectl apply -f deployment.yaml
# 2. 监控进度
kubectl rollout status deploy/nginx
# 3. 确认完成后查看版本
kubectl rollout history deploy/nginx
# 4. 如果出问题,回滚
kubectl rollout undo deploy/nginx
preStop Hook 的作用:当 Pod 被终止时,先执行 sleep 5,给 kube-proxy 时间从 Endpoints 中移除该 Pod 的 IP,确保不再有新流量进来,然后才收到 SIGTERM 退出。这是避免"正在处理的请求被中断"的关键。





