面向初级运维:已经能登录 Jenkins,准备写第一条 Pipeline。本文使用单独的练习任务,在 Linux agent 上生成文本和压缩包,不连接生产发布环境。文中的运行结果与故障现象均为教学预期。

1. 第一条流水线应该验证什么
一条能打印 Hello World 的流水线,可以证明任务被启动了。但真正接手构建任务时,还需要知道:命令在哪里运行、错误能否传给 Jenkins、产物保存在哪、失败后能留下什么信息。
本篇把这四件事放在一个小练习里。正常运行时生成报告并打包;勾选一个参数后,让检查阶段故意失败,观察后续阶段和产物的变化。
2. 先确认命令运行在 agent 上
Jenkins controller 负责协调任务,agent 提供执行环境。sh 命令在被分配的 agent 及其工作区里执行,不一定是你打开浏览器时想到的那台机器。
本例要求:已经安装 Pipeline 及 Declarative Pipeline 相关插件,有一台在线 Linux agent,标签包含 linux,并安装了普通 Shell、grep 和 tar。用独立的练习任务和工作区执行。若实际标签不同,应修改下面的 label。
在 Jenkins 新建一个 Pipeline 类型任务,定义选择 Pipeline script。首次练习直接填写脚本,后续可以迁移到代码仓库的 Jenkinsfile。节点和步骤的对应关系见 Pipeline Nodes and Processes 文档。
3. 一份可以逐段阅读的 Jenkinsfile
pipeline {
agent { label 'linux' }
options {
skipDefaultCheckout()
disableConcurrentBuilds()
timeout(time: 5, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
}
parameters {
booleanParam(name: 'FAIL_DEMO', defaultValue: false,
description: 'Enable an intentional failure in the Check stage')
}
stages {
stage('Prepare') {
steps {
sh '''
set -eu
REPORT_DIR="reports/$BUILD_NUMBER"
mkdir -p "$REPORT_DIR"
echo "build=$BUILD_NUMBER" > "$REPORT_DIR/report.txt"
echo "agent=$NODE_NAME" >> "$REPORT_DIR/report.txt"
echo "fail_demo=$FAIL_DEMO" >> "$REPORT_DIR/report.txt"
pwd
'''
}
}
stage('Check') {
steps {
sh '''
set -eu
if [ "$FAIL_DEMO" = "true" ]; then
echo "Intentional failure for learning" >&2
exit 1
fi
grep -q '^build=' "reports/$BUILD_NUMBER/report.txt"
echo "Report check passed"
'''
}
}
stage('Package') {
steps {
sh '''
set -eu
REPORT_DIR="reports/$BUILD_NUMBER"
tar -czf "$REPORT_DIR/report.tar.gz" \
-C "$REPORT_DIR" report.txt
'''
}
}
}
post {
always {
archiveArtifacts artifacts: "reports/${env.BUILD_NUMBER}/*",
allowEmptyArchive: true,
fingerprint: true
}
success {
echo 'Demo pipeline completed successfully'
}
failure {
echo 'Demo failed; inspect the first failed step and console log'
}
}
}
这里用声明式 Pipeline 组织 agent、options、parameters、stages 和 post。完整语法可查 Jenkins Pipeline Syntax。练习中的 Shell 代码使用普通 POSIX 写法,没有依赖 Bash 数组或 [[ ... ]]。
4. 第一次运行:看阶段,也看实际产物
保存后执行一次构建,保持 FAIL_DEMO=false。某些 Jenkins 界面要先运行一次,才显示参数化构建入口;之后可以使用 Build with Parameters。
正常预期如下:
| 位置 | 应看到的结果 |
|---|---|
| Prepare | 创建本次编号的目录,日志显示工作区路径 |
| Check | 输出 Report check passed |
| Package | 生成 report.tar.gz |
| 构建结果 | SUCCESS |
| 构建产物 | 本次编号下的 report.txt 和 report.tar.gz |
下载文本文件,确认 build 与当前构建编号一致。再查看压缩包里的文件,应该只有这次生成的 report.txt。
按构建编号使用子目录,可以避免把上一次工作区里的同名产物误认成本次结果。归档模式也只匹配当前编号。archiveArtifacts 的路径相对于工作区,不能归档任意系统路径;详见 Jenkins Core 归档说明。
5. 第二次运行:故意失败一次,观察错误是否可见
再次构建,勾选 FAIL_DEMO。这次预期:
- Prepare 完成,留下当前构建的报告。
- Check 输出教学错误,并以
1退出。 - Package 因前面失败而跳过。
post的 always 尝试归档已经生成的报告,failure 输出提示。- 构建结果为
FAILURE,本次没有成功打包的压缩包。
普通 sh 步骤在脚本返回非零状态时会失败。如果使用 returnStatus: true,就需要自己检查返回值并决定是否终止,否则有可能把失败吞掉。这个区别见 sh 步骤的官方参数说明。
有一份报告能下载,只能证明该文件被生成并归档。它是在检查前创建的,不能作为本次构建成功的证明。
6. 为什么失败后还要归档,为什么允许归档为空
失败后,诊断日志和中间报告往往比最终压缩包更有价值。因此把归档放进 post { always { ... } },可以在步骤正常失败时保留已有证据。
若 Prepare 在创建文件之前就失败,归档可能找不到内容。allowEmptyArchive: true 允许这种情况,不会仅因为没有匹配文件再产生一个归档错误;它并不会把之前的构建失败改成成功。
如果 agent 断连、工作区不可访问或 Jenkins 出现基础设施故障,归档仍可能无法完成,always 也不是产物必定送达的保证。要同时查看失败步骤和 post 阶段的日志。
这里的 fingerprint 用于产物来源和使用关系追踪,不是代码签名或安全校验凭证。归档文件本身也不是长期备份;本例只保留最近 10 次构建记录及关联产物。工作区中的旧目录不由这个保留选项自动清理,需要按团队策略另行管理。
7. 遇到报错,先定位在分配节点、解析还是执行阶段
| 现象 | 优先检查 |
|---|---|
| 一直等待可用 executor | agent 是否在线、label 是否匹配、执行槽是否空闲 |
| Jenkinsfile 解析失败 | 括号、声明式结构、插件是否支持所用指令 |
sh 无法执行 |
agent 是否为 Linux、Shell 是否可用 |
tar: not found |
工具是否安装在实际 agent 的 PATH 中 |
Permission denied |
agent 执行用户、工作区和文件权限 |
| 有日志却没有归档 | 路径模式、本次构建目录、post 是否成功 |
Pipeline 的 Snippet Generator 和声明式校验器,适合核对当前 Jenkins 实际支持的语法;入口与用法见 Pipeline Development Tools。本地 Shell 语法检查不能代替 Jenkins 自己的解析和调度验证。
8. 跑通后怎样接入真实项目
下一步可以把 Jenkinsfile 放进仓库,将 Prepare 换成代码检出,将 Check 换成真正的单元测试,将 Package 换成项目构建。先在测试项目上验证成功和失败两条路径,再考虑部署步骤。
仓库密码和发布凭据通过 Jenkins 凭据功能提供,并限制使用范围;不要直接写进 Jenkinsfile 或输出到控制台。这样第一条流水线留下的就不只是绿色状态,还有可核对的执行位置、失败信号和本次产物。