jenkins pipeline技巧

说明:声明式流水线写得顺手之后,总会碰到几个绕不过去的问题——跑得太慢、要在机器之间传文件、并发构建互相打架、上线前要人等拍板、命令偶发失败。这篇把六个高频技巧逐个拆开,讲清楚每个参数是干什么的、什么时候用、坑在哪。

总览:六大技巧定位

技巧解决的核心问题一句话记忆
parallel执行慢——串行太耗时「同时跑,省时间」
stash / unstash跨节点/跨阶段传文件「打包暂存,换机器取」
lock / milestone并发冲突、旧构建抢跑「上锁防撞,里程碑淘汰旧的」
input需要人拍板「停下来等人批准」
retry / timeout / waitUntil不稳定、卡死、依赖未就绪「重试、限时、等到就绪」
script声明式表达力不够「钻进 Groovy 随便写」

这六个不是互相独立的。真实的生产流水线通常就是「parallel 跑测试 + lock 占住环境 + input 等人审批 + timeout 兜底超时」,最后用 script 处理声明式写不出的动态逻辑。结尾有一份组合实战。


一、parallel 并行

把原本串行执行的多个 stage 同时跑,显著缩短流水线总时长。常用于互不依赖的测试套件、多平台构建。

1.1 参数

声明式里 parallel 是一个阶段容器,本身没有「参数」参数,但它内部每个分支都是标准 stage,并且有一个关键开关:

元素必填作用
stage('分支名'){...}必填parallel 内的每个并行分支,必须是一个独立 stage;分支名会显示在 Blue Ocean / 阶段图上
failFast: true可选写在 parallel { } 内的第一行。true = 任一分支失败,立即中止其余分支(适合「快速失败」);false(默认)= 某分支失败,其余分支仍跑完(适合「攒齐所有测试结果」)
agent {...}(各分支内)可选每个并行分支可指定不同 agent(如一个跑 Linux、一个跑 Windows),不写则继承外层 agent

脚本式(Scripted)用 parallel 步骤,形态不同:

parallel(
  failFast: true,                       // 脚本式把开关放第一个键值
  '单元测试': { node { sh 'make unit' } },
  '端到端':   { node { sh 'make e2e'  } }
)
元素作用
failFast同上,放参数首位
键: 闭包每个键是分支名,值是要执行的 Groovy 闭包(可含 node{}、steps 等)

1.2 场景与示例

场景 A(快速失败):提交前的多重校验,任一不过就别浪费时间跑其他的。
场景 B(攒齐结果):跑多组测试,希望一次看到哪组挂了,而不是第一组挂了就停。

stage('质量门禁') {
  parallel(
    failFast: true,                    // A:任一失败立刻停
    '单元测试':   { stage('Unit'){ steps{ sh 'make unit' } } },
    '代码扫描':   { stage('Scan'){ steps{ sh 'make sonar' } } },
    '构建产物':   { stage('Build'){ steps{ sh 'make build' } } }
  )
}

stage('全量测试') {
  parallel {                           // B:默认 failFast:false,跑完所有
    stage('Web E2E'){ steps{ sh 'make e2e-web' } }
    stage('API E2E'){ steps{ sh 'make e2e-api' } }
  }
}

注意坑:parallel 内的 stage 不能再嵌套 parallel(声明式限制);且 parallel 默认不共享工作区,需要共享文件请用下面的 stash。另外并行分支越多,占用的 executor 越多,注意节点并发上限。


二、stash / unstash 跨节点传文件

Jenkins 的 workspace 是按节点隔离的:在节点 A 构建出的产物,节点 B 默认看不到。stash 把文件打包暂存到 Controller 端(内存/磁盘临时区),unstash 在另一处恢复。它是「临时传递」,区别于持久化的 archiveArtifacts

2.1 stash 参数

参数必填类型/默认作用
name必填String暂存包的唯一标识,unstash 靠它取回。建议语义化,如 'dist'
includes二选一String(Ant 风格)要收纳的文件模式,如 'dist/**, target/*.jar'。与 excludes 至少给一个
excludes二选一String(Ant 风格)排除模式,如 '**/*.log'
allowEmpty可选bool,默认 falsetrue = 没有匹配到任何文件也不报错;false = 匹配为空直接失败(防止漏打包还以为成功了)
useDefaultExcludes可选bool,默认 true是否自动排除 SCM 元数据(如 .git)。一般保持默认即可

2.2 unstash 参数

参数必填作用
name必填对应 stash 时的 name
includes / excludes可选恢复时进一步缩小范围(只拿出需要的子集)

2.3 场景与示例

