K8s 滚动发布深度解析
面试官:K8s 的滚动更新是怎么实现的?
你:Deployment 通过 ReplicaSet 实现滚动更新,创建新版本的 ReplicaSet,逐步扩容新 ReplicaSet 并缩容旧 ReplicaSet,直到完成更新。通过
maxUnavailable和maxSurge控制滚动速度,确保更新过程中服务可用。面试官:如果新版本有 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:maxUnavailable 和 maxSurge 如何配置?有哪些最佳实践?必考
Section titled “Q2:maxUnavailable 和 maxSurge 如何配置?有哪些最佳实践?”三种典型配置策略:
| 场景 | 推荐配置 | 特点 | 适用情况 |
|---|---|---|---|
| 资源充足、快速更新 | maxUnavailable=0, maxSurge=30% | 先扩容再缩容,零不可用 | 关键业务、资源充裕 |
| 资源紧张、不能超配 | maxSurge=0, maxUnavailable=20% | 先缩容再扩容,节省资源 | 资源受限环境 |
| 平衡默认 | maxUnavailable=25%, maxSurge=25% | 边扩边缩,平衡速度和资源 | 通用场景 |
配置示例:
apiVersion: apps/v1kind: Deploymentmetadata: name: order-servicespec: 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:如何回滚到历史版本?回滚为什么快?”回滚命令全流程:
# 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,镜像已缓存,无需重新拉取。
链式追问二:零停机发布实战
Section titled “链式追问二:零停机发布实战”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/v1kind: Deploymentspec: 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):
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 倍 | 关键业务、大版本升级 |
| 金丝雀发布 | 新版本承接小比例流量 | 风险可控、逐步放量 | 需要流量路由支持 | 生产环境灰度测试 |
蓝绿发布实现
Section titled “蓝绿发布实现”架构图:
蓝绿发布架构:
负载均衡器(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/v1kind: Deploymentmetadata: name: app-bluespec: 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/v1kind: Deploymentmetadata: name: app-greenspec: 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: v1kind: Servicemetadata: name: my-servicespec: 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金丝雀发布实现
Section titled “金丝雀发布实现”方案一:多 Deployment + Service 标签路由
# 稳定版 Deployment(90% 流量)apiVersion: apps/v1kind: Deploymentmetadata: name: app-stablespec: 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/v1kind: Deploymentmetadata: name: app-canaryspec: 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 的 PodapiVersion: v1kind: Servicemetadata: name: my-servicespec: 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 权重路由(推荐)
# 稳定版 ServiceapiVersion: v1kind: Servicemetadata: name: app-stablespec: selector: version: stable ports: - port: 80 targetPort: 8080
---# 金丝雀 ServiceapiVersion: v1kind: Servicemetadata: name: app-canaryspec: selector: version: canary ports: - port: 80 targetPort: 8080
---# 稳定版 IngressapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-stablespec: rules: - host: api.example.com http: paths: - path: / backend: service: name: app-stable port: number: 80
---# 金丝雀 Ingress(Nginx Ingress 注解)apiVersion: networking.k8s.io/v1kind: Ingressmetadata: 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) → 排查问题后重新灰度本质一句话:蓝绿发布通过新旧环境并行实现秒级切换,金丝雀发布通过流量权重控制实现渐进式发布,两者结合可实现快速、安全的生产发布。