3. Jenkins Pipeline 基础:声明式 Pipeline 与脚本式 Pipeline 的区别

Jenkins Pipeline 是实现持续交付流水线的核心能力,它将整个构建、测试、部署流程以代码的形式定义并存储在 Jenkinsfile 中。Pipeline 支持两种语法风格:声明式(Declarative)和脚本式(Scripted)。理解两者的区别与适用场景,是编写可靠 CI/CD 管道的第一步。

3.1 两种 Pipeline 的核心差异

声明式 Pipeline 是 Jenkins 官方推荐的语法,它提供了一种更结构化、更简洁的方式来定义流水线。脚本式 Pipeline 则更为灵活,基于 Groovy 脚本引擎,允许编写复杂的逻辑和流程控制。

对比维度 声明式 Pipeline 脚本式 Pipeline
语法风格 结构化、声明式、预定义块 编程式、基于 Groovy 脚本
学习曲线 较低,适合初学者和标准流程 较高,需要 Groovy 基础
流程控制 通过 when、parallel、post 等内置指令 使用 if/else、try/catch、for 等原生 Groovy 语法
错误处理 通过 post 块统一处理 使用 try/catch/finally 手动处理
代码复用 通过 Shared Libraries 和 functions 通过 Shared Libraries 和 Groovy 方法
可视化 Blue Ocean 原生支持,阶段视图清晰 支持有限,阶段划分需手动定义
推荐场景 标准 CI/CD 流程、团队协作、新项目 复杂逻辑、动态流程、遗留系统迁移
最佳实践:在事件触发 Runbook 联动场景中,优先使用声明式 Pipeline。其结构化的 when 条件与 post 处理块,能够与外部事件(如 Webhook、Git 推送)进行清晰的状态映射,降低维护成本。

3.2 声明式 Pipeline 基本结构

声明式 Pipeline 强制使用 pipeline 块作为根节点,内部包含 agent、stages、post 等核心部分。其结构清晰,可读性强。

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                echo '拉取源代码...'
                checkout scm
            }
        }
        stage('Build') {
            steps {
                echo '执行构建...'
                sh 'mvn clean compile'
            }
        }
        stage('Test') {
            steps {
                echo '运行单元测试...'
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                echo '部署到测试环境...'
                sh 'deploy.sh'
            }
        }
    }

    post {
        always {
            echo '清理工作空间...'
            cleanWs()
        }
        success {
            echo '流水线执行成功!'
        }
        failure {
            echo '流水线执行失败,请检查日志。'
        }
    }
}
结构要点:
  • pipeline:所有声明式 Pipeline 的根块,必须位于 Jenkinsfile 最外层。
  • agent:指定流水线或某个阶段在哪个节点/容器中执行。
  • stages:包含一个或多个 stage,每个 stage 代表一个逻辑阶段。
  • steps:每个 stage 内部必须包含 steps,用于定义具体的执行步骤。
  • post:可选块,用于定义流水线结束后的处理逻辑,如清理、通知等。

3.3 脚本式 Pipeline 基本结构

脚本式 Pipeline 使用 node 块作为入口,内部完全基于 Groovy 脚本编写。它没有强制性的阶段结构,但通常使用 stage 函数来组织逻辑。

node {
    try {
        stage('Checkout') {
            echo '拉取源代码...'
            checkout scm
        }
        stage('Build') {
            echo '执行构建...'
            sh 'mvn clean compile'
        }
        stage('Test') {
            echo '运行单元测试...'
            sh 'mvn test'
        }
        stage('Deploy') {
            echo '部署到测试环境...'
            sh 'deploy.sh'
        }
    } catch (Exception e) {
        echo "流水线执行失败: ${e.message}"
        currentBuild.result = 'FAILURE'
    } finally {
        echo '清理工作空间...'
        cleanWs()
    }
}
注意:脚本式 Pipeline 中,stage 只是一个函数调用,并非强制结构。如果忘记定义 stage,流水线仍然可以执行,但 Blue Ocean 等可视化工具将无法正确展示阶段视图。在事件触发场景中,这会导致排查问题时的可视化盲区。