场景:构建机(Maven 节点)编译出 jar,部署机(deploy 节点,无 JDK)需要这个 jar。或者并行分支 A 产出、分支 B 之后要消费。

stage('Build') {
  agent { label 'maven' }
  steps {
    sh 'mvn package'
    stash name: 'app-jar', includes: 'target/*.jar', allowEmpty: false
  }
}
stage('Deploy') {
  agent { label 'deploy' }            // 另一个节点
  steps {
    unstash 'app-jar'                 // 把 jar 恢复到本节点 workspace
    sh 'scp target/*.jar server:/opt/app/'
  }
}

stash vs archiveArtifacts:stash 是「流水线内部短时传递」,不长久保留、不显示在构建页;archiveArtifacts 会把产物保存下来供下载和历史追溯。要留存交付物用后者,要跨节点搬运用前者。


三、lock / milestone 并发控制

当多个构建可能同时操作同一资源(同一个生产环境、同一个共享许可证)时,需要「互斥」;当新构建已经排队、旧的还在等人审批时,需要「让新的赢」。两者配合是生产发布的标准姿势。

3.1 lock 参数

参数必填默认作用
resource必填锁的名字(字符串)。同名锁互斥,同一时刻只有一个构建能持有
quantity可选1该资源允许同时持有的数量。>1 用于「资源池」场景,如 5 个许可证席位 → quantity: 5,第 6 个才排队
variable可选把锁的持有信息(如被授予的编号)绑定到一个环境变量名,供脚本读取
label可选给人看的队列标签,出现在「锁队列」展示里,便于排查谁在等

用法:可放在 options { } 里(整段加锁),也可放在 steps 里用 lock(resource:'x'){...} 局部加锁。

3.2 milestone 参数

参数必填作用
ordinal必填整数序号。含义:序号更大的 milestone 一旦通过,所有还卡在更早 milestone 的旧构建会被取消。通常用递增数字(1,2,3…)
label可选显示标签

3.3 场景与示例

场景 A(lock):两条流水线都可能部署到 prod-cluster,绝不能同时进行,否则互相覆盖。
场景 B(milestone):构建 #10 还在 input 等人审批,构建 #11 已经跑完并准备部署。应该让 #11 赢,取消 #10 的审批——否则 #10 被批准后会拿旧代码上线。

stage('Deploy Prod') {
  options {
    lock(resource: 'prod-cluster', quantity: 1, label: '生产集群')
    milestone(1)                       // 走到这就标记"我是最新的"
  }
  steps {
    input message: '确认发布生产?', submitter: 'ops-lead'
    milestone(2)                       // 审批通过后再标记一次
    sh 'deploy.sh'
  }
}

经验:把 milestone 放在 input 前后各一个。前一个起「新的抢跑」作用,后一个确保「批准通过的才算数」。配合 lock 杜绝并发部署。


四、input 人工审批

流水线运行到某处主动暂停,弹出表单等人操作,通过后才继续。是「人在回路」的关键,几乎所有生产发布都需要。

4.1 参数

参数必填默认作用
message必填提示文案,显示在审批弹窗和日志里,说明「要人决定什么」
id可选随机唯一标识。用于「可恢复构建」(restart from stage)时精确对齐。复用 Jenkinsfile 建议显式指定
ok可选'Proceed'确认按钮上的文字,如中文 '批准'
submitter可选所有人逗号分隔的允许审批的用户/组,如 'alice,bob,ops-team'。不在列表里的人看不到/点不了
submitterParameter可选把「实际是谁点的批准」写入这个环境变量,用于审计留痕
parameters可选额外的表单字段,让用户填写(如发布版本、回滚原因)。语法同流水线 parameters{}

注意坑input 本身没有 timeout 参数。要限时必须用 timeout 包裹:timeout(time:30,unit:'MINUTES'){ input ... },超时则按 timeout 的默认行为(中断)。否则审批窗口会一直开着,executor 也被占着。

4.2 两种写法

写法一:作为 stage 指令(声明式推荐,结构清晰)

stage('发布审批') {
  input {
    message '确认发布到生产?'
    id 'prod-approve'
    ok '批准'
    submitter 'alice,bob'
    submitterParameter 'APPROVER'
    parameters {
      string(name: 'REASON', defaultValue: '', description: '发布原因')
    }
  }
  steps { sh 'deploy.sh' }
}

写法二:作为 step(放在 steps 内,可嵌逻辑)

steps {
  def decision = input(
    message: '是否继续?',
    ok: '继续',
    parameters: [booleanParam(name: 'FORCE', defaultValue: false)]
  )
  if (decision.FORCE) { sh 'force-deploy.sh' }
}

