3. 流水线关键阶段识别:从代码提交到部署上线的全流程阶段拆解

在构建可观测的 Jenkins 流水线之前,首要任务是精确识别流水线的关键阶段(Stage)。每个阶段代表一次原子化的交付活动,也是 SLI 埋点的最小逻辑单元。只有将流水线拆解为 Checkout → Build → Test → Package → Deploy 五个核心阶段,才能针对性地定义指标口径并确定埋点位置。

3.1 全流程阶段拆解与职责定义

下表列出了从代码提交到部署上线的标准阶段划分、职责说明以及典型的失败模式。这是后续 SLI 口径设计的业务基础。

阶段 职责 典型失败模式 埋点关注点
Checkout 从 Git 仓库拉取指定分支/提交的源代码 网络超时、认证失败、分支不存在 拉取耗时、重试次数
Build 编译源代码,生成中间产物(如 .class、.o 文件) 编译错误、依赖缺失、资源不足 编译耗时、CPU/内存峰值
Test 执行单元测试、集成测试、静态代码分析 测试失败、覆盖率不达标、超时 测试通过率、执行耗时
Package 将编译产物打包为可部署的镜像或归档(如 Docker 镜像、JAR) 镜像构建失败、制品库推送失败 打包耗时、制品大小
Deploy 将制品部署到目标环境(开发/测试/生产) 部署超时、健康检查失败、回滚失败 部署耗时、滚动更新速率
实践建议: 在 Jenkins Declarative Pipeline 中,每个 stage 块应严格对应上表中的一个阶段。不要在同一个 stage 块内混合多个职责,否则会导致 SLI 数据无法归因。

3.2 流水线阶段拓扑与 SLI 埋点位置

下图展示了从代码提交到部署上线的完整阶段拓扑,并标注了每个阶段对应的 SLI 埋点位置(红色标记)。埋点应放置在阶段开始(Start)和结束(End)处,用于采集耗时与状态。

流水线阶段拓扑与 SLI 埋点位置 Checkout Build Test Package Deploy S E S E S E S E S E SLI 埋点规则 S = Stage Start(阶段开始) E = Stage End(阶段结束)
埋点原则: 每个阶段至少埋入两个时间戳(Start / End),结合阶段状态(SUCCESS / FAILURE / ABORTED)即可计算出阶段耗时、成功率、失败率等核心 SLI。对于 Build 和 Test 阶段,建议额外埋入资源消耗指标(如 CPU 使用率、内存占用)。

3.3 关键阶段 SLI 口径定义

基于上述阶段拆解,我们为每个阶段定义具体的 SLI 口径。口径是 SLI 计算的精确公式,必须与埋点数据字段一一对应。

3.3.1 Checkout 阶段 SLI

  • 阶段耗时(秒): checkout_end_timestamp - checkout_start_timestamp
  • 成功率(%): checkout_stage_status == "SUCCESS" ? 1 : 0
  • 重试次数: 由 Jenkins 插件或流水线脚本显式记录 checkout_retry_count

3.3.2 Build 阶段 SLI

  • 编译耗时(秒): build_end_timestamp - build_start_timestamp
  • CPU 峰值(%): 从节点监控或 top 命令采集 build_cpu_max
  • 内存峰值(MB): build_memory_max

3.3.3 Test 阶段 SLI

  • 测试通过率(%): test_passed_count / test_total_count
  • 测试执行耗时(秒): test_end_timestamp - test_start_timestamp
  • 覆盖率(%): test_coverage_percent(由 JaCoCo / Cobertura 等工具输出)

3.3.4 Package 阶段 SLI

  • 打包耗时(秒): package_end_timestamp - package_start_timestamp
  • 制品大小(MB): package_artifact_size
  • 推送成功率(%): package_push_status == "SUCCESS" ? 1 : 0

3.3.5 Deploy 阶段 SLI

  • 部署耗时(秒): deploy_end_timestamp - deploy_start_timestamp
  • 健康检查通过率(%): deploy_health_check_passed / deploy_health_check_total
  • 回滚触发次数: deploy_rollback_count
注意: 上述口径中的字段名(如 checkout_start_timestamp)需要在 Jenkins Pipeline 脚本中通过 env 变量或自定义步骤显式写入。不要依赖 Jenkins 内置的 currentBuild.duration 作为阶段耗时,因为它包含队列等待时间,会污染 SLI 数据。

3.4 埋点位置代码示例(Jenkins Declarative Pipeline)

以下代码展示了如何在 Checkout 和 Build 阶段嵌入 SLI 埋点。其他阶段模式相同。

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                script {
                    // 埋点:阶段开始
                    env.CHECKOUT_START = System.currentTimeMillis()
                }
                checkout scm
                script {
                    // 埋点:阶段结束
                    env.CHECKOUT_END = System.currentTimeMillis()
                    // 计算耗时并记录到自定义 Metric
                    def duration = (env.CHECKOUT_END as long) - (env.CHECKOUT_START as long)
                    echo "CHECKOUT_DURATION_MS=${duration}"
                }
            }
        }
        stage('Build') {
            steps {
                script {
                    env.BUILD_START = System.currentTimeMillis()
                }
                sh 'mvn clean compile'
                script {
                    env.BUILD_END = System.currentTimeMillis()
                    def duration = (env.BUILD_END as long) - (env.BUILD_START as long)
                    echo "BUILD_DURATION_MS=${duration}"
                }
            }
        }
        // 后续阶段(Test、Package、Deploy)同理
    }
}

3.5 阶段依赖与并行策略对埋点的影响

在实际流水线中,阶段之间可能存在并行或条件依赖。这会影响 SLI 的归因方式:

  • 串行阶段: 每个阶段独立埋点,SLI 直接累加即可得到端到端耗时。
  • 并行阶段(如并行 Test): 需要为每个并行分支单独埋点,端到端耗时取所有分支的最大值。
  • 条件阶段(如仅 master 分支执行 Deploy): 埋点逻辑必须包裹在条件判断内,避免空阶段产生无效 SLI。

下图展示了串行与并行阶段的埋点差异:

串行与并行阶段埋点对比 Stage A Stage B Stage C 串行:SLI 累加 Stage X Parallel A Parallel B Stage Y 并行:SLI 取最大值(Parallel A/B 中耗时较长者)
最佳实践: 在并行分支中,使用 script { parallel } 块时,在每个分支内部独立埋点。端到端 SLI 计算时,应取所有并行分支的 end_timestamp 最大值与 start_timestamp 最小值。