
前言
K8S原生Deployment支持滚动更新和重建两种发布模式。
滚动更新模式
Pod升级自动进行,失败自动暂停。通过以下策略控制升级速率:
- Max Unavailable : 最大不可用实例数/比例
- Max Surge : 调度过程中,可超过期望实例数的数/比例
然而,由于发布期间无法暂停,导致没有时间验证新版本正确性,仅依赖Pod Ready状态判断过于片面,基本没有灰度能力。因此在生产环境使用非常危险。
重建模式
先删除所有旧版本Pod,再创建新版本Pod,期间服务停机,不适用于在线服务。
对于生产环境,往往需要节奏可控、可灰度、可回滚的发布方式。常见的有批次发布、金丝雀发布、蓝绿发布等策略。本文以批次发布为例,在不改变Workload的情况下,对原生Deployment增强,实现可控的渐进式更新。
注意:本文是以学习探究为目的,如果要在生产环境使用,推荐OpenKruise,Argo Rollouts
流程分析
批次发布流程

回滚流程

功能需求
批次发布
- 每批更新的Pod支持指定数量或百分比
- 批次之间设置卡点,人工确认后继续发布
- 不涉及流量管理
连续发布
- 假设当前线上版本为V1,正在发布V2(未完成),支持重新发布V3
回滚
- 发布期间或发布结束后,可快速回滚到发布前的版本
HPA兼容
- 批次发布期间发生扩缩容,新旧版本比例满足当前批次比例
概要设计
采用旁路设计,无需修改Deployment Controller代码。新增 BatchRelease CRD、BatchRelease Controller。
核心机制:
- 发布时阻止原生Deployment Controller工作
- BatchRelease Controller根据发布策略控制ReplicaSet扩缩容
- 发布完成后归还控制权

详细设计
阻止原生Deployment Controller工作
为避免BatchRelease Controller和原生Deployment Controller冲突,需要在BatchRelease工作时禁止原生Deployment Controller调谐。
方案:通过设置以下参数实现:
spec:
paused: true
strategy:
type: Recreate
原理分析:
原生Deployment Controller核心调谐逻辑中有以下判断:
// 1. Paused=true 阻止发布和回滚
if d.Spec.Paused {
return dc.sync(ctx, d, rsList)
}
// 2. 在sync中处理扩缩容
// 但BatchRelease发布期间存在多个活跃ReplicaSet
// FindActiveOrLatest会返回nil,不会执行扩容逻辑
if activeOrLatest := deploymentutil.FindActiveOrLatest(newRS, oldRSs); activeOrLatest != nil {
// 扩缩容逻辑
}
// 3. Recreate模式阻止按比例扩缩容
if deploymentutil.IsRollingUpdate(deployment) {
// 按比例扩缩容逻辑
}
总结:
deployment.Spec.Paused = true阻止发布和回滚deployment.Spec.Strategy.Type = "Recreate"阻止按比例扩缩容- BatchRelease发布完成后恢复为
Paused=false和RollingUpdate
BatchRelease状态机设计

状态转换示例:
假设发布策略为 [1, 50%, 100%],Deployment期望副本数为10:
Initial → RollingUpdate
├─ Batch 0 分组状态: Initial → Upgrade(1个新Pod,9个老Pod) → Blocking(等待确认) → Completed
├─ Batch 1 分组状态: Initial → Upgrade(5个新Pod,5个老Pod) → Blocking(等待确认) → Completed
└─ Batch 2 分组状态: Initial → Upgrade(10个新Pod) → Completed
→ Finalizing → Completed
BatchRelease CR 设计
精简示例:
apiVersion: rollouts.yuyy.com/v1alpha1
kind: BatchRelease
metadata:
name: yuyy-nginx
spec:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: yuyy-nginx
strategy:
steps:
- replicas: 1 # 第一批: 1个Pod
- replicas: 10% # 第二批: 10%
- replicas: 30% # 第三批: 30%
- replicas: 100% # 第四批: 全部
template:
spec:
containers:
- name: nginx
image: nginx:1.15
status:
phase: Completed
currentStepIndex: 3
currentStepState: Completed
observedUpdateReversion: "5c66667f6" # 当前发布版本Hash
关键字段说明:
spec.strategy.steps: 发布批次策略,支持数字或百分比spec.template: 发布的Pod模板status.phase: 当前发布阶段status.currentStepIndex: 当前批次索引status.currentStepState: 当前批次状态
完整CR示例见附录。
实现批次发布
1. Initial 阶段
职责:
- 初始化 BatchRelease Status
- 记录当前发布的 PodTemplate Hash 到
status.observedUpdateReversion - 更新 Deployment,阻止原生 Deployment Controller 调谐
核心代码逻辑:
// 接管Deployment控制权
d.Spec.Template = br.Spec.Template
d.Spec.Paused = true
d.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
d.Annotations[BatchReleaseControlInfoAnno] = controllerRef
2. RollingUpdate 阶段
职责:
- 优先处理扩缩容事件,按当前批次比例扩缩容
- 对当前批次进行滚动更新
滚动更新策略:
- 先扩容新版本 ReplicaSet,直到达到
maxSurge限制 - 再缩容旧版本 ReplicaSet,受
maxUnavailable控制 - 满足当前批次 partition 后进入 Blocking 状态
扩缩容兼容:
// 检测扩缩容事件
for rs := range activeReplicaSets {
if rs.Annotations["desired-replicas"] != deployment.Spec.Replicas {
return true // 发生了扩缩容
}
}
// 按当前批次比例进行扩缩容
newRSReplicasLimit := NewRSReplicasLimit(currentPartition, deployment)
oldRSReplicasLimit := deployment.Spec.Replicas - newRSReplicasLimit
3. Finalizing 阶段
职责:
- 归还 Deployment 控制权
- 记录版本快照到 Annotation
核心代码逻辑:
// 归还Deployment控制权
d.Spec.Paused = false
d.Spec.Strategy.Type = apps.RollingUpdateDeploymentStrategyType
d.Spec.Strategy.RollingUpdate = &RollingUpdateDeployment{
MaxSurge: br.Status.MaxSurge,
MaxUnavailable: br.Status.MaxUnavailable,
}
delete(d.Annotations, BatchReleaseControlInfoAnno)
4. Completed 阶段
职责:
- 等待下一次发布或回滚
实现回滚

