Jenkinsfile:从测试到可靠流水线

验证:未记录
适用版本
未记录
验证范围
尚未登记验证记录。

Jenkinsfile 是放入 Git 的流水线定义。优先使用 Declarative Pipeline(声明式流水线),复杂业务逻辑写到仓库脚本,Groovy 负责阶段编排。前置:已连接标记为 linux-test 的 Agent(执行节点),安装 Git、Pipeline、JUnit、Timestamper 插件。

1. 建立第一条真实测试流水线#

使用自己的 Go 或 Node.js 项目,不把 echo success 当测试。Node.js 项目的 lint、test 必须是实际命令,测试进程必须非交互、失败返回非零。保存 Jenkinsfile:

pipeline {
  agent { label 'linux-test' }
  options {
    skipDefaultCheckout(true)
    disableConcurrentBuilds()
    timestamps()
    timeout(time: 20, unit: 'MINUTES')
    buildDiscarder(logRotator(numToKeepStr: '20'))
  }
  parameters {
    choice(name: 'LANGUAGE', choices: ['node', 'go'], description: 'Project toolchain')
  }
  stages {
    stage('Checkout') {
      steps { deleteDir(); checkout scm }
    }
    stage('Verify') {
      steps {
        script {
          if (params.LANGUAGE == 'go') {
            sh 'go test -race ./... && go vet ./...'
          } else {
            sh 'npm ci && npm run lint && npm test'
          }
        }
      }
    }
  }
  post {
    always { deleteDir() }
    failure { echo 'Tests failed; inspect the failing stage before retrying.' }
  }
}

新建 Pipeline → Definition 选择 Pipeline script from SCM(Source Code Management,源代码管理)→ 固定仓库、凭据、分支和 Jenkinsfile 路径 → Build with Parameters。Go 的 race 检测需要兼容平台与 C 编译器;构建镜像必须包含对应工具链。

验收:提交一个失败测试,任务必须失败且后续阶段不运行;恢复测试再提交应成功。切勿配置 npm test || true。

2. 配置、报告与并行#

  • environment 可在流水线或阶段中设置环境变量;withEnv 可在步骤块内临时设置环境变量。向 PATH 加入工具目录时可使用下方的 PATH+NAME 语法。
  • tools { jdk 'jdk17' } 选择应用构建 JDK,不改变运行 Jenkins/Agent 所需的 Java 版本;两者分别管理。
  • junit 'reports/*.xml' 读取已生成的 JUnit XML 测试报告;没有报告先配置测试框架的 reporter,不能把日志重命名为 XML。
  • stash/unstash 用于同一构建的小文件跨 Agent 传递;大型镜像/依赖放制品仓库,避免把 Controller 当文件中转站。

withEnv 中的 PATH+NAME#

withEnv(['PATH+NODE=/opt/build_tools/nodejs/20/bin']) 会在块内把指定目录放到现有 PATH 前面,保留原有搜索路径。+NODE 中的 NODE 只是区分多条 PATH 设置的标签,不会创建名为 NODE 的环境变量,也不要求在 Jenkins 工具管理中注册 NODE。这种写法也可用于 PATH+JAVA、PATH+MAVEN 等;这些目录必须实际存在于执行该步骤的 Agent 上。

pipeline {
  agent any
  stages {
    stage('Build') {
      steps {
        withEnv([
          'PATH+NODE=/opt/build_tools/nodejs/20/bin',
          'PATH+JAVA=/opt/jdk/bin'
        ]) {
          sh 'node --version'
          sh 'java -version'
        }
      }
    }
  }
}

withEnv 的设置只对花括号内的步骤及其启动的子进程生效,离开该块后恢复原来的环境。sh 根据块内的 PATH 查找 node 和 java;如果 Agent 原有 PATH 中也有同名程序,前置的新目录通常会优先被找到。直接写 PATH=/opt/jdk/bin 则会覆盖原有 PATH,可能使其他系统命令无法找到。

withEnv 引用现成的绝对路径,无须在 Manage Jenkins → Tools 注册;只有使用 tools { jdk 'jdk17' } 或 tool 'jdk17' 按名称选取 Jenkins 管理的安装项时,名称才必须事先注册。可在块内执行 sh 'echo "$PATH"; command -v node; command -v java' 核对实际搜索路径和命中的程序。

参考:Jenkins withEnv 步骤文档。

需要并行时,为每个分支分配独立 workspace(工作目录),避免同时删除目录或覆盖同一个报告;失败时可启用 parallelsAlwaysFailFast()。不要并行修改同一部署环境。

3. 失败和重试语义#

场景 处理
测试失败/扫描发现问题 阻断;不要自动重试到偶然成功
网络临时失败/Agent 丢失 只对幂等步骤限定次数重试
部署、数据库迁移 先检查实际状态,不能整段无条件 retry
等待生产审批 使用 agent none,不占用执行节点;单独设置超时
多个 Job 发布同一环境 使用共享环境锁;disableConcurrentBuilds 只约束当前 Job