4.3 场景与示例

场景:生产发布前强制二级审批,且只让运维负责人能批,并记录谁批的、为什么批,满足合规审计。

stage('生产发布门禁') {
  input {
    message: '即将发布到 PROD,确认?'
    ok: '确认发布'
    submitter: 'ops-lead,sre'
    submitterParameter: 'APPROVED_BY'
    parameters: [ string(name: 'TICKET', description: '变更单号') ]
  }
  steps {
    echo "由 ${APPROVED_BY} 批准,变更单 ${TICKET}"
    sh 'deploy-prod.sh'
  }
}

五、retry / timeout / waitUntil 容错三件套

流水线跑在真实环境里,会遇到:偶发失败(flaky)、卡死、依赖服务还没起来。这三个步骤分别兜底。

5.1 retry 参数

参数必填默认作用
count必填最大重试次数(不含首次)。retry(3) = 最多跑 4 次(1 次原始 + 3 次重试)
conditions可选重试触发条件列表,如 [nonresumable, unstable]。默认任何失败都重试;可限定只在特定状态重试

5.2 timeout 参数

参数必填默认作用
time必填超时阈值数值
unit可选MINUTES单位:NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
activity可选falsetrue = 只要构建有任意活动(输出日志)就重置计时,适合「长任务但一直在动」;false = 从包裹开始就固定倒计时

options { timeout(...) } 用在阶段级可设整体上限;timeout(time:..){ steps... } 用在 steps 内可只包裹某个易卡死的命令。

5.3 waitUntil 参数

参数必填默认作用
timeout可选无(一直等)最长等待时间,超时则失败。建议必填,否则可能永久阻塞
initialRecurrencePeriod可选内部默认首次检查前的初始等待(毫秒级),用于避免「立刻轮询」的惊群

waitUntil 内放一个返回布尔值的闭包,Jenkins 周期性执行,直到返回 true 或超时。

5.4 场景与示例

场景:① 集成测试偶发超时但重跑就过 → retry;② 某命令可能死循环 → timeout 兜底;③ 刚启动的服务端口还没监听 → waitUntil 轮询就绪。

steps {
  // 1) 重试偶发失败的集成测试
  retry(3) { sh 'make integration-test' }

  // 2) 给可能卡死的步骤上锁死时间
  timeout(time: 10, unit: 'MINUTES') {
    sh './long-migration.sh'
  }

  // 3) 轮询等待服务就绪(最多等 5 分钟)
  timeout(time: 5, unit: 'MINUTES') {
    waitUntil {
      script { return sh(returnStatus: true, script: 'curl -sf http://svc/health') == 0 }
    }
  }
}

注意坑retry 会重跑整个闭包。如果闭包里有「副作用」(如已发了通知、已建了资源),重试会重复执行——务必保证被重试的逻辑是幂等的。


六、script 逃生舱

声明式流水线语法简洁但有边界:不能用任意循环、动态生成 stage、复杂条件分支。script 块让你在声明式内部「切回 Groovy 全部能力」。

用法与变量作用域

stage('动态逻辑') {
  steps {
    script {
      def targets = ['web','api','worker']      // 块内可见
      def results = [:]
      targets.each { svc ->
        results[svc] = buildJob(svc)              // 调共享库/任意方法
      }
      // 可写复杂 if/循环/异常捕获
      if (results.values().any { it == 'FAIL' }) {
        error('存在失败服务,中止')
      }
    }
  }
}
要点说明
无参数script 只接受一个 Groovy 闭包,没有独立参数
返回值闭包的最后一个表达式值就是 script{} 的返回值,可赋给外层变量
作用域def 变量仅在该 script 块内有效;要跨 stage 共享,赋值给 env. 或声明在 pipeline 顶层

6.1 场景与示例

场景 A:根据 Git 变更的文件动态决定要构建哪些模块(声明式 when 写不出这种循环)。
场景 B:根据参数 + 外部 API 返回,动态拼出要执行的 stage 列表。

stage('按需构建') {
  steps {
    script {
      def changed = sh(returnStdout: true, script: 'git diff --name-only HEAD~1')
                       .split('\n').toList()
      def modules = changed.collect { it.split('/')[0] }.unique()
      if (modules.isEmpty()) { modules = ['web','api'] }
      modules.each { m ->
        echo "构建模块: ${m}"
        build job: "build-${m}", wait: true
      }
    }
  }
}

原则:能用声明式指令(when / parallel 等)就别用 script;只有当「声明式真的写不出」时才进 script,并保持块小而清晰,便于维护与排错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值