版本记录机制
控制器在 BatchRelease 的 Annotation 中维护版本快照:
| Annotation | 说明 |
|---|---|
| current-stable-reversion | 当前稳定版本的 Pod 模板 Hash |
| current-stable-reversion-raw | 当前稳定版本的 Pod 模板完整 YAML |
| last-stable-reversion | 上一个稳定版本的 Pod 模板 Hash |
| last-stable-reversion-raw | 上一个稳定版本的 Pod 模板完整 YAML |
更新时机: Finalize阶段更新
// last <- current
br.Annotations["last-stable-reversion"] = br.Annotations["current-stable-reversion"]
br.Annotations["last-stable-reversion-raw"] = br.Annotations["current-stable-reversion-raw"]
// current <- new
br.Annotations["current-stable-reversion"] = newHash
br.Annotations["current-stable-reversion-raw"] = yaml.Marshal(newTemplate)
触发回滚
用户添加 Annotation rollback: "true" 触发回滚:
kubectl annotate batchrelease yuyy-nginx rollback=true
回滚场景:
-
发布期间回滚
- 使用
current-stable-reversion-raw的 PodTemplate - 回滚到发布前的稳定版本
- 使用
-
发布完成后回滚
- 使用
last-stable-reversion-raw的 PodTemplate - 回滚到上一个稳定版本
- 使用
快速回滚策略:
为快速止损,回滚时强制使用 [1, 100%] 策略:
- 第一批: 1个Pod,用于灰度验证
- 第二批: 全部Pod
if isRollback {
br.Spec.Strategy.Steps = []Step{
{Replicas: intstr.Parse("1")},
{Replicas: intstr.Parse("100%")},
}
}
实现连续发布
场景: V1 → V2(进行中) → V3
处理逻辑:
// 每次调谐时检查 PodTemplate 是否变化
currentHash := ComputeHash(br.Spec.Template)
if br.Status.ObservedUpdateReversion != currentHash {
// 重新进入 Initial 阶段
br.Status.Phase = PhaseInitial
}
效果: 自动中断V2发布,开始V3发布
使用示例
1. 创建 Deployment
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: yuyy-nginx
spec:
replicas: 10
selector:
matchLabels:
app: yuyy-nginx
template:
metadata:
labels:
app: yuyy-nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
EOF
2. 创建 BatchRelease 开始发布
kubectl apply -f - <<EOF
apiVersion: rollouts.yuyy.com/v1alpha1
kind: BatchRelease
metadata:
name: yuyy-nginx
spec:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: yuyy-nginx
strategy:
steps:
- replicas: 1
- replicas: 30%
- replicas: 100%
template:
metadata:
labels:
app: yuyy-nginx
spec:
containers:
- name: nginx
image: nginx:1.15 # 更新镜像
ports:
- containerPort: 80
EOF
3. 查看发布状态
# 查看 BatchRelease 状态
kubectl get batchrelease yuyy-nginx
# NAME PHASE INDEX STATE REASON
# yuyy-nginx RollingUpdate 0 Blocking StepBlocking
# 查看 Pod 状态
kubectl get pods -l app=yuyy-nginx
# 会看到 1 个新版本 Pod (nginx:1.15) 和 9 个旧版本 Pod
4. 继续发布下一批次
当前批次验证通过后,继续发布:
# 修改 currentStepState 为 Completed 触发下一批次
kubectl patch batchrelease yuyy-nginx --type='merge' --subresource='status' -p '{"status":{"currentStepState":"Completed"}}'
5. 回滚操作
发现问题需要回滚:
kubectl annotate batchrelease yuyy-nginx rollback=true
总结
本项目通过旁路设计方式,在不修改 Kubernetes 原生 Deployment Controller 的前提下,实现了:
- 批次发布 - 支持自定义批次策略,每批支持数字或百分比
- 人工卡点 - 每批完成后暂停,等待确认
- 快速回滚 - 发布期间或完成后可快速回滚
- 连续发布 - 支持发布中断并开始新版本发布
- HPA兼容 - 发布期间扩缩容保持批次比例
核心技术要点:
- 通过
Paused=true+Strategy.Type=Recreate阻止原生Deployment Controller 调谐 - 状态机驱动发布流程
- Annotation 存储版本快照实现回滚
- 兼容原生 Deployment 的扩缩容和滚动更新策略
附录
附录A: 原生Deployment Controller滚动更新原理
基于 Kubernetes v1.27.0 源码分析
入口
// cmd/kube-controller-manager/app/controllermanager.go:450
register("deployment", startDeploymentController)
核心调谐逻辑
// pkg/controller/deployment/deployment_controller.go:581
func (dc *DeploymentController) syncDeployment(ctx context.Context, key string) error {
// 1. 获取所有ReplicaSet
rsList, err := dc.getReplicaSetsForDeployment(ctx, d)
// 2. 处理删除
if d.DeletionTimestamp != nil {
return dc.syncStatusOnly(ctx, d, rsList)
}
// 3. 处理暂停
if d.Spec.Paused {
return dc.sync(ctx, d, rsList)
}
// 4. 处理回滚
if getRollbackTo(d) != nil {
return dc.rollback(ctx, d, rsList)
}
// 5. 处理扩缩容
if isScalingEvent {
return dc.sync(ctx, d, rsList)
}
// 6. 处理发布
switch d.Spec.Strategy.Type {
case apps.RecreateDeploymentStrategyType:
return dc.rolloutRecreate(ctx, d, rsList, podMap)
case apps.RollingUpdateDeploymentStrategyType:
return dc.rolloutRolling(ctx, d, rsList)
}
}
滚动更新逻辑
// pkg/controller/deployment/rolling.go:32
func (dc *DeploymentController) rolloutRolling(ctx, d, rsList) error {
newRS, oldRSs, _ := dc.getAllReplicaSetsAndSyncRevision(ctx, d, rsList, true)
// 1. 先扩容新版本
scaledUp, _ := dc.reconcileNewReplicaSet(ctx, allRSs, newRS, d)
if scaledUp {
return dc.syncRolloutStatus(ctx, allRSs, newRS, d)
}
// 2. 再缩容旧版本
scaledDown, _ := dc.reconcileOldReplicaSets(ctx, allRSs, oldRSs, newRS, d)
if scaledDown {
return dc.syncRolloutStatus(ctx, allRSs, newRS, d)
}
// 3. 清理Replicaset
if deploymentutil.DeploymentComplete(d, &d.Status) {
dc.cleanupDeployment(ctx, oldRSs, d)
}
return dc.syncRolloutStatus(ctx, allRSs, newRS, d)
}
扩容计算:
maxSurge := deployment.Spec.Strategy.RollingUpdate.MaxSurge
maxReplicas := deployment.Spec.Replicas + maxSurge
缩容计算:
maxUnavailable := deployment.Spec.Strategy.RollingUpdate.MaxUnavailable
minAvailable := deployment.Spec.Replicas - maxUnavailable
newRSUnavailablePodCount := newRS.Spec.Replicas - newRS.Status.AvailableReplicas
maxScaledDown := allPodsCount - minAvailable - newRSUnavailablePodCount
附录B: BatchRelease CR 完整示例
apiVersion: rollouts.yuyy.com/v1alpha1
kind: BatchRelease
metadata:
annotations:
current-stable-reversion: 5c66667f6
current-stable-reversion-raw: |
metadata:
creationTimestamp: null
labels:
app: yuyy-nginx
spec:
containers:
- image: nginx:1.15
name: nginx
ports:
- containerPort: 80
last-stable-reversion: 6d99c799f9
last-stable-reversion-raw: |
metadata:
creationTimestamp: null
labels:
app: yuyy-nginx
spec:
containers:
- image: nginx:1.14.2
name: nginx
ports:
- containerPort: 80
name: yuyy-nginx
namespace: default
spec:
strategy:
steps:
- replicas: 1
- replicas: 10%
- replicas: 30%
- replicas: 60%
- replicas: 100%
template:
metadata:
labels:
app: yuyy-nginx
spec:
containers:
- image: nginx:1.15
name: nginx
ports:
- containerPort: 80
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: yuyy-nginx
status:
phase: Completed
currentStepIndex: 4
currentStepState: Completed
lastUpdateTime: "2025-12-07T09:19:17Z"
maxSurge: 25%
maxUnavailable: 25%
message: ""
observedGeneration: 7
observedUpdateReversion: 5c66667f6
reason: ""
updatedReadyReplicas: 10







