Skip to content

K8s 滚动发布深度解析

面试官:K8s 的滚动更新是怎么实现的?

:Deployment 通过 ReplicaSet 实现滚动更新,创建新版本的 ReplicaSet,逐步扩容新 ReplicaSet 并缩容旧 ReplicaSet,直到完成更新。通过 maxUnavailablemaxSurge 控制滚动速度,确保更新过程中服务可用。

面试官:如果新版本有 Bug,如何快速回滚?回滚为什么很快?

这个追问考察对 ReplicaSet 历史版本保留机制的理解,能说清回滚原理的候选人,才算真正掌握了 K8s 的发布机制。


链式追问一:Deployment 滚动更新原理

Section titled “链式追问一:Deployment 滚动更新原理”

Q1:Deployment 滚动更新的底层实现是什么?必考

Section titled “Q1:Deployment 滚动更新的底层实现是什么?”

ReplicaSet 交替扩缩容机制

更新前状态:
Deployment (replicas: 3)
└── ReplicaSet-v1 (replicas: 3, image: v1.0)
├── Pod-v1-1 (Running)
├── Pod-v1-2 (Running)
└── Pod-v1-3 (Running)
触发更新(kubectl set image deployment/my-app app=my-app:v2.0):
1. Deployment 创建新的 ReplicaSet-v2
2. ReplicaSet-v2 replicas=0(初始状态)
滚动更新过程(maxUnavailable=1, maxSurge=1, replicas=3):
步骤 1: 扩容新 Pod(maxSurge 允许超出期望数 1 个)
RS-v1: 3 Pod RS-v2: 1 Pod(共 4 个 Pod)
Pod-v2-1 创建中(Pending → ContainerCreating)
步骤 2: 新 Pod 就绪 → 缩容旧 Pod
RS-v1: 2 Pod RS-v2: 1 Pod(共 3 个 Pod)
Pod-v1-3 终止(Terminating)
步骤 3: 再扩容新 Pod
RS-v1: 2 Pod RS-v2: 2 Pod(共 4 个 Pod)
Pod-v2-2 创建
步骤 4: 新 Pod 就绪 → 缩容旧 Pod
RS-v1: 1 Pod RS-v2: 2 Pod(共 3 个 Pod)
步骤 5-6: 继续交替扩缩容...
最终状态:
RS-v1: 0 Pod(保留 ReplicaSet 对象,供回滚用)
RS-v2: 3 Pod(所有 Pod 运行 v2.0 镜像)

关键参数解析

参数含义默认值作用
maxUnavailable更新过程中最多允许不可用的 Pod 数量25%控制更新速度(值越大更新越快,但风险越高)
maxSurge更新过程中最多允许超出期望副本数的 Pod 数量25%控制资源超配(值越大更新越快,但资源消耗越多)

计算公式

滚动更新中 Pod 数量约束:
可用 Pod 数 ≥ replicas - maxUnavailable
总 Pod 数 ≤ replicas + maxSurge
示例(replicas=10):
maxUnavailable=2, maxSurge=2
┌────────────────────────────────────┐
│ 最少可用 Pod:10 - 2 = 8 │
│ 最多总 Pod:10 + 2 = 12 │
│ │
│ 滚动过程: │
│ RS-v1: 8 Pod │
│ RS-v2: 4 Pod │
│ 总计: 12 Pod(满足 maxSurge=2) │
└────────────────────────────────────┘

本质一句话:滚动更新本质是”边建边拆”,新 ReplicaSet 逐步扩容、旧 ReplicaSet 逐步缩容,通过 maxUnavailable 和 maxSurge 平衡更新速度和资源消耗。


Q2:maxUnavailablemaxSurge 如何配置?有哪些最佳实践?必考

Section titled “Q2:maxUnavailable 和 maxSurge 如何配置?有哪些最佳实践?”

三种典型配置策略

场景推荐配置特点适用情况
资源充足、快速更新maxUnavailable=0, maxSurge=30%先扩容再缩容,零不可用关键业务、资源充裕
资源紧张、不能超配maxSurge=0, maxUnavailable=20%先缩容再扩容,节省资源资源受限环境
平衡默认maxUnavailable=25%, maxSurge=25%边扩边缩,平衡速度和资源通用场景

配置示例

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 10
strategy:
type: RollingUpdate # 滚动更新策略
rollingUpdate:
maxUnavailable: 2 # 最多 2 个 Pod 不可用
maxSurge: 2 # 最多超出期望 2 个 Pod
template:
spec:
containers:
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
# 就绪探针(必须配置!)
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
failureThreshold: 3

