Deployment 进阶:滚动更新、回滚与金丝雀发布的底层逻辑
文章目录
前置知识: Deployment、ReplicaSet、Pod 的基本关系,
kubectl apply 和 kubectl get 的用法。
一、Deployment 与 ReplicaSet 的版本管理体系
理解滚动更新之前,必须先弄清楚 Deployment 管理 ReplicaSet 的方式。
一个 Deployment 每执行一次 kubectl apply,就会创建一个新的 ReplicaSet,而旧 ReplicaSet 不会被立即删除——它作为历史快照被保留下来,这就是回滚的物理基础。
每个 ReplicaSet 上都有一个 Annotation,记录创建它时的 Pod 模板快照:
kubectl get rs nginx-7d9fb8c9 -o jsonpath='{.metadata.annotations}'
# 输出示例:
# {"deployment.kubernetes.io/revision": "1",
# "kubernetes.io/change-cause": "kubectl apply --filename=deployment-v1.yaml"}
kubectl get rs nginx-7d9fb8c9 -o jsonpath='{.spec.template}'
这个 spec.template 就是回滚时恢复的完整 Pod 定义——不是增量 diff,而是完整的快照。
revisionHistoryLimit默认为 10,表示最多保留 10 个历史 ReplicaSet。设置为 0 时不保留历史,无法回滚。
二、滚动更新策略:maxSurge 与 maxUnavailable 的精确计算
2.1 两个参数的实际含义
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # 最多比期望副本数多出多少
maxUnavailable: 0 # 最多允许多少个 Pod 不可用
| 参数 | 含义 | 取值范围 |
|---|---|---|
maxUnavailable |
滚动更新期间允许不可用的 Pod 上限 | 绝对数或百分比,可为 0 |
maxSurge |
滚动更新期间最多超出期望副本数的 Pod 上限 | 绝对数或百分比,可以为 0 |
关键点:两个参数不能同时为 0——那样滚动更新就没有任何操作空间,K8s 会直接拒绝。
2.2 精确计算:任意时刻的 Pod 数量
设期望副本数 replicas=N,maxUnavailable=P,maxSurge=S:
- 不可用 Pod 上限:
min(N, P)(若 P 为绝对数,直接取 P;若为百分比,取⌊N × P%⌋) - 总 Pod 数上限:
N + S(若 S 为绝对数,取 S;若为百分比,取N + ⌊N × S%⌋)
以 replicas=5, maxUnavailable=2, maxSurge=1 为例:
| 阶段 | 旧 Pod | 新 Pod | 总 Pod | 不可用 Pod |
|---|---|---|---|---|
| 开始前 | 5 | 0 | 5 | 0 |
| 滚动中 | 3 | 2 | 5 | 2(触及上限,暂停) |
| 滚动中 | 2 | 3 | 6(触及 maxSurge=1 上限) | 2 |
| 完成后 | 0 | 5 | 5 | 0 |
两种极端配置的业务含义:
maxUnavailable=0, maxSurge=1:滚动期间总 Pod 数最多为N+1,适合对可用性要求极高的服务(如在线交易系统)maxUnavailable=1, maxSurge=0:始终保持N个 Pod,但会短暂出现不可用窗口,适合对资源敏感的场景(如开发测试环境)
三、kubectl rollout undo 内部机制
3.1 执行流程分解
kubectl rollout undo deployment/nginx
这行命令背后发生了四件事:
一个容易忽略的细节:回滚操作本身也会生成一个新的 revision。这意味着 kubectl rollout history 中会看到回滚操作作为一条新记录,而不是覆盖原来的 revision。
# 查看 revision 历史
kubectl rollout history deployment/nginx
# 输出示例:
# deployment/nginx
# REVISION CHANGE-CAUSE
# 3 kubectl apply --filename=deployment-v3.yaml
# 2 kubectl apply --filename=deployment-v2.yaml
# 1 kubectl apply --filename=deployment-v1.yaml
# 4 kubectl rollout undo --to-revision=2 ← 回滚操作产生的 revision
3.2 回滚到指定版本
# 回滚到 revision=2
kubectl rollout undo deployment/nginx --to-revision=2
不指定 --to-revision 时,默认回滚到上一个版本(即 revision 列表中的 revision-1)。
3.3 回滚失败的场景
# 查看回滚状态
kubectl rollout status deployment/nginx
常见回滚失败原因:
| 场景 | 表现 | 根因 |
|---|---|---|
| revision 历史被清空 | error: no rollout history is available |
revisionHistoryLimit=0 |
| 指定版本不存在 | error: unable to find specified revision |
revision 编号不在保留范围内 |
| Pod 模板语法错误 | 滚动更新卡在 Progressing |
上一版本的 YAML 本身有缺陷 |
四、金丝雀发布的原生实现
4.1 为什么不用 Argo Rollouts
Argo Rollouts 是功能完整的金丝雀控制器,但引入门槛较高。原生 K8s 方式不需要任何额外 CRD,通过两个 Deployment + Service 标签选择器即可实现基础的灰度发布。
4.2 实现方式
第一步:部署稳定版本(v1)
# deployment-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-stable
spec:
replicas: 10
selector:
matchLabels:
app: nginx
version: v1
template:
metadata:
labels:
app: nginx
version: v1
spec:
containers:
- name: nginx
image: nginx:1.24
ports:
- containerPort: 80
第二步:Service 指向 v1
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx # 匹配所有版本
version: v1 # 初始时只指向 v1
ports:
- port: 80
targetPort: 80
第三步:部署金丝雀版本(v2)
# deployment-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-canary
spec:
replicas: 2 # 小比例:10个v1 + 2个v2 = 约17%流量到v2
selector:
matchLabels:
app: nginx
version: v2
template:
metadata:
labels:
app: nginx
version: v2
spec:
containers:
- name: nginx
image: nginx:1.25 # 新版本
ports:
- containerPort: 80
第四步:验证金丝雀 Pod 正常工作后,逐步调整 Service 标签切换流量
# 初期:Service 只指向 v1
kubectl patch service nginx -p '{"spec":{"selector":{"version":"v1"}}}'
# 金丝雀验证通过后:将 20% 流量切到 v2
kubectl patch service nginx -p '{"spec":{"selector":{"version":"v2"}}}'
# 此时 nginx Service 的 selector 变为 version=v2
# v1 的 10 个 Pod 仍在运行,但不再接收 Service 流量
# 最终:完全切换到 v2
kubectl scale deployment nginx-stable --replicas=0
4.3 Ingress-Nginx 原生金丝雀注解
如果集群使用 Ingress-Nginx Controller,可以利用注解实现基于权重的流量分配,无需修改 Service:
# 主版本 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-main
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-stable
port:
number: 80
---
# 金丝雀 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-canary
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20" # 20% 流量到金丝雀
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-canary
port:
number: 80
启用金丝雀注解后,
canary-weight注解按指定百分比将请求转发到金丝雀 Service,其余流量走主版本 Ingress。权重为 0 时所有流量走主版本,权重为 100 时所有流量走金丝雀。
五、滚动更新与 HPA 的并发冲突
这是生产环境里最容易被忽视的问题之一。
5.1 冲突场景复现
假设配置如下:
# Deployment
spec:
replicas: 5
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
---
# HPA
spec:
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
滚动更新进行中时:
- Deployment 正在创建新 Pod,Pod 总数暂时达到
5 + 1 = 6 - HPA 采样到 CPU 使用率升高,触发扩容到 8 个副本
- Deployment Controller 感知到期望副本数从 5 变为 8,立即开始扩容新 ReplicaSet
- 扩容过程中 Pod 总数达到
8 + 1 = 9
结果:HPA 扩容 + maxSurge 叠加,Pod 数量短暂超过 maxReplicas + maxSurge,对集群资源造成额外压力。
5.2 解决方案
方案一:禁用 HPA 期间的滚动更新
# 在滚动更新前暂停 HPA 扩容
kubectl autoscale deployment nginx --min=5 --max=5 --cpu-percent=80
# 等价于固定 replicas=5,HPA 不再干预
# 滚动更新完成后再恢复 HPA
kubectl delete hpa nginx
kubectl autoscale deployment nginx --min=3 --max=10 --cpu-percent=80
方案二:使用 rollout pause / rollout resume
# 触发滚动更新后立即暂停
kubectl rollout pause deployment/nginx
# 执行 HPA 相关操作
kubectl autoscale deployment nginx --min=3 --max=10
# HPA 稳定后恢复滚动更新
kubectl rollout resume deployment/nginx
方案三:设置合理的 maxSurge
如果集群资源紧张,将 maxSurge 设为 0 或 1,避免因 HPA 扩容叠加产生过多额外 Pod:
spec:
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1 # 允许同时有 1 个不可用
六、生产环境配置决策树
七、验证命令清单
# 1. 查看滚动更新状态
kubectl rollout status deployment/nginx -w
# 2. 查看当前 revision 历史
kubectl rollout history deployment/nginx
# 3. 查看具体 revision 的详情
kubectl rollout history deployment/nginx --revision=2
# 4. 回滚到上一版本
kubectl rollout undo deployment/nginx
# 5. 回滚到指定版本
kubectl rollout undo deployment/nginx --to-revision=1
# 6. 查看新旧 ReplicaSet 分布
kubectl get rs -l app=nginx
# 7. 暂停滚动更新(常配合 HPA 使用)
kubectl rollout pause deployment/nginx
# 8. 恢复滚动更新
kubectl rollout resume deployment/nginx
# 9. 查看 Deployment 滚动策略配置
kubectl get deployment nginx -o jsonpath='{.spec.strategy}'
# 10. 强制重启(重新触发滚动更新)
kubectl rollout restart deployment/nginx
八、总结
Deployment 的滚动更新不是"新版本替换旧版本"这么简单。背后的 ReplicaSet 版本快照机制、maxSurge 与 maxUnavailable 的精确约束、HPA 并发时的资源竞争问题——每个环节都有明确的内部逻辑。
理解这些逻辑,才能在滚动更新卡住时知道查什么、在回滚失败时知道根因在哪、在设计灰度策略时选择合适的实现方式。
关键结论:
kubectl rollout undo本质是恢复旧 ReplicaSet 的 Pod 模板快照,每次回滚都生成新 revisionmaxSurge=1, maxUnavailable=0适合高可用要求,maxSurge=0, maxUnavailable=1适合资源受限场景- 原生金丝雀发布用两个 Deployment + Service 标签切换即可实现,无需引入额外 CRD
- HPA 与滚动更新并发时,使用
rollout pause/resume或固定 replicas 规避资源叠加
觉得这篇文章有收获的话,欢迎点赞、关注。技术创作不易,每一份支持都是坚持下去的动力。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)