4. 回滚触发机制:手动回滚、自动回滚(健康检查失败)、定时回滚策略
回滚触发机制,说白了就是「什么时候该跑路」。
我见过不少团队,镜像部署得挺溜,但回滚全靠人肉盯屏。结果呢?半夜三点线上挂了,运维还在翻日志找版本号。嗯,这其实挺要命的。
我个人习惯把回滚触发分成三类:手动、自动、定时。这三者不是互斥的,而是互补的。你想想看,一个健壮的系统,应该同时支持这三种方式,才能应对各种突发状况。
4.1 手动回滚:最后的保险栓
手动回滚,是最原始也最可靠的方式。为什么这么说?因为机器再智能,也理解不了业务上下文。
我在项目中遇到过这么一件事:某次新版本上线后,监控指标一切正常,但客服那边炸了锅——用户下单后收不到确认短信。健康检查只看服务是否活着,它看不出来短信通道是不是被运营商限流了。这时候,必须由人来做决策。
手动回滚的典型场景:
- 业务反馈异常,但监控指标正常
- 灰度发布中发现小范围问题,需要快速止损
- 合规或安全要求,需要人工审批后才能回滚
手动回滚的操作流程,我建议做成「一键式」的。别让运维在半夜还要敲一堆命令。
# 一个简化版的手动回滚脚本示例
# 假设我们使用 Kubernetes
kubectl rollout undo deployment/my-app -n production
# 如果想回滚到指定版本
kubectl rollout undo deployment/my-app --to-revision=3 -n production
# 确认回滚状态
kubectl rollout status deployment/my-app -n production
这里有个坑:回滚前一定要确认当前版本号。我曾经见过有人手滑,把刚上线半小时的版本给回滚了,结果滚到了更老的版本,数据兼容性出了问题。所以,我习惯在回滚前先跑一条检查命令:
# 查看历史版本
kubectl rollout history deployment/my-app -n production
# 输出示例
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# 3 <none>
# 4 <none> <-- 当前版本
注意:手动回滚虽然可靠,但依赖人的响应速度。如果半夜出问题,从发现到决策再到执行,少说也要 5-10 分钟。对于高并发场景,这时间足够造成大量损失了。
4.2 自动回滚:健康检查失败时的自救
自动回滚,是系统在检测到「自己不行了」的时候,主动退回到上一个稳定版本。
核心触发条件就是健康检查失败。健康检查分两种:
- 存活检查(Liveness Probe):容器还活着吗?死了就重启。
- 就绪检查(Readiness Probe):容器能处理请求吗?不能就摘掉流量。
自动回滚的逻辑通常是:如果就绪检查连续失败 N 次,且持续时间超过 M 秒,则触发回滚。
我给大家看一个我在生产环境用过的配置:
# Kubernetes Deployment 中的健康检查配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
revisionHistoryLimit: 10 # 保留最近10个版本,方便回滚
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
spec:
containers:
- name: app
image: my-app:1.2.3
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 3
failureThreshold: 2
这里有个细节:failureThreshold 不要设得太小。我曾经设成 1,结果网络抖动了一下,整个集群开始疯狂回滚,那场面……嗯,挺壮观的。建议至少设成 3,给系统一点容错空间。
我的经验:自动回滚一定要配合「回滚熔断」机制。如果回滚后健康检查仍然失败,说明问题可能不在代码层面(比如数据库 schema 变了),这时候应该停止自动回滚,转为人肉介入。否则会陷入「回滚-失败-再回滚」的死循环。
4.3 定时回滚:计划内的「后悔药」
定时回滚,这个可能有些人觉得奇怪——为什么要定时回滚?
其实它主要用在两个场景:
- 灰度发布的「观察期」回退:比如你计划灰度 10% 的流量,观察 24 小时。如果 24 小时后没有人工确认「没问题」,系统自动回滚到上一个版本。
- 定期回归测试后的回滚:有些团队会在凌晨跑自动化回归测试,测试通过后自动推进,测试失败则自动回滚到基线版本。
定时回滚的配置,我习惯用 CronJob 来实现:
# 一个定时回滚的 CronJob 示例
apiVersion: batch/v1
kind: CronJob
metadata:
name: scheduled-rollback
spec:
schedule: "0 3 * * *" # 每天凌晨3点执行
jobTemplate:
spec:
template:
spec:
containers:
- name: rollback-check
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
# 检查当前版本是否已经运行超过24小时
# 如果是,且没有人工确认标记,则回滚
CURRENT_REVISION=$(kubectl rollout history deployment/my-app -n production | tail -1 | awk '{print $1}')
DEPLOY_TIME=$(kubectl get deployment my-app -n production -o jsonpath='{.metadata.annotations.deploy-time}')
CURRENT_TIME=$(date +%s)
if [ $((CURRENT_TIME - DEPLOY_TIME)) -gt 86400 ]; then
echo "版本已运行超过24小时,未收到确认,执行回滚"
kubectl rollout undo deployment/my-app -n production
else
echo "版本运行时间不足24小时,跳过"
fi
restartPolicy: OnFailure
注意:定时回滚是一把双刃剑。用得好,可以防止「忘了确认」导致的线上事故;用不好,可能会在业务高峰期突然回滚,造成更大的混乱。我建议只在灰度发布阶段使用定时回滚,全量发布后应该关闭这个策略。
4.4 三种触发机制的对比
为了方便大家选择,我整理了一个对比表:
| 触发方式 | 响应速度 | 可靠性 | 适用场景 | 风险点 |
|---|---|---|---|---|
| 手动回滚 | 慢(分钟级) | 高(人肉判断) | 业务异常、合规审批 | 依赖人的响应速度 |
| 自动回滚 | 快(秒级) | 中(依赖监控) | 健康检查失败、性能劣化 | 可能误触发,需要熔断 |
| 定时回滚 | 计划内(小时级) | 中(依赖时间窗口) | 灰度观察期、回归测试 | 可能打断正常业务 |
4.5 核心逻辑流程图
下面这张图,是我梳理的回滚触发机制整体流程。你可以看到,三种触发方式最终都会汇聚到「回滚执行引擎」,但决策路径完全不同。
从这张图你能看出来,三种触发方式最终都会走到「回滚条件判断」这个节点。但自动触发和定时触发多了一层「条件是否满足」的校验,这是为了防止误操作。
我的建议:在实际生产中,我通常把三种方式组合使用。手动回滚作为兜底,自动回滚处理常见故障,定时回滚用于灰度发布的「超时未确认」场景。三者配合,基本能覆盖 99% 的回滚需求。
嗯,关于回滚触发机制,核心就是这些。记住一句话:回滚不是失败,而是对系统负责的表现。设计好触发机制,比设计好部署机制更重要。
可可精品资料库