实战案例分析

案例 1:电商大促期间更新(资源紧张)
配置:
replicas: 50
maxUnavailable: 5 # 最多 5 个 Pod 不可用(10%)
maxSurge: 0 # 不超配,避免资源耗尽
滚动过程:
RS-v1: 45 Pod → RS-v2: 5 Pod
RS-v1: 40 Pod → RS-v2: 10 Pod
...
每批次减少 5 个旧 Pod,增加 5 个新 Pod
优势:
✓ 资源使用可控(总 Pod ≤ 50)
✓ 更新速度适中
✗ 每批次有 5 个 Pod 不可用(需保证剩余 45 Pod 扛住流量)
案例 2:支付网关更新(零不可用)
配置:
replicas: 10
maxUnavailable: 0 # 零不可用
maxSurge: 3 # 允许超配 3 个 Pod
滚动过程:
RS-v1: 10 Pod → RS-v2: 3 Pod(总计 13 Pod)
RS-v2 Pod 就绪 → RS-v1 缩容到 7 Pod(总计 10 Pod)
RS-v2 扩容到 6 Pod → RS-v1 缩容到 4 Pod
...
优势:
✓ 全程无不可用 Pod(服务 100% 可用)
✓ 更新速度快(可并行扩容)
✗ 资源峰值超配 30%(需提前预留资源)

Q3:如何回滚到历史版本?回滚为什么快?必考

Section titled “Q3:如何回滚到历史版本?回滚为什么快?”

回滚命令全流程

Terminal window
# 1. 查看发布历史
kubectl rollout history deployment/order-service
# REVISION CHANGE-CAUSE
# 1 <none> # 初始版本
# 2 kubectl set image... # v1.1
# 3 kubectl set image... # v1.2(当前版本)
# 2. 查看指定版本详情
kubectl rollout history deployment/order-service --revision=2
# Pod Template:
# Image: myapp:v1.1
# Environment: SPRING_PROFILES_ACTIVE=prod
# 3. 回滚到上一个版本
kubectl rollout undo deployment/order-service
# deployment.apps/order-service rolled back
# 4. 回滚到指定版本
kubectl rollout undo deployment/order-service --to-revision=1
# 5. 查看回滚状态
kubectl rollout status deployment/order-service
# Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
# deployment "order-service" successfully rolled out

回滚为什么快?原理分析

Deployment 保留历史 ReplicaSet:
当前状态(v1.2):
Deployment
├── RS-v1 (replicas: 0, image: v1.0) ← 保留对象,Pod 数为 0
├── RS-v1.1 (replicas: 0, image: v1.1) ← 保留对象
└── RS-v1.2 (replicas: 3, image: v1.2) ← 当前版本
执行回滚(kubectl rollout undo):
1. Deployment 更新期望 ReplicaSet → RS-v1.1
2. RS-v1.1 扩容到 replicas: 3
├── 镜像已存在(无需拉取)
├── 镜像分层复用(启动快)
└── 新 Pod 创建时间:~5-10s
3. RS-v1.2 缩容到 replicas: 0
回滚耗时对比:
├─ 首次部署:拉取镜像(30s)+ 启动容器(20s)= 50s
└─ 回滚:镜像已缓存(0s)+ 启动容器(10s)= 10s
关键:旧 ReplicaSet 对象保留,只是 Pod 数为 0

历史版本保留配置

spec:
revisionHistoryLimit: 10 # 保留最近 10 个 ReplicaSet(默认值)
# 过小的风险:
# revisionHistoryLimit: 2
# → 只能回滚到最近 2 个版本,更早版本被删除
# → 无法回滚到稳定版本
# 过大的风险:
# revisionHistoryLimit: 50
# → etcd 存储大量历史 ReplicaSet 对象
# → kubectl get rs 输出过多无用信息

本质一句话:回滚快是因为旧 ReplicaSet 对象保留(Pod 数为 0),只需扩容旧 RS 并缩容新 RS,镜像已缓存,无需重新拉取。


Q4:如何实现真正的零停机发布?必考

Section titled “Q4:如何实现真正的零停机发布?”

仅靠滚动更新是不够的,需要三个关键配置:

零停机发布三大要素:
1. readinessProbe(就绪探针)
├── 新 Pod 必须通过探针才接受流量
└── 避免 Pod 未启动完成就接收请求
2. preStop 钩子(优雅关闭)
├── Pod 收到 SIGTERM 前,先等待 15s
├── 让 kube-proxy 更新 iptables 规则(摘除 Pod IP)
└── 确保不再有新请求到来
3. 应用优雅关闭(Graceful Shutdown)
├── 应用收到 SIGTERM 后,停止接收新请求
├── 等待正在处理的请求完成(最长 30s)
└── 关闭数据库连接、清理资源

