小Cの已经记不起来的博客

Git 工作流中的 灰度发布 最佳实践

我一开始以为灰度发布就是部署平台里点一下“10% 流量”,跟 Git 工作流没啥关系。结果后来发现真不是这么回事,代码要是从 Git 阶段就没把“哪些能发、哪些先别动、出问题怎么关”说清楚,灰度就容易变成线上抽奖。尤其是几个功能同时上,A 功能 5% 开着没事,B 功能合并后一跑,A 也跟着崩了,最后只能半夜回滚。

下面这套是我实际用下来比较顺的做法,不一定标准答案,但至少能少背锅。

别把分支当灰度开关

我以前也见过团队这么玩:

git checkout -b canary/feature-v2

然后让流水线判断:canary/* 分支就发 5%,main 分支就全量。

看起来挺直观,实际上很痛苦。因为同一个功能可能要反复灰度、扩量、暂停、回滚,你总不能每次都新建分支、删分支、改 CI。最后分支越堆越多,谁也不知道现在线上到底跑的是哪条分支。

更稳一点的做法是:Git 里只负责“这段代码是否进入发布版本”,灰度由 feature flag 或发布配置控制。

比如代码合进 main

git checkout main
git pull origin main
git checkout -b feat/search-canary
git commit -am "feat(search): support canary rollout behind flag"
git push -u origin feat/search-canary

PR 标题可以写:

feat(search): support canary rollout behind search_canary

这样 reviewer 一看就知道:这玩意儿合进去不会直接改线上,除非开关开了。

Feature flag 是灰度的核心,别写死在代码里

灰度期间最怕什么?就是每次从 5% 扩到 20%,还要重新改代码、提 PR、跑 CI、发版。那这不叫灰度,这叫重新发布。

我建议 flag 至少能控制:

flags:
  search_canary:
    enabled: true
    rollout_percentage: 5
    allowlist:
      - internal
      - qa
    default: false

代码里判断:

if flags.Bool("search_canary", user) {
    return searchV2(query)
}
return searchV1(query)

这比这种写法好太多了:

if user.ID % 100 < 5 {
    return searchV2(query)
}

第一种可以在配置平台点一下,第二种每次改比例都要上线。真出了线上问题,配置平台关一下 flag 比重新部署快多了。

我常用的一套路:main + release train

纯 GitFlow 太重,天天 feature、develop、hotfix、release,光切分支就累。后来我比较习惯主干开发,发布走 release train。

大概流程是:

git checkout main
git pull origin main

git checkout -b release/2026-09-12
git push -u origin release/2026-09-12

然后 PR 先合到 release,验证过了再合回 main。或者反过来,main 已经稳定了,从 main 切 release,把想发的那批 PR cherry-pick 进去。

git cherry-pick 1a2b3c4
git cherry-pick 5d6e7f8
git push origin release/2026-09-12

打灰度 tag:

git tag v2026-09-12-canary
git push origin v2026-09-12-canary

流水线识别到 canary 这个 tag,就先发 5%。没问题再打正式 tag:

git tag v2026-09-12
git push origin v2026-09-12

这样 Git 里至少有个东西能追踪:现在灰度的是哪个版本,正式发的是哪个版本。别等出事了在群里问“现在线上到底啥版本”。

PR 最好拆成“开关、实现、观测、清理”

大 PR 是灰度杀手。一个 PR 又改业务,又改数据库,又升级依赖,还顺手重构。灰度 5% 出错了,你根本分不清是哪块影响。

我比较喜欢这么拆:

# PR 1: 先加 flag,默认关闭
# PR 2: 新实现,只有 flag 打开才走
# PR 3: 加日志和指标,比如错误率、延迟、命中率
# PR 4: 配置从 0% 开 5%
# PR 5: 指标没问题,扩到 20%
# PR 6: 全量后一周内删 flag 和旧代码

这样每一步都能回滚,也能看懂。尤其是“清理旧 flag”这步很关键,不然半年后代码里全是 feature_v2_tmp_canary,新人看代码像考古。

灰度发布一定要留下回滚线索

出事的时候,最怕手忙脚乱找不到版本。每次灰度至少要在 release notes 或 MR 描述里写清楚:

## v2026-09-12-canary
- PR #132: feat(search): canary behind search_canary
- PR #133: fix(search): fallback timeout
- flag: search_canary
- rollout: 5%
- rollback: disable search_canary, or deploy v2026-09-11

回滚优先顺序我建议是:

  1. 关 feature flag
  2. 把 rollout percentage 改成 0
  3. 部署上一版 stable tag
  4. git revert 具体 PR

最后一步才轮到 Git revert:

git revert -m 1 <merge-commit>
git push origin main

合并提交回滚要看清 -m,不然 Git 会烦你。

不要只看 CI 绿

CI 绿只能说明构建和测试过了,不能说明线上 5% 用户没被影响。灰度观察至少看:

HTTP 5xx
P95 / P99 latency
业务成功率
错误日志新增关键字
flag 命中数量
客户端崩溃或兼容性问题

一个很土但有效的规矩:灰度 5% 至少看半小时,复杂一点的业务至少跨一个完整周期。别晚上快下班才开始灰度,除非你就想看告警群热闹。

几个坑,踩了真疼

第一个坑是数据库迁移和灰度混在一起。代码可以 5% 灰度,但 ALTER TABLE 是全量的,这种最容易炸。最好先让新旧代码都兼容,再灰度读写路径。

第二个坑是 flag 永远不删。代码里一堆 if else,开关多了根本不敢动,因为你不知道这个 flag 还有没有人用。

第三个坑是 release 分支谁都能直推。最后线上版本不是 Git 管,是谁手快管。release 分支最好 protected,必须走 PR 或者流水线,别给“我本地直接 push 一下”留机会。

第四个坑是只灰度代码不灰度配置。比如新增一个下游服务,flag 关了代码不走,但启动时还是初始化连接池,照样可能拖慢应用。这种依赖最好也支持延迟加载,或者单独灰度。

一个最小落地版本

如果团队现在没有完整体系,可以先做这几步:

git checkout main
git pull
git checkout -b feat/new-search

PR 要求:

- 默认不改线上行为
- 新功能带 flag
- PR 里写清楚 flag 名字
- 灰度 tag 或 MR 标签写 rollout 比例
- 回滚方式写清楚

发布时:

git checkout main
git pull
git checkout -b release/2026-09-12
git push -u origin release/2026-09-12
git tag v2026-09-12-canary
git push origin v2026-09-12-canary

等稳定后再自动化:CI 看到 canary 部署 5%,看到 stable 全量,看到 flag 配置变化就扩量。一开始别想一步到位,先把代码合并、配置开关、流量控制这三件事分开。

这样至少能明白:现在线上是哪个 Git tag,哪些 flag 开着,灰度比例是多少,出问题了先关什么,回滚回哪里。比“谁在群里喊一下我部署了最新版”靠谱多了。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放