上线出问题别硬扛,回滚这条路要提前铺好

极狐GitLab
2026年11月3日
18762
分享:

上线出问题别硬扛,回滚这条路要提前铺好

先明确一件事:回滚不是「撤销发布」,而是重新部署一次已经验证过的历史版本。极狐GitLab 的环境(Environment)会记录每一次部署,需要回退时,从环境的部署标签页里选择要回滚到的那次部署即可;旗舰版还提供自动回滚,在检测到严重警报时自动重新部署最近一次成功的部署。整套机制的前提是——部署动作本身是可重复的,且制品是可追溯的。

为什么回滚总在出事那一刻才被想起

大部分团队的流水线写得相当漂亮:构建、测试、扫描、部署,一条龙。但把「部署」这一段单独拎出来问一句「如果要回到上一个版本,你敲什么命令」,能立刻答上来的人不多。

原因不难理解。回滚是一类典型的低频高危动作——平时一次都用不上,用上的时候整个团队都在盯着监控大屏。于是它通常演化成三种形态:

  • 人肉形态:谁发布的谁 SSH 上去改,改完没人知道现在跑的到底是哪个版本。

  • 考古形态:翻流水线历史,找到上一次成功的作业,手动重跑,祈祷它还能跑通。

  • 硬扛形态:不回滚,直接在生产上热修,把一次发布事故变成一次发布事故加一次未经测试的热修。

这三种形态的共同点是:回滚耗时无法预估。而事故响应里最贵的从来不是「改回去」这个动作,是「搞清楚怎么改回去」的时间。

真正让回滚变便宜的,不是多写一段脚本,而是在日常流水线里就把三件事固化下来:环境有名字、部署有记录、制品可重复。

把回滚做成一次普通部署

先说最基础的一层。极狐GitLab 里,只要在作业中声明 environment,这次部署就会被记录进环境的部署历史,带上提交 SHA、流水线编号和部署时间:

deploy:production:
  stage: deploy
  script:
    - ./scripts/deploy.sh "$CI_COMMIT_SHA"
  environment:
    name: production
    url: https://prod.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual

这一段带来的价值在平时是隐性的,出事时是决定性的:环境页面会保留完整的部署列表,回滚时直接从部署标签页里选目标部署,不需要任何人回忆上一次上线的是哪个提交。

when: manual 这个配置值得单独说一句。把生产部署设成手动,等于给每次上线留了一个明确的审批点和操作人,事后复盘时「谁在什么时间点了发布」是现成的,不用翻聊天记录。

第二层是让部署脚本幂等且参数化。上面例子里把 $CI_COMMIT_SHA 作为参数传给部署脚本,就是为了同一个脚本既能部署新版本,也能重新部署旧版本。如果脚本里写死了分支名或者 latest 标签,回滚时就只能靠人改代码——那就又回到了硬扛形态。

一个稳妥的做法是让部署脚本接受一个「版本标识」入参,回滚时传入历史部署的 SHA:

rollback:production:
  stage: deploy
  script:
    - ./scripts/deploy.sh "$ROLLBACK_TO_SHA"
  environment:
    name: production
    url: https://prod.example.com
  variables:
    ROLLBACK_TO_SHA: "请填入目标部署的提交 SHA"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual

这个作业的用处不只是「能回滚」,而是把回滚这件事变得可审计:谁发起的、回到哪个 SHA、花了多久,全部和其他流水线一样留痕。

自动回滚:让人从告警里摘出来

手动回滚解决的是「能回」,自动回滚解决的是「人不在的时候也能回」。逻辑缺陷和性能劣化经常在深夜发生,等值班同学被电话叫醒、打开电脑、找到正确的部署,十几分钟已经过去了。

极狐GitLab 的自动回滚会在检测到严重警报时,自动选择并重新部署最近一次成功的部署。要让它知道该回滚哪个环境,警报载荷里必须带上 gitlab_environment_name 键,值为环境名称:

{
  "gitlab_environment_name": "production",
  "title": "生产环境 5xx 错误率超过阈值",
  "severity": "critical",
  "start_time": "2026-11-03T02:14:00Z"
}

开启路径在项目的 设置 > CI/CD,展开「自动部署回滚」,勾选「启用自动回滚」。这项能力属于旗舰版,JihuLab.com 与私有化部署均可用,且默认是关闭的。

但必须清楚它的边界,否则开了自动回滚反而会制造新的意外:

  • 如果检测到警报时已经有部署正在运行,这次回滚会被跳过。

  • 回滚存在三分钟冷却:三分钟内只会回滚一次,同一时间检测到多个警报也只执行一次。

  • 它回滚的是「最近一次成功部署」,如果最近那次成功部署本身就带着问题,自动回滚只会把你送回同一个坑里。

所以自动回滚的正确定位是争取时间的手段,不是免检证明。它替你做完第一轮止血,人还是要起来看清楚根因。

四个容易踩的坑

一、回滚了代码,没回滚数据。 这是回滚事故里最常见的一类。代码回到上一版,数据库 schema 却已经是新版,结果往往是更严重的报错。凡是涉及不可逆的数据库变更(删列、改类型、重命名字段),都应该走可扩展迁移:先加新结构、双写、再切读、最后清理旧结构,让每个步骤之间的代码版本都能独立运行。做不到这一点,就要提前讲清楚这个版本不支持回滚。

二、临时环境被自动停止了,没人发现。 环境支持 auto_stop_in 配置,例如 auto_stop_in: 1 week,到期后会自动触发作业停止环境。这里有个容易忽略的实现细节:用于停止环境的后台工作进程受资源限制每小时才运行一次,所以实际停止时间会晚于你设定的时刻。另外,带 action: access 或 prepare 的作业会重置计划停止时间,而 verify 操作不会——如果你只想检查环境状态不想续期,用 verify。

三、回滚目标选错了。 部署列表里排在最前面的不一定是你想要的那个。回滚前核对两件事:目标部署对应的提交 SHA,以及那次部署之后的数据库变更记录。

四、把回滚当成了免责。 回滚成功不等于问题解决,它只是把影响窗口缩短了。没有根因分析和后续的回归测试,同一个问题会在下一次发布时原样回来。

常见问题

回滚会影响 Git 提交历史吗?

不会。回滚是重新部署一个历史版本,不改变分支和提交历史。如果你需要的是撤销代码变更,那是另一个动作( revert 提交或回滚合并请求),不要和部署回滚混为一谈。

自动回滚开着,是不是就不需要值班了?

不能这么设计。自动回滚有部署并发时跳过、三分钟冷却两项限制,且只针对能触发严重警报的故障类型。它覆盖不了「发布后三天才暴露的慢查询」这类问题。

临时环境要不要配自动停止?

建议配。auto_stop_in 能避免评审环境长期占用资源,成本账很直接。但要让团队知道停止时间存在约一小时的调度误差,不要拿它做精确的定时清理。


想把这些能力落到自己的流水线里? 可以从官方文档中心查阅环境与环境管理以及 CI/CD 变量,照着示例把 environment 声明和回滚作业先加进一个非关键项目试跑一次。真正演练过一次回滚,才知道自己的发布链路到底结不结实。