完整配置示例

apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 零不可用
maxSurge: 1 # 允许超配 1 个 Pod
template:
spec:
terminationGracePeriodSeconds: 60 # 最长等待 60s 优雅关闭
containers:
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
# 1. 就绪探针(新 Pod 必须通过才接流量)
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20 # 启动后 20s 开始探测
periodSeconds: 5 # 每 5s 探测一次
failureThreshold: 3 # 失败 3 次才标记未就绪
successThreshold: 1 # 成功 1 次标记就绪
# 2. preStop 钩子(优雅关闭前等待)
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
# 作用:
# Pod 收到删除请求 → 先 sleep 15s
# → kube-proxy 更新 iptables 规则(摘除 Pod IP)
# → 再发送 SIGTERM 给容器
# → 应用开始优雅关闭
# 资源配额
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
# 环境变量(优雅关闭配置)
env:
- name: GRACEFUL_SHUTDOWN_TIMEOUT
value: "30s"

应用侧优雅关闭(Spring Boot)

application.yml
server:
shutdown: graceful # 启用优雅关闭
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最长等待 30s
# 优雅关闭流程:
# 1. 收到 SIGTERM 信号
# 2. Spring Boot 标记为"关闭中",拒绝新请求
# 3. 等待正在处理的请求完成(最长 30s)
# 4. 关闭数据库连接池、清理缓存
# 5. 退出进程(exit code 0)

零停机发布时间线

T+0s 新 Pod 创建(ContainerCreating)
T+20s readinessProbe 开始探测
T+25s 新 Pod 通过 readinessProbe → 加入 Service Endpoints
└── 开始接收流量(新 Pod 就绪)
T+30s 旧 Pod 收到删除请求
├── 执行 preStop 钩子(sleep 15s)
└── kube-proxy 更新 iptables(摘除旧 Pod IP)
T+45s 旧 Pod 收到 SIGTERM 信号
├── 应用停止接收新请求
├── 等待正在处理的请求完成(最长 30s)
└── 关闭连接、清理资源
T+75s 旧 Pod 退出(或被强制 SIGKILL)
全程:无请求失败(零停机)

常见故障排查

现象原因解决方案
滚动更新时 502 错误新 Pod 未通过 readinessProbe 就接收流量配置 readinessProbe
旧 Pod 被杀时请求失败未配置 preStop,kube-proxy 未及时更新路由添加 preStop sleep 15s
应用启动慢被重启livenessProbe 失败(initialDelaySeconds 过小)增大 initialDelaySeconds 或使用 startupProbe
Pod 终止慢(超过 60s)应用未实现优雅关闭,长连接未断开增大 terminationGracePeriodSeconds 或优化应用关闭逻辑

Q5:K8s 如何实现蓝绿发布和金丝雀发布?高频

Section titled “Q5:K8s 如何实现蓝绿发布和金丝雀发布?”

三种发布策略对比

发布策略原理优点缺点适用场景
滚动发布逐步替换 Pod资源消耗少、自动化回滚慢、旧版本逐步消失日常小版本更新
蓝绿发布新旧版本并行,瞬间切换切换快、回滚秒级资源消耗 2 倍关键业务、大版本升级
金丝雀发布新版本承接小比例流量风险可控、逐步放量需要流量路由支持生产环境灰度测试

架构图

蓝绿发布架构:
负载均衡器(LB)
├── Service-Blue(蓝环境,当前生产版本 v1.0)
│ ├── Pod-Blue-1
│ ├── Pod-Blue-2
│ └── Pod-Blue-3
└── Service-Green(绿环境,新版本 v2.0)
├── Pod-Green-1
├── Pod-Green-2
└── Pod-Green-3
发布流程:
1. 绿环境部署完成,验证通过
2. 切换 Service Selector:version=blue → version=green
3. 流量瞬间切换到绿环境(秒级切换)
4. 蓝环境保留,如有问题立即切回

配置示例

# 蓝环境 Deployment(当前生产版本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: blue # 蓝环境标签
template:
metadata:
labels:
app: my-app
version: blue
spec:
containers:
- name: app
image: myapp:v1.0
---
# 绿环境 Deployment(新版本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: green # 绿环境标签
template:
metadata:
labels:
app: my-app
version: green
spec:
containers:
- name: app
image: myapp:v2.0
---
# Service(初始指向蓝环境)
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
version: blue # 当前指向蓝环境
ports:
- port: 80
targetPort: 8080
---
# 切换到绿环境(修改 Service selector)
# kubectl patch service my-service -p '{"spec":{"selector":{"version":"green"}}}'

