Jenkins Pipeline 语法:初学者的全面指南

通过这本面向初学者的全面指南,揭开 Jenkins pipeline 语法的神秘面纱。学习 Declarative 和 Scripted pipeline 的基础知识,包括 agents、stages、steps、post-actions、environments、parameters 和最佳实践。通过实际示例和可操作的建议,让您能够有效地构建健壮的 CI/CD 工作流。

Jenkins Pipeline 语法:初学者全面指南

Jenkins Pipeline 语法允许你在与代码共存的 Jenkinsfile 中描述构建、测试和部署流程。这很重要,因为你的管道变更可以像应用程序代码一样被审查、版本控制和回滚。

如果你是 Jenkins 管道新手,建议从声明式管道语法开始。它结构清晰、可读性强,无需太多 Groovy 知识即可覆盖大多数 CI/CD 工作流。

声明式管道 vs. 脚本化管道

Jenkins 支持两种管道风格。

声明式管道使用严格的 pipeline { ... } 结构。对于大多数团队来说,这是最佳默认选择,因为 Jenkins 可以验证文件结构并在 UI 中清晰显示阶段。

脚本化管道在 node { ... } 块内使用 Groovy。它提供了更多控制,但如果你的团队不熟悉 Groovy,则更容易创建难以维护的管道。

优先使用声明式管道。仅在需要声明式无法清晰表达的逻辑时,才考虑脚本化管道。

一个最小的声明式管道

一个基本的 Jenkinsfile 包含代理、阶段和步骤:

pipeline {
    agent any

    stages {
        stage('构建') {
            steps {
                echo '正在构建应用程序'
                sh 'mvn clean package'
            }
        }

        stage('测试') {
            steps {
                sh 'mvn test'
            }
        }
    }
}

agent any 告诉 Jenkins 在任何可用代理上运行管道。每个 stage 在 Jenkins UI 中单独显示。steps 块包含 Jenkins 运行的命令。

在 Windows 代理上,使用 bat 代替 sh

bat 'gradlew.bat test'

agent 指令

agent 指令决定管道或阶段的运行位置。

在任何可用代理上运行:

agent any

在具有特定标签的代理上运行:

agent { label 'linux && docker' }

跳过全局代理,为每个阶段选择代理:

pipeline {
    agent none

    stages {
        stage('构建') {
            agent { label 'linux' }
            steps {
                sh './build.sh'
            }
        }
    }
}

当不同阶段需要不同环境时(例如 Linux 用于构建,Windows 用于安装程序测试),agent none 非常有用。

阶段和步骤

使用阶段将管道拆分为开发者一目了然的工作:检出、构建、测试、打包、部署。

stages {
    stage('检出') {
        steps {
            checkout scm
        }
    }

    stage('单元测试') {
        steps {
            sh 'npm test'
        }
    }
}

保持阶段有意义。一个名为“运行”的阶段包含五十个命令很难调试。包含二十个小阶段的管道则显得杂乱。目标是让阶段匹配实际工作流中的检查点。

环境变量

使用 environment 存储多个阶段需要的值:

pipeline {
    agent any

    environment {
        APP_NAME = 'orders-api'
        IMAGE = "registry.example.com/team/orders-api:${env.BUILD_NUMBER}"
    }

    stages {
        stage('构建镜像') {
            steps {
                sh 'docker build -t "$IMAGE" .'
            }
        }
    }
}

在 Groovy 字符串中,可以通过 env.APP_NAME 读取变量。在 shell 步骤中,Jenkins 会导出环境变量,因此 $APP_NAME$IMAGE 在 shell 中有效。

不要将机密信息放在 environment 中。请使用 Jenkins 凭据和 withCredentials

参数

参数允许用户在手动启动构建时选择值:

pipeline {
    agent any

    parameters {
        string(name: 'BRANCH_NAME', defaultValue: 'main', description: '要构建的 Git 分支')
        booleanParam(name: 'RUN_TESTS', defaultValue: true, description: '运行单元测试')
        choice(name: 'DEPLOY_ENV', choices: ['dev', 'staging', 'prod'], description: '目标环境')
    }

    stages {
        stage('显示输入') {
            steps {
                echo "分支: ${params.BRANCH_NAME}"
                echo "部署目标: ${params.DEPLOY_ENV}"
            }
        }
    }
}

使用参数处理不同运行间可能变化的内容。不要用它们绕过生产变更的审查。

使用 when 的条件阶段

when 指令控制阶段是否运行:

stage('部署到生产环境') {
    when {
        branch 'main'
    }
    steps {
        sh './deploy-prod.sh'
    }
}

你也可以使用表达式:

stage('测试') {
    when {
        expression { return params.RUN_TESTS }
    }
    steps {
        sh 'npm test'
    }
}

when 放在阶段上,而不是 steps 内部。

后置操作

post 块在阶段或管道完成后运行。它们对于发布测试报告、归档构建证据和清理工作很有用。

pipeline {
    agent any

    stages {
        stage('测试') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }
    }

    post {
        failure {
            echo '管道失败'
        }
        always {
            cleanWs()
        }
    }
}

junit 即使测试失败也会发布测试结果。cleanWs() 来自工作空间清理插件,因此在使用前请安装该插件。

选项

使用 options 设置管道行为:

pipeline {
    agent any

    options {
        timestamps()
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '20'))
    }

    stages {
        stage('构建') {
            steps {
                sh './build.sh'
            }
        }
    }
}

这些选项使日志更易读、停止挂起的构建、防止同一作业的重叠运行,并限制旧构建历史。

一个实用的初学者管道

此示例检出代码、构建 Java 项目、发布测试结果并归档打包的 JAR:

pipeline {
    agent { label 'linux' }

    options {
        timestamps()
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
    }

    stages {
        stage('检出') {
            steps {
                checkout scm
            }
        }

        stage('构建') {
            steps {
                sh 'mvn clean package'
            }
        }

        stage('发布') {
            steps {
                junit 'target/surefire-reports/*.xml'
                archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
            }
        }
    }
}

这是一个比大型部署管道更好的起点。一旦构建可靠,可以添加镜像发布、安全扫描或部署阶段。

脚本化管道基础

一个简单的脚本化管道如下所示:

node('linux') {
    stage('检出') {
        checkout scm
    }

    stage('构建') {
        sh 'mvn clean package'
    }
}

不要将声明式 pipeline {} 块包裹在脚本化管道中。保持两种风格分离,除非你有特定且经过测试的理由在声明式管道的 script { ... } 块中混合少量脚本化部分。

良好的管道习惯

Jenkinsfile 存储在源代码控制中。将机密信息保存在 Jenkins 凭据中。为阶段命名,使其与所做的工作匹配。在 post 块中发布报告,以便失败时仍能留下有用的证据。在构建挂起过夜之前添加超时。

大多数初学者管道问题源于隐藏的假设:某个工具存在于一个代理上但不在另一个上、机密信息被硬编码、或者报告仅在成功时发布。在管道中明确这些假设,Jenkins 将变得更容易信任。