Jenkins 流水线跨阶段的数据共享:让变量“动态更新“成为可能
在 CI/CD 流程里,"跨阶段的数据共享和状态传递"是一个非常常见的需求——上一个阶段算出来的值、跑出来的状态、检测到的事实,往往需要传给下一个阶段继续用。但 Jenkins 声明式流水线里,顶层
environment {}定义的变量是"不可变"的,这一点让不少同学吃过亏。本文聊聊这个问题的来龙去脉,以及一种让变量"动态更新"的新做法。
一、为什么需要跨阶段传递变量
Jenkins 流水线通常由多个阶段(Stage)串联而成:Prepare → Build → Test → Deploy → Post。在实际项目里,阶段之间几乎不可能完全独立——前面阶段的产出,后面阶段往往要用。典型场景有这么几类:
- 动态构建与部署配置:在 Prepare 或 Build 阶段,通过脚本动态算出版本号、镜像标签、构建产物路径等,再设置为全局变量,供后续的 Test / Deploy 阶段使用;
- 条件控制与流程分支:某个阶段做完检查后,设置一个状态变量(如
SKIP_DEPLOY=true),后续部署阶段读取它来决定是否跳过执行; - 捕获运行时信息:在代码扫描或测试阶段,动态提取出"失败用例数量""安全漏洞数量"等,存入全局变量,供流水线最后的 post 阶段做通知或归档。
简而言之:没有跨阶段的变量传递,流水线就只能各管各的,协同能力会大打折扣。
二、传统做法:script 块 + env 对象
在声明式流水线里,顶层 environment {} 中定义的变量不可变,无法在后续阶段直接覆盖。要实现动态修改全局变量,最标准的做法是使用 script 块配合 env 对象,在某个阶段里通过 env.VARIABLE_NAME = "value" 显式地定义或修改变量。这个变量会注入到 Jenkins 的环境上下文中,后续所有阶段都能读到更新后的值。
pipeline {
agent any
stages {
stage('Build') {
steps {
script {
// 动态计算并设置全局环境变量
env.APP_VERSION = "1.0.0-${env.BUILD_NUMBER}"
env.DEPLOY_ENV = "staging"
}
}
}
stage('Deploy') {
steps {
// 成功读取上一阶段设置的变量
echo "正在部署版本: ${env.APP_VERSION} 到 ${env.DEPLOY_ENV} 环境"
}
}
}
}
这种做法的局限
这种写法虽然标准,但有一个明显的短板:需要更新的变量名必须在脚本里显式声明——上面例子里的 env.APP_VERSION、env.DEPLOY_ENV 都是写死的。一旦有新的变量要更新,又得回来改一次流水线配置脚本。
换句话说,变量"清单"是固定的,"值"是动态的。对那些"变量本身也想动态决定"的场景(比如从一份外部配置里读取一组键值对,全部注入流水线),这种写法就力不从心了——每次新增一个字段都要改 Jenkinsfile,运维同学苦不堪言。
三、新做法:从外部配置批量注入,未声明的变量也能用
针对传统做法的局限,我们换一种思路:不再在脚本里逐个声明要更新的变量名,而是从一份外部配置文件中读取一组键值对,循环写入 env。这样即便流水线顶层 environment {} 里没有声明的变量,也能被动态设置并传递给后续阶段。
整套做法的精髓就三步:
- 准备一份外部 JSON 配置:把需要更新或新增的变量写成 JSON 文件,放在一个可访问的位置(HTTP 服务、对象存储、配置中心都行)。这份文件就是"变量清单"的真正来源——变量名和变量值都在这里,不在 Jenkinsfile 里。例如:
{
"RUN_TESTS": false,
"BRANCH": "develop",
"VERSION": "2.0.0",
"DATE": "20260330",
"NUM": 999
}
-
流水线里拉取并解析:用
wget/curl把配置下载到工作目录,再用readJSON(依赖 pipeline-utility-steps 插件)读成 Map 对象。到这一步,变量还是"文件里的数据",没进 Jenkins 环境。 -
循环写入
env:用configMap.each { key, value -> env[key] = value }把 Map 里每一组键值对都写入env。这一步是关键——env[key]的写法让变量名本身也变成动态的,不需要在脚本里写死任何变量名,配置里有什么,就注入什么。
完成这三步后,所有后续阶段(包括另一个 agent 上的阶段)都能直接读取这些变量,就像它们一开始就在 parameters {} 里声明过一样。更妙的是,原本在 parameters {} 里声明的变量(如 VERSION、RUN_TESTS、BRANCH),如果同名出现在 JSON 里,也会被覆盖——一份配置就能同时实现"新增变量"和"覆盖默认值"两个目的。
小提示1:这种方式无法无法更新覆盖在
environment {}里声明定义的变量
小提示2:
readJSON首次执行可能会报插件相关错误,可参考Jenkins读取Json文件报错解决方案 解决;
小提示3:写入
env的 value 会被统一转为字符串类型。
四、这种做法的亮点与优势
相比传统的"env.XXX = ... 显式声明"方式,新做法带来几个明显的优势:
1. 变量名解耦,新增变量零代码改动
这是最大的亮点。新增一个变量再也不需要改 Jenkinsfile,只要在 JSON 配置里加一行,下次流水线跑就会自动注入。运维和开发同学终于不用为了一个变量反复改流水线配置、走代码评审。
2. 一份配置同时实现"新增 + 覆盖"
JSON 里同名的变量会覆盖 environment {} / parameters {} 里的默认值。也就是说,同一份配置既能新增全新变量,又能动态调整已有变量的默认值,一个机制两件事一起办。
3. 配置外置,便于多环境/多版本管理
因为变量清单是外部文件,完全可以按环境、按版本准备不同的 JSON:dev 一份、staging 一份、prod 一份;v1.0 一份、v2.0 一份。切换时只需更换配置地址,流水线脚本本身保持不动,便于多环境多版本并行。
4. 变量集中可见,配置即文档
所有可调变量集中在一个 JSON 里,团队成员一眼就能看到"这次流水线会用到哪些变量、默认是什么"。比把变量散落在 Jenkinsfile 各个 script 块里要清晰得多,配置本身就成了一份活的流水线说明书。
5. 跨阶段、跨 agent 依然有效
示例中"更新变量"和"读取变量"两个阶段用了独立的 docker agent,但后者依然能读到前者注入的变量。这说明注入的变量是真正的"全局环境变量",不是某个阶段的局部变量,跨阶段、跨节点都能稳定传递。
五、典型使用场景
- 版本号动态化:构建阶段从外部配置读取版本号,注入后供部署、归档、通知阶段统一使用,避免版本号在多处硬编码;
- 流程分支控制:配置里加一个
SKIP_DEPLOY=true,部署阶段读取后直接跳过,用配置驱动流水线行为,比改 Jenkinsfile 安全得多; - 多环境发布:dev/staging/prod 各一份配置,同一份 Jenkinsfile 跑出不同环境的结果,降低流水线维护成本;
- 运行时信息归档:测试阶段把"失败用例数""扫描漏洞数"等写入外部配置或直接注入 env,post 阶段统一读取后发通知、归档;
- 批量参数下发:上游系统(如审批平台、调度系统)把一组参数写成 JSON 推到流水线可访问的位置,流水线自动注入,实现"上游下发配置 → 流水线消费"的联动。
六、小结
Jenkins 声明式流水线里"顶层 environment {} 不可变"这个限制,长期以来让跨阶段的变量共享略显别扭。传统 script 块 + env.XXX 的写法虽然能用,但变量名硬编码、新增要改脚本,扩展性有限。
新做法把"变量清单"外置成一份 JSON,用 readJSON + env[key] = value 的循环实现变量名也动态化的注入,带来几个实在的好处:
- 新增变量零代码改动——配置加一行即可;
- 新增与覆盖一气呵成——同名变量自动覆盖默认值;
- 配置外置便于多环境——一份 Jenkinsfile 配多份配置;
- 配置即文档——变量集中可见,团队一目了然;
- 跨阶段跨 agent 稳定传递——真正的全局环境变量。
如果你也厌倦了"加一个变量改一次 Jenkinsfile",不妨试试这种"配置驱动变量"的思路,把流水线的灵活性和可维护性都提上来。

浙公网安备 33010602011771号