第17章|CI 的可观测性:日志、指标、追踪与失败画像

你会发现:CI 失败本身并不可怕,可怕的是“失败不可解释”。这一章把 CI 当成一条生产线:它也需要可观测性—— 不是为了看起来高级,而是为了把 排障 变成 工程化,把“红了就重跑”变成“红了就知道为什么、该改哪里、值不值得重跑”。

Quick wins

从“看日志”升级到“看画像”

  • 把失败分成可治理的类别:代码/环境/依赖/基础设施/不稳定
  • 给每类失败配“证据链模板”,减少拍脑袋
  • 用数据驱动:降低重跑率、平均恢复时间(MTTR)
Core model

CI 的四大信号(CI-SLI)

  • Throughput:吞吐 / 交付节奏
  • Latency:端到端耗时 / 等待
  • Errors:失败率 / 失败类型
  • Saturation:资源饱和 / 队列压力
Outcome

把 CI 变成“可诊断系统”

  • 日志:结构化 + 可关联(run/job/step)
  • 指标:可告警(不是“看板漂亮”)
  • 追踪:一次运行的关键路径(Critical Path)

1. 先校准目标:CI 可观测性到底要解决什么?

很多人把 CI 的可观测性理解成“多打点、多存日志、多画图”,最后得到的是:海量数据 + 更慢的流水线。 真正要解决的是三个工程问题:

  1. 诊断效率:失败发生时,能不能在 5 分钟内判断“代码问题还是平台问题”?
  2. 治理优先级:哪一类失败/慢最值得修?修完能带来多少收益?
  3. 反馈闭环:你修复了某类失败之后,它是否真的下降?有没有复发?
一句话定义:CI 可观测性 = 让每一次运行都能被“解释、比较、归因、改进”。如果做不到归因,数据再多也只是装饰。

2. 信号与指标:把“体验”变成可计算的 SLI

对研发而言,CI 的“体验”就是:改完代码多久能知道结果、失败时多久能定位、通过率稳不稳、队列会不会爆。 我们用 CI 版四大信号来刻画它(你可以把它理解为 CI 的 SLI 库)。

2.1 Throughput:吞吐(“我们每小时能验证多少变化?”)

2.2 Latency:延迟(“从提交到结果,中间到底在等什么?”)

延迟要拆解,否则你会被平均值骗。

提示:把延迟分成 等待(Queue)和 工作(Run)两类,治理路径完全不同:等待靠扩容/并发/调度,工作靠缓存/并行/裁剪/增量。

2.3 Errors:错误(“失败是不是稳定可复现?失败原因属于谁?”)

2.4 Saturation:饱和(“平台是不是在喘不过气?”)

CI 可观测性看板:从信号到行动 核心:Throughput / Latency / Errors / Saturation Throughput 128 runs/hour Latency 8m 42s P50 6m · P95 14m · Queue 2m Run Queue Errors 3.8% flake 1.2% · infra 0.9% · code 1.7% MTTR: 11m Saturation & Evidence 把“可观测数据”串成一次运行的证据链:run → job → step → artifact → external Runner pool util 78% cache hit 61% Evidence chain run → job → step → ext 关联键:run_id / sha / attempt
图 1:CI 可观测性“信号→证据链→行动”的看板模型。关键不是数字本身,而是它能否直接指向治理动作。

3. 日志:从“打印字符串”到“可关联的事件流”

CI 日志的第一原则:能关联。能把一条异常输出和具体的 run/job/step、代码 SHA、重试次数、runner 类型、缓存命中、依赖版本对应起来。 否则你看到的只是“某次失败的碎片”,很难比较与归因。

3.1 结构化日志(即使最终展示成文本)

反模式:把“重跑能绿”的失败当成代码问题。这种失败最该优先治理,因为它吞噬的是团队注意力:每一次重跑都在偷你的专注力与节奏。

3.2 日志的“证据链”

你可以把一次 CI 运行当成侦探故事:线索(日志)必须能串起来。 常见的证据链片段包括:

4. 指标:把“看板”升级成“可告警的系统”

指标的价值不是“有很多图”,而是:当 CI 体验变差时,你能在用户(研发)抱怨之前就发现,而且能定位到责任面。

4.1 告警的设计:别让噪声杀死系统

  1. 优先告警“趋势”而不是“单点”:例如失败率在 30 分钟窗口内从 2% → 8%,比某一次失败更值得告警。
  2. 拆维度:同一个告警必须指出“受影响范围”。例如只影响某个 runner 池、某个 workflow、某个分支策略。
  3. 把“可控性”写进阈值:阈值要能触发明确动作:扩容、回滚 runner 镜像、冻结合并、隔离 flaky 测试。