3.4 Pipeline 核心块与指令详解

无论是声明式还是脚本式,都共享一些核心概念。下表列出了声明式 Pipeline 中最重要的块和指令。

块/指令 作用 示例
pipeline 声明式 Pipeline 的根块 pipeline { ... }
agent 定义执行环境(节点、Docker、标签) agent { label 'linux' }
stages 包含所有阶段定义的容器块 stages { stage('Build') { ... } }
stage 定义一个逻辑阶段,必须有 steps stage('Test') { steps { ... } }
steps 阶段内具体的执行步骤 steps { sh 'make' }
post 流水线结束后的条件处理块 post { failure { ... } }
when 条件判断,控制阶段是否执行 when { branch 'main' }
parallel 并行执行多个阶段或步骤 parallel { stage('A') ... stage('B') ... }
environment 定义环境变量 environment { MY_VAR = 'value' }
options 设置流水线全局选项 options { timeout(time: 1, unit: 'HOURS') }

3.5 声明式 Pipeline 结构图

下图展示了声明式 Pipeline 的完整层级结构,帮助理解各块之间的包含关系。

声明式 Pipeline 结构层级图 pipeline { } agent { label 'linux' } stages { } stage('Checkout') { } steps { checkout scm } stage('Build') { } steps { sh 'mvn compile' } stage('Test') { } steps { sh 'mvn test' } post { } always { cleanWs() } | success { notify() } | failure { alert() }

3.6 声明式 vs 脚本式:事件触发场景下的选择

在事件触发 Runbook 联动 CI/CD 管道配置中,两种 Pipeline 各有优劣。以下对比表聚焦于事件驱动场景的关键能力。

事件触发场景能力 声明式 Pipeline 脚本式 Pipeline
Webhook 触发条件过滤 通过 when { trigger "webhook" } 简洁实现 需手动解析 payload 并编写条件逻辑
多分支并行处理 原生支持 parallel 块,结构清晰 需使用 Groovy 的并行 API,代码较复杂
失败重试与回滚 通过 retry 选项和 post 块统一管理 需手动编写 try/catch 和重试逻辑
与外部系统联动 通过 environment 和 withCredentials 安全传递参数 更灵活,可直接调用 Groovy 的 HTTP 库
可视化调试 Blue Ocean 提供完整的阶段视图和日志 阶段划分不强制,可视化效果较差
选型建议:对于大多数事件触发 Runbook 联动场景(如 Git 推送触发构建、Webhook 触发部署),声明式 Pipeline 是更优选择。它降低了编写门槛,同时提供了足够的灵活性来应对标准流程。仅在需要动态生成阶段、复杂循环或深度自定义错误处理时,才考虑脚本式 Pipeline。

3.7 两种 Pipeline 的代码结构对比图

下图从代码结构层面直观对比声明式与脚本式 Pipeline 的组织方式。

声明式 vs 脚本式 Pipeline 代码结构对比 声明式 Pipeline pipeline { agent any stages { stage('Build') { steps { ... } } post { success { ... } } } 脚本式 Pipeline node { try { stage('Build') { ... } } catch (Exception e) { currentBuild.result = 'FAILURE' } finally { cleanWs() } }

3.8 从脚本式迁移到声明式的关键步骤

对于已有脚本式 Pipeline 的团队,迁移到声明式可以提升可读性和可维护性。以下是迁移的核心步骤:

  1. 提取 node 块为 pipeline 块:将最外层的 node { } 替换为 pipeline { },并添加 agent 指令。
  2. 将 stage 函数调用转换为 stage 块:确保每个 stage 都有明确的 steps 子块。
  3. 替换 try/catch/finally 为 post 块:声明式使用 post 统一处理成功、失败、始终执行等场景。
  4. 将 if/else 条件转换为 when 指令:声明式提供更简洁的条件判断语法。
  5. 使用 environment 块管理环境变量:替代脚本式中的 withEnv 或直接赋值。
迁移示例:脚本式中的 if (env.BRANCH_NAME == 'main') { ... } 可转换为声明式中的 when { branch 'main' },代码量减少约 40%,且意图更清晰。