1. 先校准目标:CI 可观测性到底要解决什么?
很多人把 CI 的可观测性理解成“多打点、多存日志、多画图”,最后得到的是:海量数据 + 更慢的流水线。 真正要解决的是三个工程问题:
- 诊断效率:失败发生时,能不能在 5 分钟内判断“代码问题还是平台问题”?
- 治理优先级:哪一类失败/慢最值得修?修完能带来多少收益?
- 反馈闭环:你修复了某类失败之后,它是否真的下降?有没有复发?
2. 信号与指标:把“体验”变成可计算的 SLI
对研发而言,CI 的“体验”就是:改完代码多久能知道结果、失败时多久能定位、通过率稳不稳、队列会不会爆。 我们用 CI 版四大信号来刻画它(你可以把它理解为 CI 的 SLI 库)。
2.1 Throughput:吞吐(“我们每小时能验证多少变化?”)
- Runs per hour:每小时运行次数(按仓库/分支/工作流拆维度)
- Validated commits:被验证的提交数(排除跳过 CI 的提交)
- Change lead time (CI segment):从提交到 CI 给出结论的时间段贡献
2.2 Latency:延迟(“从提交到结果,中间到底在等什么?”)
延迟要拆解,否则你会被平均值骗。
- Queue time:排队等待执行(平台饱和最敏感)
- Setup time:拉代码/装依赖/下载缓存/启动服务
- Test time:单测/集成/E2E 的耗时分布(P50/P95/P99)
- Artifact time:构建、打包、上传、签名、SBOM 生成
2.3 Errors:错误(“失败是不是稳定可复现?失败原因属于谁?”)
- Failure rate:失败率(分 workflow/job/step)
- Flake rate:不稳定测试比例(同一变更重复运行结果不一致)
- Retry rate:重跑率(重点:重跑是否能绿?绿了说明是 flaky 或环境问题)
- Ownership:失败归属(代码/测试/依赖/基础设施/配置/权限)
2.4 Saturation:饱和(“平台是不是在喘不过气?”)
- Runner utilization:Runner 使用率、并发槽位占用
- Queue depth:队列深度与增长速度
- Cache hit rate:缓存命中(命中下降经常意味着 key 设计或依赖波动)
- External dependencies:npm/pypi/docker registry 的失败/耗时
3. 日志:从“打印字符串”到“可关联的事件流”
CI 日志的第一原则:能关联。能把一条异常输出和具体的 run/job/step、代码 SHA、重试次数、runner 类型、缓存命中、依赖版本对应起来。 否则你看到的只是“某次失败的碎片”,很难比较与归因。
3.1 结构化日志(即使最终展示成文本)
- 最小字段:run_id / attempt / job / step / sha / repo / branch
- 环境字段:runner 镜像、OS、CPU/内存规格、网络区域、工具链版本
- 依赖字段:lockfile hash、依赖下载源(registry)、缓存 key
3.2 日志的“证据链”
你可以把一次 CI 运行当成侦探故事:线索(日志)必须能串起来。 常见的证据链片段包括:
- 时间线:每个 step 的开始/结束时间(用于定位瓶颈与关键路径)
- 资源线:CPU、内存、磁盘、网络(定位“隐形饱和”)
- 外部依赖线:registry、git server、artifact store 的耗时/错误
- 重试线:第几次 attempt 成功?失败模式是否一致?
4. 指标:把“看板”升级成“可告警的系统”
指标的价值不是“有很多图”,而是:当 CI 体验变差时,你能在用户(研发)抱怨之前就发现,而且能定位到责任面。
4.1 告警的设计:别让噪声杀死系统
- 优先告警“趋势”而不是“单点”:例如失败率在 30 分钟窗口内从 2% → 8%,比某一次失败更值得告警。
- 拆维度:同一个告警必须指出“受影响范围”。例如只影响某个 runner 池、某个 workflow、某个分支策略。
- 把“可控性”写进阈值:阈值要能触发明确动作:扩容、回滚 runner 镜像、冻结合并、隔离 flaky 测试。
5. 追踪:把一次 CI 运行的“关键路径”画出来
追踪并不只属于微服务。CI 也有“调用链”:拉代码 → 装依赖 → 构建 → 测试 → 上传 → 发布(或生成产物)。 当你能把这些 step 变成 span(哪怕只是概念上的 span),你就能回答一个关键问题:到底是哪一段在拖慢端到端?
- 关键路径:决定总耗时的最长路径(并行任务越多,这个越重要)
- 扇出/扇入:并发矩阵、分片测试、并行构建会让“瓶颈”从单点变成系统问题
- 跨系统关联:CI 与 artifact store、registry、集群 API 的关联(常见慢点)
6. 失败画像落地:给每一类失败一张“诊断卡”
画像不是 PPT 的分类树,而是可以直接用于 On-call 的“诊断卡”。你可以把每类失败固化成一个模板:
- 判定条件:如何识别?(退出码、关键字、超时阈值、集中度)
- 第一证据:第一眼看什么?(哪个 step、哪个外部依赖、是否 queue 激增)
- 第二证据:如何快速排除?(重跑是否可绿、是否只在某 runner 发生)
- 默认动作:隔离、回滚、扩容、冻结合并、降级检查项
- 修复后的验证:哪个指标必须下降?观察窗口多长?
7. 一次失败的证据链演练:从红灯到根因
下面这张图用“证据链”的方式演示一次失败如何被拆解。你会注意到:每一步都在把问题空间缩小,直到能做出最小可行的修复动作。
8. 最后把它变成系统:你需要的最小落地清单
- 关联键:统一 run_id / attempt / sha / runner_pool / cache_key,贯穿日志与指标。
- 失败画像:至少先落地 4 类(Code / Flaky / Dependency / Infra),并能自动打标签。
- 核心指标:Queue P95、Total P95、Failure rate、Retry rate、Flake rate、Cache hit rate。
- 告警规则:趋势型 + 拆维度 + 指向明确动作(扩容、回滚、隔离、冻结)。
- 治理例会:每周只做两件事:删掉噪声、消灭 Top 1 失败类别(或 Top 1 慢点)。
1) 这次失败属于哪类画像?
2) 我需要的证据链是什么?下一步看哪里?
3) 修复后我用哪个指标证明“真的变好了”?