经验阈值(可起步用):Queue time 的 P95 超过 3-5 分钟通常意味着平台饱和或突发流量;Retry rate 超过 10% 往往意味着测试不稳定或环境漂移。

5. 追踪:把一次 CI 运行的“关键路径”画出来

追踪并不只属于微服务。CI 也有“调用链”:拉代码 → 装依赖 → 构建 → 测试 → 上传 → 发布(或生成产物)。 当你能把这些 step 变成 span(哪怕只是概念上的 span),你就能回答一个关键问题:到底是哪一段在拖慢端到端?

失败画像(Failure Portrait):分类 → 归因 → 治理闭环 目标:降低重跑率与 MTTR,把“红了”变成“知道怎么修” Taxonomy Code 编译错误 / 断言失败 / 逻辑回归 Test (Flaky) 偶发超时 / 顺序依赖 / 非确定性 Dependency registry 抖动 / 版本漂移 / lockfile 变更 Infra/Platform runner 镜像 / 网络 / 存储 / 权限 / 配额 Attribution & Loop 闭环 识别 → 归因 → 修复 → 验证 识别:自动打标签 按 step 错误码 / 超时 / 关键字 / 退出码 / 依赖源异常 归因:确定责任面 代码 vs 平台:重跑是否可绿?是否集中在某 runner/区域? 修复:最小改变、可回滚 隔离 flaky / 回滚 runner 镜像 / 固化依赖 / 调整缓存 key 验证:指标回落 + 复发监控 观察 7 天窗口:flake rate、retry rate、MTTR、queue P95 失败样本 → 画像
图 2:失败画像 = 可治理的分类系统 + 归因规则 + 治理闭环。它让“重跑”从习惯动作变成最后手段。

6. 失败画像落地:给每一类失败一张“诊断卡”

画像不是 PPT 的分类树,而是可以直接用于 On-call 的“诊断卡”。你可以把每类失败固化成一个模板:

  1. 判定条件:如何识别?(退出码、关键字、超时阈值、集中度)
  2. 第一证据:第一眼看什么?(哪个 step、哪个外部依赖、是否 queue 激增)
  3. 第二证据:如何快速排除?(重跑是否可绿、是否只在某 runner 发生)
  4. 默认动作:隔离、回滚、扩容、冻结合并、降级检查项
  5. 修复后的验证:哪个指标必须下降?观察窗口多长?
关键:诊断卡的目标不是“写得很全”,而是让新人也能做出正确的第一步。你要把最容易走错的路,写成明确的“不做什么”。

7. 一次失败的证据链演练:从红灯到根因

下面这张图用“证据链”的方式演示一次失败如何被拆解。你会注意到:每一步都在把问题空间缩小,直到能做出最小可行的修复动作。

证据链:把一次失败“拆成可判定的事实” 从 run → job → step → 外部依赖 → 根因 → 修复动作 事实 1:失败集中度 仅发生在 ubuntu-latest runner 池 同一 SHA 重跑 3 次,2 次成功 事实 2:关键 step npm ci 偶发超时(网络/registry) cache hit 从 75% 降到 41% 事实 3:外部依赖 registry 响应 P95 激增 同时间窗口多仓库共振失败 Diagnosis 判定:更像平台/依赖问题(非代码) 理由:重跑可绿 + 多仓库共振 + runner 池集中 动作 A:降低外部依赖波动 切换镜像源 / 本地代理缓存 / 增强重试策略(带退避) 并记录 registry 错误码与耗时指标 动作 B:修复缓存 key 与固化依赖 cache key: lockfile hash + node version 避免“命中下降 → 下载增多 → 更慢更不稳”的正反馈 关键技巧:先用“集中度 + 重跑可绿 + 共振窗口”划分责任面,再决定是否投入深挖代码。
图 3:一次失败的证据链演练。可观测性的价值在于:每一步都把问题空间缩小到“能做动作”的范围。

8. 最后把它变成系统:你需要的最小落地清单

  1. 关联键:统一 run_id / attempt / sha / runner_pool / cache_key,贯穿日志与指标。
  2. 失败画像:至少先落地 4 类(Code / Flaky / Dependency / Infra),并能自动打标签。
  3. 核心指标:Queue P95、Total P95、Failure rate、Retry rate、Flake rate、Cache hit rate。
  4. 告警规则:趋势型 + 拆维度 + 指向明确动作(扩容、回滚、隔离、冻结)。
  5. 治理例会:每周只做两件事:删掉噪声、消灭 Top 1 失败类别(或 Top 1 慢点)。
你现在应该能回答三个问题:
1) 这次失败属于哪类画像?
2) 我需要的证据链是什么?下一步看哪里?
3) 修复后我用哪个指标证明“真的变好了”?
← 上一章:Secrets 管理 下一章:CI 性能优化(缓存、并行、增量) →