Jenkins Pipeline 多job依赖、触发多Job、并行执行及制品下载

以下是关于 Jenkins Pipeline 触发多平台Job、并行执行及制品下载 的详细攻略,涵盖核心概念、关键配置和完整示例。

一、核心场景与目标

假设我们需要在一个主Pipeline中:

  1. 自动识别当前代码分支(如 wip/jenkins);
  2. 对分支名中的特殊字符(如 /)进行转义,适配Jenkins的Job命名规则;
  3. 并发触发3个平台的子Job(MacOSX、Windows、Linux),等待它们执行完成(!!!还有一种方式值直接下载其他job最后一次成功的制品,但是每个job的制品可能版本就不一样,就比如可能不是最新一次提交而生成的制品,有可能这次提交,其他job还没有执行完成,又或者执行失败了!!!);
  4. 从每个子Job中下载构建好的制品(如可执行文件,下载制品也可以通过Jenkins自带的插件下载,直接通过URL下载是由于一直有一个权限问题,目前不知道为什么);
  5. 聚合所有平台的制品,打包存档。

二、关键概念与细节

1. 分支名转义:为什么需要处理 /

Jenkins中,多分支Pipeline的Job名称会自动将分支名中的 / 编码为 %2F(URL编码规则)。例如:

  • 原始分支名:wip/jenkins
  • 对应的Jenkins Job名称:wip%2Fjenkins

处理方式 :用 sed 命令将 / 替换为 %2F,确保后续拼接Job路径时匹配Jenkins实际名称。

2. 触发Job的URL vs 下载制品的URL

Jenkins中,"触发Job"和"下载制品"的路径格式不同,这是最容易出错的点:

