4. 构建阶段 SLI:代码编译 / 镜像构建时长 P50 / P95 / P99 指标定义与采集方法
构建阶段是 CI/CD 流水线的核心瓶颈环节,代码编译与镜像构建的耗时直接决定了开发者的等待时长和部署频率。本节定义构建阶段的关键 SLI 口径,并给出标准化的采集与计算方案。
4.1 指标定义与业务含义
| 指标名称 | 口径定义 | 业务含义 |
|---|---|---|
| 编译时长 P50 | 从代码检出完成到编译产物生成的时间,取 50 分位值 | 代表半数构建的典型耗时,反映日常开发体验 |
| 编译时长 P95 | 同上口径,取 95 分位值 | 代表长尾构建的耗时上限,暴露缓存失效或依赖冲突 |
| 编译时长 P99 | 同上口径,取 99 分位值 | 极端异常构建的耗时,通常伴随资源争抢或配置错误 |
| 镜像构建时长 P50 | 从 Dockerfile 读取到镜像推送完成的时间,取 50 分位值 | 反映镜像层缓存命中率与网络传输效率 |
| 镜像构建时长 P95 | 同上口径,取 95 分位值 | 大镜像或跨机房推送的典型延迟上限 |
| 镜像构建时长 P99 | 同上口径,取 99 分位值 | 极端场景下的镜像构建失败或超时阈值 |
口径对齐要点:编译时长与镜像构建时长必须分开统计,不可合并为“构建总时长”。合并口径会掩盖编译缓存失效或镜像层复用率低等独立问题,导致调优方向误判。
4.2 采集方法:基于 CI 事件埋点
推荐使用 CI 管道事件钩子 + 时间戳打点 的方式采集原始数据,避免侵入式 agent 对构建环境造成干扰。
4.2.1 埋点位置与事件标签
| 阶段 | 开始事件 | 结束事件 | 标签示例 |
|---|---|---|---|
| 代码编译 | compile_start |
compile_end |
job_id, repo, branch, compiler_version |
| 镜像构建 | image_build_start |
image_build_end |
job_id, dockerfile_path, base_image |
| 镜像推送 | image_push_start |
image_push_end |
job_id, registry, image_size |
4.2.2 采集脚本示例(GitLab CI + Prometheus Pushgateway)
#!/bin/bash
# 编译开始打点
curl -X POST http://pushgateway:9091/metrics/job/build_sli \
-d "# TYPE compile_duration_seconds gauge
compile_duration_seconds{job_id=\"${CI_JOB_ID}\",repo=\"${CI_PROJECT_NAME}\",branch=\"${CI_COMMIT_BRANCH}\",phase=\"compile_start\"} $(date +%s)"
# 执行编译
mvn clean package -DskipTests
# 编译结束打点
curl -X POST http://pushgateway:9091/metrics/job/build_sli \
-d "compile_duration_seconds{job_id=\"${CI_JOB_ID}\",repo=\"${CI_PROJECT_NAME}\",branch=\"${CI_COMMIT_BRANCH}\",phase=\"compile_end\"} $(date +%s)"
注意:Pushgateway 方式适合短生命周期 job,但需定期清理过期指标,避免累积导致查询膨胀。生产环境建议改用 OpenTelemetry CI 事件 exporter 直接写入时序数据库。
4.3 分位值计算:从原始事件到 P50 / P95 / P99
原始数据为每个 job 的 compile_duration_seconds 和 image_build_duration_seconds 两个 gauge 值。分位值计算推荐使用 T-Digest 或 HDR Histogram 算法,避免全量排序带来的内存开销。
4.3.1 PromQL 查询示例(基于 Histogram 指标类型)
# 编译时长 P50
histogram_quantile(0.50, sum(rate(compile_duration_seconds_bucket[5m])) by (le))
# 编译时长 P95
histogram_quantile(0.95, sum(rate(compile_duration_seconds_bucket[5m])) by (le))
# 编译时长 P99
histogram_quantile(0.99, sum(rate(compile_duration_seconds_bucket[5m])) by (le))
4.3.2 分桶设计建议
| 指标 | 推荐桶范围 | 桶数量 |
|---|---|---|
| 编译时长 | 10s, 30s, 60s, 120s, 300s, 600s, 1800s, 3600s | 8 个桶 |
| 镜像构建时长 | 5s, 15s, 30s, 60s, 120s, 300s, 600s, 1800s | 8 个桶 |
调优经验:桶边界应覆盖 99% 的构建场景,同时避免桶过细导致 histogram_quantile 误差增大。建议先运行一周采集原始数据,再根据实际分布调整桶边界。
4.4 构建阶段 SLI 采集架构
下图展示了从 CI 事件触发到 SLI 可视化的完整数据流,包含埋点、传输、聚合与告警四个层级。
4.5 常见问题与避坑指南
- 时间戳精度不足:CI 环境变量
$(date +%s)仅精确到秒,对于秒级构建误差较大。建议使用$(date +%s%N)纳秒级时间戳,或直接读取 CI 内置的CI_JOB_STARTED_AT等精确时间字段。 - 缓存命中误判:编译时长短不一定是好事,需结合
cache_hit标签区分全量编译与增量编译。建议在埋点时额外记录cache_status: full/incremental/no_cache。 - 镜像构建与推送分离:部分 CI 将镜像构建与推送合并为一个步骤,导致无法单独分析推送延迟。务必在 pipeline 中拆分为两个独立 stage,分别埋点。
- 分位值波动噪音:单次构建异常会导致 P99 剧烈抖动。建议对 P99 使用 移动窗口平均(如 7 天滑动窗口)后再触发告警,避免误报。
实战案例:某团队发现编译时长 P95 从 120s 突增到 300s,排查发现是 Maven 依赖镜像源切换导致下载延迟。通过 SLI 分位值趋势图,在 30 分钟内定位到根因,回滚镜像源配置后 P95 恢复至 130s。
4.6 构建阶段 SLI 口径速查表
| SLI 名称 | 口径公式 | 采集频率 | 告警阈值建议 |
|---|---|---|---|
| 编译时长 P50 | compile_end - compile_start |
每次构建 | > 基线 × 1.5 持续 1h |
| 编译时长 P95 | compile_end - compile_start |
每次构建 | > 基线 × 2.0 持续 30min |
| 编译时长 P99 | compile_end - compile_start |
每次构建 | > 基线 × 3.0 持续 15min |
| 镜像构建时长 P50 | image_build_end - image_build_start |
每次构建 | > 基线 × 1.5 持续 1h |
| 镜像构建时长 P95 | image_build_end - image_build_start |
每次构建 | > 基线 × 2.0 持续 30min |
| 镜像构建时长 P99 | image_build_end - image_build_start |
每次构建 | > 基线 × 3.0 持续 15min |
基线建议取过去 7 天同一时段(如工作日 10:00-12:00)的 P50 值,避免因业务高峰期或低峰期差异导致误告警。