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' 核对实际搜索路径和命中的程序。
需要并行时,为每个分支分配独立 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 步骤