AI回滚策略:灾难不够用了之后

一次事故后我们发现,回滚按钮好按,但微服务、模型文件和数据库 schema 往往对不齐。下面记录后来怎么把回滚从"碰运气"改成可预期流程。

问题是这样的

在那次事故之前,我们对回滚的理解很简单:发现问题,点击一个按钮,一切恢复如初。但现实往往比想象复杂:

  • 微服务环境下,A服务回滚了,依赖它的B服务还在用新版本API
  • 大模型升级后,模型文件、权重配置、推理框架需要同步回退
  • 数据库schema变更,哪怕只是加了一个字段,直接回滚老版本代码就崩
  • 配置中心改了一项参数,谁还记得三天前的值是什么?

这些问题堆在一起,所谓的"快速回滚"往往变成了"快速制造二次事故"。

背景和约束

环境大致如下:

  • 20+个微服务,Kubernetes部署
  • 3个大模型推理服务,分别处理NLP、CV和多模态任务
  • 分布式配置中心(Nacos)
  • 数据库使用MySQL + PostgreSQL
  • 要求RTO(恢复时间目标)不超过15分钟
  • 不允许长时间停机回滚

在这样的环境下,简单粗暴的回滚方案显然不够用。

我们的回滚演进史

第一阶段:纯人工回滚

最开始,我们的回滚流程是这样的:

# 发现问题
# 告警 -> 值班工程师 -> 登录跳板机 -> 查找问题版本 -> git checkout旧版本 -> 重新构建 -> kubectl apply
# 整个过程取决于工程师的经验和反应速度,从15分钟到1小时不等

这个阶段的致命问题是:过程依赖人工记忆,不同工程师的执行步骤可能有差异,关键操作没有审计。

第二阶段:GitOps + ArgoCD

引入ArgoCD后,我们把回滚过程标准化了:

# 通过Git记录每一次部署的状态
# 回滚就是将Git仓库恢复到之前的commit
# ArgoCD自动检测到变化,将集群状态同步到期望状态

这时候的回滚流程:

sequenceDiagram participant 告警系统 participant 工程师 participant Git仓库 participant ArgoCD participant K8s集群 告警系统->>工程师: 异常告警 工程师->>Git仓库: git revert到上一版本 Git仓库-->>ArgoCD: Webhook通知 ArgoCD->>K8s集群: 同步到期望状态 ArgoCD-->>工程师: 回滚完成通知

好多了,至少回滚过程是可审计、可重复的。但新的问题来了:

  • 代码回滚了,数据库schema怎么回滚?
  • 模型文件回滚了,推理框架版本不兼容怎么办?
  • 配置中心改过的东西,Git里没有历史记录

第三阶段:分层回滚策略

我们意识到,回滚得按资源类型拆开设计,没法一个按钮兜住所有层。

应用层回滚:代码、配置、Docker镜像,通过GitOps管理

数据层回滚:数据库变更需要通过迁移脚本管理,支持正向迁移和反向迁移

模型层回滚:模型文件版本管理,每次升级保留上一个版本,回滚时同步切换模型和推理框架

基础设施回滚:Terraform状态管理,基础设施变更通过基础设施即代码

这个阶段,我们开始把回滚策略拆分成几个层次。

当前实践:智能回滚框架

现在我们的回滚框架是这样的,它解决了前面提到的大部分问题,但也有一些边界。

核心设计思路

1. 不可变性原则

每次部署产出一个不可变 release。回滚等于切回之前保存的那份完整状态,不是在原 deployment 上改镜像 tag。

# 原来的做法(可变基础设施)
kubectl set image deployment/myapp myapp=myapp:v2
# 回滚:kubectl set image deployment/myapp myapp=myapp:v1
# 问题:中间过程不可控,容易被覆盖

# 现在的做法(不可变基础设施)
helm upgrade --install myapp ./myapp --version 1.2.3
# 回滚:helm rollback myapp 2
# 优势:每次部署都是一个完整的、不可变的release

2. 全链路版本一致性

一个变更可能涉及多个组件,回滚时必须保证所有相关组件都回到同一版本。

# 版本元数据示例
version: v2.3.1
components:
  - name: nlp-inference
    version: v2.3.1
  - name: nlp-model
    version: v2.3.0  # 注意:模型版本和代码版本不一定完全一致
  - name: inference-framework
    version: v1.5.2

回滚时,系统会检查版本依赖关系,确保不会回滚到不兼容的组合。

3. 自动健康检查

回滚完成后还要跑健康检查——切版本只是第一步,服务真恢复得靠探针和自定义脚本确认。

# 健康检查配置
healthChecks:
  - type: http
    endpoint: /health
    expectedStatus: 200
    timeout: 10s
  - type: custom
    script: /scripts/check-model-availability.sh
    threshold: 0.95  # 模型可用率必须达到95%