场景 路径格式说明 示例(基于分支 wip/jenkins
触发Job 用「Jenkins项目全称」,层级用 / 分隔,无 job/ 前缀(Jenkins内部识别格式)。 Middleware/Whiteboard/WhiteboardTools/WbresOnMacOSX/wip%2Fjenkins
下载制品 用「浏览器URL格式」,每个层级需加 job/ 前缀(HTTP请求路径格式)。 http://jenkins-url/job/Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresOnMacOSX/job/wip%2Fjenkins/3/artifact/...

核心区别 :触发Job时Jenkins接受"项目全称"(无 job/),而下载制品时的HTTP URL必须用 job/ 分隔层级。

3. 并行触发Job:为什么需要并行?

如果3个平台的子Job无依赖关系(可独立执行),并行触发能大幅缩短总构建时间:

  • 顺序执行:总时间 = t1 + t2 + t3
  • 并行执行:总时间 ≈ max(t1, t2, t3)

Jenkins通过 parallel 块实现并行,需注意语法(子阶段用键值对定义,支持 failFast 快速失败)。

三、详细步骤与配置

步骤1:定义分支名并转义

在Pipeline开始阶段,获取当前分支名并转义 /%2F,确保后续路径正确。

groovy 复制代码
stage('Set BRANCH_CODING') {
  steps {
    script {
      // 获取当前分支名(Jenkins内置变量 BRANCH_NAME,如 wip/jenkins)
      // 转义 / 为 %2F,适配Jenkins Job名称格式
      env.BRANCH_CODING = sh(
        script: '''echo "${BRANCH_NAME}" | sed 's/\\//%2F/g' ''',  // 关键:用sed替换 / 为 %2F
        returnStdout: true
      ).trim()
      echo "转义后的分支名(适配Jenkins):${env.BRANCH_CODING}"  // 输出:wip%2Fjenkins
    }
  }
}
步骤2:并行触发多平台子Job

parallel 块并发触发3个平台的子Job,每个子Job包含"触发→等待完成→下载制品"逻辑。

关键配置

  • triggerJobPath:触发Job用的"项目全称"(无 job/);
  • dependJobPath:下载制品用的"URL路径"(含 job/);
  • build 步骤:wait: true 等待Job完成,propagate: true 子Job失败时主Job也失败;
  • failFast: true:可选,一个子Job失败则立即终止所有并行任务。
步骤3:下载制品并验证

子Job执行完成后,通过构建编号(buildNumber)精准定位最新制品,用 curl 下载并验证文件是否存在。

步骤4:聚合制品并存档

所有平台制品下载完成后,打包为zip并存档到Jenkins。

四、完整Pipeline脚本(带详细注释)

groovy 复制代码
pipeline {
  agent {
    node {
      label 'UBUNTU'  // 运行主Pipeline的节点标签
    }
  }

  // 可选:手动指定测试分支(实际场景用Jenkins内置的BRANCH_NAME)
  environment {
    // BRANCH_NAME = 'wip/jenkins'  // 测试时可手动设置,正式环境注释掉
  }

  stages {
    // 步骤1:处理分支名,转义 / 为 %2F
    stage('Set BRANCH_CODING') {
      steps {
        script {
          // 从Jenkins内置变量获取当前分支名(如 git 分支)
          // 用sed将 / 替换为 %2F(适配Jenkins Job名称)
          env.BRANCH_CODING = sh(
            script: '''echo "${BRANCH_NAME}" | sed 's/\\//%2F/g' ''',
            returnStdout: true
          ).trim()
          echo "✅ 转义后的分支名:${env.BRANCH_CODING}"  // 示例输出:wip%2Fjenkins
        }
      }
    }

    // 步骤2:并行触发3个平台的子Job并下载制品
    stage('并行触发子Job并下载制品') {
      steps {
        script {
          // 并行执行3个子阶段(MacOSX/Windows/Linux)
          parallel(
            failFast: true,  // 快速失败:一个子Job失败,所有并行任务终止
            // 子阶段1:MacOSX平台
            "MacOSX": {
              // 1. 定义路径(触发Job和下载制品的路径分开)
              def jenkinsUrl = env.JENKINS_URL.trim().replaceAll(/\/$/, '')  // 去除URL末尾的 /
              // 触发Job用的项目全称(无 job/)
              def triggerJobPath = "Middleware/Whiteboard/WhiteboardTools/WbresOnMacOSX/${env.BRANCH_CODING}"
              // 下载制品用的URL路径(含 job/)
              def dependJobPath = "Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresOnMacOSX/job/${env.BRANCH_CODING}"
              def artifactSourcePath = 'tools/wbres/bin/macos/wbres'  // 子Job中制品的相对路径
              def artifactTargetDir = 'bin/macos/'  // 本地保存路径

              // 2. 触发子Job并等待完成
              echo "=== [MacOSX] 触发子Job:${triggerJobPath} ==="
              def buildResult = build(
                job: triggerJobPath,  // 传入项目全称(无 job/)
                wait: true,           // 等待子Job执行完成
                propagate: true,      // 子Job失败时,主Job也失败
                quietPeriod: 0        // 立即触发,不延迟
              )
              def buildNumber = buildResult.getNumber()  // 获取子Job的构建编号(如 3)
              echo "=== [MacOSX] 子Job执行完成,构建编号:${buildNumber} ==="

              // 3. 构建下载URL并下载制品
              def downloadUrl = "${jenkinsUrl}/job/${dependJobPath}/${buildNumber}/artifact/${artifactSourcePath}"
              sh "mkdir -p ${artifactTargetDir}"  // 创建本地目录
              echo "=== [MacOSX] 下载URL:${downloadUrl} ==="

              // 用Jenkins凭证下载(避免明文密码)
              withCredentials([usernamePassword(
                credentialsId: '2c66b483-ef17-436e-a0d2-114d00bdf090',  // 你的凭证ID
                usernameVariable: 'JENKINS_USER',
                passwordVariable: 'JENKINS_PWD'
              )]) {
                sh """
                  # 用curl下载制品,-f确保失败时返回非0状态码
                  curl -f -L -v \
                  -u "${JENKINS_USER}:${JENKINS_PWD}" \
                  "${downloadUrl}" \
                  -o "${artifactTargetDir}/wbres"

                  # 验证下载结果
                  echo "=== [MacOSX] 验证制品 ==="
                  ls -l "${artifactTargetDir}"
                  if [ -f "${artifactTargetDir}/wbres" ]; then
                    echo "✅ [MacOSX] 制品下载成功"
                  else
                    echo "❌ [MacOSX] 制品下载失败"
                    exit 1  # 失败时终止Pipeline
                  fi
                """
              }
            },

            // 子阶段2:Windows x64平台(逻辑与MacOSX一致,仅路径不同)
            "Windows x64": {
              def jenkinsUrl = env.JENKINS_URL.trim().replaceAll(/\/$/, '')
              def triggerJobPath = "Middleware/Whiteboard/WhiteboardTools/WbresX64OnWindows/${env.BRANCH_CODING}"
              def dependJobPath = "Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresX64OnWindows/job/${env.BRANCH_CODING}"
              def artifactSourcePath = 'tools/wbres/bin/win32/wbres.exe'
              def artifactTargetDir = 'bin/win32/'

              echo "=== [Windows] 触发子Job:${triggerJobPath} ==="
              def buildResult = build(job: triggerJobPath, wait: true, propagate: true, quietPeriod: 0)
              def buildNumber = buildResult.getNumber()
              echo "=== [Windows] 子Job执行完成,构建编号:${buildNumber} ==="

              def downloadUrl = "${jenkinsUrl}/job/${dependJobPath}/${buildNumber}/artifact/${artifactSourcePath}"
              sh "mkdir -p ${artifactTargetDir}"
              echo "=== [Windows] 下载URL:${downloadUrl} ==="

              withCredentials([usernamePassword(credentialsId: '2c66b483-ef17-436e-a0d2-114d00bdf090', usernameVariable: 'JENKINS_USER', passwordVariable: 'JENKINS_PWD')]) {
                sh """
                  curl -f -L -v -u "${JENKINS_USER}:${JENKINS_PWD}" "${downloadUrl}" -o "${artifactTargetDir}/wbres.exe"
                  echo "=== [Windows] 验证制品 ==="
                  ls -l "${artifactTargetDir}"
                  if [ -f "${artifactTargetDir}/wbres.exe" ]; then
                    echo "✅ [Windows] 制品下载成功"
                  else
                    echo "❌ [Windows] 制品下载失败"
                    exit 1
                  fi
                """
              }
            },

            // 子阶段3:Linux (Docker)平台
            "Linux (Docker)": {
              def jenkinsUrl = env.JENKINS_URL.trim().replaceAll(/\/$/, '')
              def triggerJobPath = "Middleware/Whiteboard/WhiteboardTools/WbresGccLinuxDocker/${env.BRANCH_CODING}"
              def dependJobPath = "Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresGccLinuxDocker/job/${env.BRANCH_CODING}"
              def artifactSourcePath = 'tools/wbres/bin/linux/wbres'
              def artifactTargetDir = 'bin/linux/'

              echo "=== [Linux] 触发子Job:${triggerJobPath} ==="
              def buildResult = build(job: triggerJobPath, wait: true, propagate: true, quietPeriod: 0)
              def buildNumber = buildResult.getNumber()
              echo "=== [Linux] 子Job执行完成,构建编号:${buildNumber} ==="

              def downloadUrl = "${jenkinsUrl}/job/${dependJobPath}/${buildNumber}/artifact/${artifactSourcePath}"
              sh "mkdir -p ${artifactTargetDir}"
              echo "=== [Linux] 下载URL:${downloadUrl} ==="

              withCredentials([usernamePassword(credentialsId: '2c66b483-ef17-436e-a0d2-114d00bdf090', usernameVariable: 'JENKINS_USER', passwordVariable: 'JENKINS_PWD')]) {
                sh """
                  curl -f -L -v -u "${JENKINS_USER}:${JENKINS_PWD}" "${downloadUrl}" -o "${artifactTargetDir}/wbres"
                  echo "=== [Linux] 验证制品 ==="
                  ls -l "${artifactTargetDir}"
                  if [ -f "${artifactTargetDir}/wbres" ]; then
                    echo "✅ [Linux] 制品下载成功"
                  else
                    echo "❌ [Linux] 制品下载失败"
                    exit 1
                  fi
                """
              }
            }
          )
        }
      }
    }

    // 步骤3:聚合所有平台的制品并存档
    stage('聚合制品并存档') {
      steps {
        script {
          // 将所有平台的制品打包为zip
          sh 'mkdir -p bin && zip -r wbres_all_platforms.zip bin/'
        }
        // 存档到Jenkins(仅当所有步骤成功时)
        archiveArtifacts(
          artifacts: 'wbres_all_platforms.zip',
          fingerprint: true,
          onlyIfSuccessful: true
        )
        echo "✅ 所有平台制品已聚合并存档:wbres_all_platforms.zip"
      }
    }
  }

  // 构建结束后通知(可选)
  post {
    success {
      echo "🎉 主Pipeline执行成功!所有制品已就绪。"
      // 可添加邮件/企业微信通知:emailext to: 'xxx@example.com', subject: '构建成功', body: '...'
    }
    failure {
      echo "❌ 主Pipeline执行失败,请查看日志。"
      // 失败通知:emailext to: 'xxx@example.com', subject: '构建失败', body: '...'
    }
  }
}

五、调试与验证技巧

  1. 验证分支名转义

    查看 Set BRANCH_CODING 阶段的输出,确认 BRANCH_CODING 正确转义(如 wip/jenkinswip%2Fjenkins)。

  2. 检查触发Job的路径

    在Jenkins网页端导航到子Job,查看浏览器URL中的"项目全称"(如 Middleware/Whiteboard/.../wip%2Fjenkins),与脚本中的 triggerJobPath 对比是否一致。

  3. 验证下载URL

    下载阶段输出的 downloadUrl 可直接复制到浏览器,用Jenkins账号密码访问,确认能否下载制品(排除URL格式错误)。

  4. 并行执行日志

    在Jenkins Pipeline页面,并行阶段会显示为"分支",可点击单个分支查看详细日志(如MacOSX子阶段的触发和下载过程)。

六、常见问题与解决

  1. "No item named ... found"

    原因:triggerJobPath 与Jenkins实际项目全称不匹配(如未转义 / 或多了 job/)。

    解决:按"触发Job的URL格式"修正 triggerJobPath,确保无 job// 已转义为 %2F

  2. 并行阶段语法错误

    原因:parallel 块内用 stage(...) { ... } 定义子阶段,或 failFast 位置错误。

    解决:子阶段用键值对 "名称": { ... } 定义,failFast 作为 parallel 函数的参数(如 parallel(failFast: true, "子阶段1": { ... }))。

  3. 制品下载404

    原因:downloadUrl 中的 dependJobPath 未加 job/ 层级,或构建编号错误。

    解决:确保 dependJobPath 每个层级都有 job/,并通过 buildResult.getNumber() 获取正确的构建编号。

通过以上步骤,你可以实现"并行触发多平台Job→等待完成→下载制品→聚合存档"的完整流程,大幅提升构建效率并确保制品版本一致性。

相关推荐
Ghost Face...4 小时前
深入解析U-Boot命令系统
linux·运维·服务器
ajax_beijing4 小时前
当同一个弹性云服务器所在子网同时设置了snat和弹性公网IP时,会优先使用哪个
linux·运维·服务器
eddy-原4 小时前
运维自动化与监控体系综合实践作业
运维·自动化·1024程序员节
聆风吟º4 小时前
Linux远程控制Windows桌面的cpolar实战指南
linux·运维·windows
随风语4 小时前
云计算与服务器
运维·服务器·云计算
wanhengidc4 小时前
服务器会遭受到哪些网络攻击
运维·服务器
轮子大叔4 小时前
如何自建内网穿透(FRP)服务器
运维·服务器
dessler5 小时前
Elasticsearch(ES)Cerebro部署和使用
linux·运维·elasticsearch