蓝绿发布时间线

T+0s 绿环境部署(3 个 Pod,v2.0)
T+60s 绿环境验证通过(功能测试、性能测试)
T+61s 切换 Service selector(version=green)
└── 流量瞬间切换到绿环境(~1s)
T+62s 监控绿环境(错误率、延迟)
T+300s 确认无问题 → 删除蓝环境 Deployment

方案一:多 Deployment + Service 标签路由

# 稳定版 Deployment(90% 流量)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-stable
spec:
replicas: 9 # 9 个 Pod
selector:
matchLabels:
app: my-app # Service 通过此标签选择 Pod
version: stable
template:
spec:
containers:
- name: app
image: myapp:v1.0
---
# 金丝雀 Deployment(10% 流量)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-canary
spec:
replicas: 1 # 1 个 Pod
selector:
matchLabels:
app: my-app # 同一标签,被同一个 Service 选中
version: canary
template:
spec:
containers:
- name: app
image: myapp:v2.0 # 新版本镜像
---
# Service 选择所有 app=my-app 的 Pod
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app # 同时选中 stable 和 canary 的 Pod
ports:
- port: 80
targetPort: 8080
# 流量分配:
# stable: 9 Pod → 90% 流量(随机轮询)
# canary: 1 Pod → 10% 流量

流量分配原理

Service 负载均衡(随机轮询):
假设 Service 选择 10 个 Pod:
├── 9 个 stable Pod(v1.0)
└── 1 个 canary Pod(v2.0)
iptables 规则(简化版):
Chain KUBE-SVC-XXX
├── 10% 概率 → canary Pod
└── 90% 概率 → stable Pod(平均分配到 9 个 Pod)
实际效果:
canary Pod 承接 ~10% 流量
stable Pod 承接 ~90% 流量(每个 stable Pod 约 10%)

方案二:Ingress 权重路由(推荐)

# 稳定版 Service
apiVersion: v1
kind: Service
metadata:
name: app-stable
spec:
selector:
version: stable
ports:
- port: 80
targetPort: 8080
---
# 金丝雀 Service
apiVersion: v1
kind: Service
metadata:
name: app-canary
spec:
selector:
version: canary
ports:
- port: 80
targetPort: 8080
---
# 稳定版 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-stable
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
backend:
service:
name: app-stable
port:
number: 80
---
# 金丝雀 Ingress(Nginx Ingress 注解)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% 流量到金丝雀
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
backend:
service:
name: app-canary
port:
number: 80

金丝雀发布流程

金丝雀发布步骤(逐步放量):
T+0min 部署金丝雀 Deployment(replicas: 1, v2.0)
└── 创建金丝雀 Ingress(weight: 10%)
T+5min 监控金丝雀版本指标
├── 错误率: 0.05%(< 0.1%,正常)
├── P95 延迟: 180ms(< 200ms,正常)
└── CPU 使用率: 60%(正常)
T+10min 增加流量权重到 30%
kubectl annotate ingress app-canary \
nginx.ingress.kubernetes.io/canary-weight=30
T+20min 监控无异常 → 增加到 50%
└── 继续观察
T+30min 监控无异常 → 增加到 100%
└── 全量切换
T+35min 删除旧版本 Deployment 和 Ingress

实战案例

场景:电商订单服务 v2.0 灰度发布(新增优惠功能)
监控指标(Prometheus + Grafana):
├─ 错误率:< 0.1%(正常)
├─ P95 延迟:< 200ms(正常)
├─ QPS:稳定在 5000
└─ 业务指标:订单转化率、优惠使用率
灰度过程:
10:00 → weight: 10%(金丝雀 QPS: 500)
└── 监控日志:无 ERROR
10:30 → weight: 30%(金丝雀 QPS: 1500)
└── 监控数据库连接池:正常
11:00 → weight: 50%(金丝雀 QPS: 2500)
└── 监控业务指标:转化率提升 5%
11:30 → weight: 100%(全量切换)
└── 监控整体指标:稳定
12:00 → 删除旧版本 Deployment
异常回滚预案:
如果金丝雀版本错误率 > 1%:
→ 立即 weight: 0%(切回稳定版,耗时 <10s)
→ 排查问题后重新灰度

本质一句话:蓝绿发布通过新旧环境并行实现秒级切换,金丝雀发布通过流量权重控制实现渐进式发布,两者结合可实现快速、安全的生产发布。