回滚触发机制

我们设计了多种触发回滚的方式,避免完全依赖人工判断。

1. 自动检测

监控指标异常时,系统可以自动触发回滚:

# 伪代码示例
if error_rate > 0.05 and latency_p99 > 1000ms:
    if recent_deployment:
        trigger_rollback(to_version=previous_stable_version)
    else:
        notify_oncall()

这里有个关键问题:如何区分"部署引起的问题"和"外部因素引起的问题"?我们在实际使用中设置了30分钟的"冷却期",在这段时间内出现问题,优先考虑回滚。

2. 人工触发

值班工程师可以通过CLI或Web界面触发回滚,但必须说明原因。

# 人工回滚命令
$ ./rollback-service --service nlp-inference --reason "推理延迟过高"
Rollback initiated for nlp-inference from v2.3.1 to v2.2.5
Reason: 推理延迟过高
Triggered by: [email protected]

3. 计划回滚

有些场景下,我们会在低峰期主动触发回滚,比如测试新版本回滚流程是否顺畅。

大模型场景的特殊处理

大模型服务的回滚有几个特殊点需要考虑:

模型文件很大:动辄几十GB,回滚时快速下载是关键。我们使用了分层缓存策略:

graph TB A[回滚触发] --> B{模型文件是否在本地缓存} B -->|是| C[直接使用缓存版本] B -->|否| D[从最近的数据中心拉取] D --> E{最近的数据中心是否有} E -->|是| F[快速下载] E -->|否| G[从原始存储拉取] F --> H[更新缓存] G --> H C --> I[启动推理服务] H --> I

推理框架依赖:不同版本的模型可能需要不同版本的推理框架。回滚时需要同步切换。

# 模型和推理框架版本对应关系
nlp-model-v2.3.0 -> pytorch-2.0.0 + transformers-4.30.0
nlp-model-v2.2.5 -> pytorch-1.13.0 + transformers-4.20.0

# 回滚脚本示例
#!/bin/bash
MODEL_VERSION=$1
FRAMEWORK_VERSION=$(get_framework_version $MODEL_VERSION)
pip install "torch==$FRAMEWORK_VERSION" "transformers==$FRAMEWORK_VERSION"

GPU状态恢复:模型加载后,GPU的显存占用和状态可能不同,需要重新初始化。

踩过的坑

坑一:回滚时间预估不准

第一次做压力测试回滚时,我们预估5分钟就能完成,结果用了20分钟。原因是:

  • 模型文件下载比预期慢(网络抖动)
  • GPU显存释放有延迟
  • 健康检查脚本写得不够健壮

教训:回滚时间要包含最坏情况的缓冲,并且要定期实际演练。

坑二:配置回滚和数据回滚冲突

有一次回滚时,我们发现配置回滚成功了,但数据库里的配置数据还是新版本。原因是:

  • 应用配置存在Nacos
  • 一些配置数据存在MySQL
  • 两者回滚策略不一致

教训:所有配置变更都要通过统一的管理平台,避免"配置散落各处"。

坑三:回滚后无法重现问题

回滚确实解决了眼前的问题,但事后分析时,我们无法重现问题现象,因为系统状态已经改变。

教训:回滚前必须先"保存现场":

# 保存当前状态
./save-state --include logs,metrics,config,version
# 再执行回滚
./rollback-service --service nlp-inference

坑四:部分回滚导致服务不可用

只回滚了应用代码,没有回滚依赖的配置,导致服务启动失败。

教训:回滚必须按版本元数据整链切换,单个服务单独退版本很容易把依赖关系扯断。

结果和边界

实施这套回滚策略后,我们的RTO从30分钟降到了8分钟,事故影响范围也明显缩小。但也不是所有场景都适用:

适用场景

  • 标准化的微服务架构
  • 有清晰的版本管理
  • 有完善的监控和告警
  • 能够接受一定程度的回滚时间

不适用场景

  • 高频交易等对延迟极度敏感的场景(回滚可能造成短暂中断)
  • 涉及金融数据等不可逆操作的场景(只能通过补偿,不能简单回滚)
  • 单体应用拆分不完全,内部依赖复杂的情况

结语

分层回滚加全链路版本一致性之后,RTO 从 30 分钟压到 8 分钟。大模型场景还要额外处理模型文件缓存、推理框架版本和 GPU 状态恢复。

回滚前记得先保存现场(日志、指标、配置、版本),否则事后很难复现问题。演练频率不能省——我们第一次压测预估 5 分钟,实际跑了 20 分钟。

版权声明: 本文首发于 指尖魔法屋-AI回滚策略:灾难不够用了之后https://blog.thinkmoon.cn/post/290-ai-rollback-strategy-disaster-safe-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!