使用 Jenkins 自带 Snippet Generator(步骤生成器)确认插件参数;提交前通过 流水线校验器 校验。更多语法见 官方参考。

4. 运行命令并读取结果:sh#

sh '命令' 在 Linux Agent 上运行 shell;默认退出码非零会使步骤失败。需要读取标准输出或自行处理退出码时,在 Declarative Pipeline 的 script 块中使用:

steps {
  script {
    def commit = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
    echo "Building commit ${commit}"

    def status = sh(script: 'test -f target/app.jar', returnStatus: true)
    if (status != 0) {
      error 'Expected target/app.jar after packaging'
    }
  }
}

returnStdout: true 返回标准输出字符串,常用 .trim() 去掉末尾换行;标准错误仍进构建日志。returnStatus: true 返回数字退出码,此时必须明确决定失败如何处理,否则可能误把失败当成功。每次 sh 都是独立 shell:在一个 sh 中 cd 或 export,不会改变下一个 sh 的工作目录或环境。需要让一段脚本共享 shell 状态时,放进同一个多行 sh;需要跨步骤设置环境变量则用 withEnv 或 environment。不要把凭据通过 returnStdout 读入 Groovy 字符串。sh 参数

5. 在子目录执行:dir#

单仓库多服务项目可用 dir('service-a') 改变块内步骤的工作目录;相对路径也以此为基准:

steps {
  checkout scm
  dir('service-a') {
    sh './mvnw -B verify'
    archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
  }
}

这里的 target/*.jar 指 service-a/target/*.jar,离开 dir 块后恢复原工作目录。dir 不会创建独立 workspace;并行分支若修改相同文件,仍需隔离工作目录。相较于在一次 sh 中执行 cd service-a,dir 还会让后续的 archiveArtifacts 等 Jenkins 步骤使用同一目录。dir 步骤

6. 按条件运行阶段:when#

在多分支 Pipeline 中,仅主分支执行发布阶段;beforeAgent true 可在分配该阶段 Agent 前判断条件:

stage('Publish') {
  agent { label 'release-agent' }
  when {
    beforeAgent true
    branch 'main'
  }
  steps {
    sh './mvnw -B deploy'
  }
}

branch 'main' 依赖多分支 Pipeline 提供分支上下文;普通单分支 Job 不应照搬。when 只决定是否进入阶段,不负责保护发布凭据或目标仓库权限;相关隔离见 多分支与凭据。when 语法

7. 失败后仍收集报告与产物:post#

post 可以放在阶段或流水线级别;always 无论成功失败都会执行,适合收集测试报告,success 只在成功时运行,cleanup 常用于最后清理。

stage('Test') {
  steps {
    sh './mvnw -B test'
  }
  post {
    always {
      junit testResults: 'target/surefire-reports/*.xml', allowEmptyResults: true
      archiveArtifacts artifacts: 'target/surefire-reports/**', allowEmptyArchive: true
    }
  }
}

这样即使测试命令失败,已有报告仍会进入 Jenkins 测试结果页。allowEmptyResults: true 只为“构建在生成报告前失败”的情况容错;它也可能掩盖测试根本没运行,正式质量门禁应另行确保测试报告存在。archiveArtifacts 让文件可从该构建下载,不等同于发布到 Nexus 等制品仓库。原有示例的流水线级 post { always { deleteDir() } } 会在结束时清理 workspace,报告归档应在清理前完成。post 语法、归档说明

8. 限时与重试:timeout、retry#

timeout 限制步骤最长等待时间;retry(2) 最多执行两次(含首次)。只对临时网络故障等可安全重复的操作重试:

steps {
  timeout(time: 2, unit: 'MINUTES') {
    retry(2) {
      sh 'curl --fail --silent --show-error https://staging.example.com/healthz'
    }
  }
}

把 timeout 放在外层可约束所有尝试的总耗时。retry 默认会重试块内的失败;不要直接包住发布、支付或数据库迁移等可能已部分成功的操作。需要应对 Agent 丢失时,应按执行环境选择受支持的重试条件,而不是重跑整个业务流程。timeout 与 retry 步骤

9. 同一次构建跨 Agent 传小文件:stash、unstash#

stash 暂存当前 workspace 的文件;之后在同一次 Pipeline 运行的其他 Agent 上用同名 unstash 还原到当前 workspace:

stage('Build') {
  agent { label 'build-agent' }
  steps {
    sh './mvnw -B package'
    stash name: 'app-jar', includes: 'target/*.jar'
  }
}
stage('Inspect') {
  agent { label 'inspect-agent' }
  steps {
    unstash 'app-jar'
    sh 'ls -l target/*.jar'
  }
}

默认找不到匹配文件时 stash 会失败,有助于发现构建缺少产物。stash 默认只在本次运行中可用,不是跨 Job 的制品仓库;大文件或长期保留的制品应上传制品仓库。stash/unstash 步骤

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