CI/CD 流水线从 14 分 32 秒压到 3 分 47 秒,部署频率却几乎没动——我花了三个月才发现卡点在哪

🔑 关键词:CI/CD 流水线优化,DevOps 落地,部署频率,变更失败率,ArgoCD sync-wave

📖 摘要:一个 37 人团队的真实改造记录:流水线提速 74%,部署频率只从每周 1.8 次涨到 2.1 次。后来砍掉三人审批、取消发布窗口,频率上去了,变更失败率反而从 12% 涨到 19%。文末有 4 个能直接抄的配置细节。

先说个尴尬的结果

图片

2023 年 11 月到 2024 年 2 月,我在一家做 B 端 SaaS 的公司负责推部署流程的改造。团队 37 个人,后端 24、前端 9、QA 4,代码散在 11 个仓库里(我们试过一次 monorepo,三个月后放弃了,那段经历以后单独写)。

老板给的目标很朴素:上线这事儿能不能快点。

我的第一反应是流水线太慢。这个判断不算错。当时那套 GitHub Actions 跑一次平均 14 分 32 秒,我用 act 和 GitHub 自带的时间轴拆过一遍:

  • npm ci:4 分 12 秒(11 个 workspace,node_modules 加起来 1.4G)
  • jest:6 分 20 秒
  • docker build:2 分 50 秒
  • lint + 上传产物:1 分 10 秒

六周以后我挺得意的

图片

我干的事情说起来很无聊,都是文档里能查到的东西。

依赖那一步换成 pnpm,加上 --mount=type=cache,target=/root/.pnpm-store,4 分 12 秒降到 51 秒。jest 用 --shard=1/4 拆到四个 runner 上并行,6 分 20 秒变成 1 分 28 秒(并行分片的账单我没细算,感觉跟以前差不太多,因为单次跑的时间短了)。docker build 换 BuildKit,加 --cache-from 和多阶段构建,2 分 50 秒压到 58 秒。lint 那 1 分 10 秒几乎没动,我也懒得再抠了。

最后总时长 3 分 47 秒,降幅大概 74%。

我当时在周会上说这个数字的时候是有点得意的。然后接下来两个月,我们的部署频率从每周 1.8 次涨到了——每周 2.1 次。

那个感觉很奇怪,像你花两个月把高速公路修好了,结果发现路上根本没车。

图片

26 小时 36 分钟

我去翻了两个月的 Slack 和 Jira 记录,统计从 MR 合并到 kubectl apply 之间到底经历了什么。中位数是 26 小时 40 分钟

  • 从合并到第一个人 review:4 小时 10 分
  • review 完到 QA 在 staging 上验完:9 小时 20 分
  • 验完到发布窗口打开:11 小时 30 分(我们只有周二、周四 15:00–17:00 能发生产)
  • 窗口内的审批加部署:1 小时 40 分

流水线在那 26 小时 40 分里占了 3 分 47 秒。

我做了个横向对比。隔壁那个组 12 个人,做内部工具的,流水线 9 分 12 秒,比我们慢一倍还多。但他们每天部署 11 次。差别不在工具上:他们没有发布窗口,没有审批环节,出问题了 git revert 然后重跑,三分钟的事。

图片

砍掉审批之后,数字变难看了

2024 年 3 月,我把生产的审批从三个人减到零(改成 Slack 里一个按钮,谁点都行,但会记录是谁点的),发布窗口也从每天两次改成全天可发。

部署频率上去了,每天 11 次左右,跟隔壁组差不多。

但是变更失败率从 12% 涨到 19%

这个数字我纠结了很久要不要写出来,因为它跟很多 DevOps 文章的说法不太一致。我的解释是这样的:以前那三个人审批的时候,很多小毛病被提前挡在外面了,或者被延后到下一个窗口才发现,压根没进生产,也就不算“失败变更”。现在什么都往生产里扔,分母变大了,分子也变大了。

图片

同一时期 MTTR 从 4 小时 20 分降到 22 分钟,需要人工介入回滚的比例从 9% 降到 3%。

所以后来我们内部改了个口径,主看“需要人工介入回滚的比例”,不再拿变更失败率做考核。DORA 那四个指标是给你看趋势用的,一旦变成 KPI 就会开始变形——2023 年那份 Accelerate 报告有三万六千多人参与,样本够大了,但它也没法告诉你你们公司该怎么定指标。

四个可以直接抄的细节

1. ArgoCD 的 sync-wave。 我们有个服务,Deployment 和数据库迁移是两个分开的 Application,以前经常出现新 pod 起来了、迁移还没跑完,日志里刷一堆 column does not exist。给迁移那个 Application 加注解 argocd.argoproj.io/sync-wave: "-1",Deployment 用 "0",顺序就对了。这个在文档里,但很多人不看。

2. kubectl rollout status--timeout 默认值是 0,也就是无限等。 我们有个 job 卡了四个小时没人发现,就是因为这个。现在所有脚本统一 --timeout=180s

图片

3. Terraform 的 state 锁。 两个人同时 apply 会卡死很久。-lock-timeout=10m 只能缓解,根本解法是别让两个人碰同一个 state,我们按环境拆了目录之后这个问题就没了。

4. 翻 jest 那 6 分 20 秒的时候,发现里面有 47 秒是两年前写的一个 await new Promise(r => setTimeout(r, 1000)),注释写着“等下游服务”,那个服务 2022 年就下线了。没有人的代码是干净的,包括我自己的。

说点不确定的

我现在的看法是,大部分团队的 DevOps 卡点不在工具链上,在“谁有权决定这件事可以上线”上。工具链优化能拿到的东西是有上限的,大概就是从“等 20 分钟”变成“等 4 分钟”,但你后面还站着 26 个小时。

不过这套做法不一定适合你。如果你们是金融或者医疗,审批那关就是砍不掉,那也别硬砍,你能做的是把审批前面那 24 小时先干掉——review 的 SLA、staging 的可用性、测试数据的准备,这些比换 CI 工具实在得多。我们那个 staging 环境当时一周有两天是坏的,这事我一开始完全没注意到。

🏷️ 标签: