并行构建:parallel 的几种写法与多节点调度
December 10, 2024About 3 min
并行构建:parallel 的几种写法与多节点调度
Jenkins 里并行有声明式的 parallel 块和脚本式的 parallel 步骤两套写法。游戏项目里典型场景是同一份代码同时在 Windows / Linux / 多台机器上跑测试或打包,下面按从简到繁列一遍。
声明式 parallel:嵌套 stage
声明式 Pipeline 里,一个 stage 可以用 parallel 包住一组子 stage 并行跑。规则:一个 stage 只能有 steps、stages、parallel、matrix 之一;parallel 块里的子 stage 仍然能用 agent、when、post 等。
加 failFast true 让任一并行分支失败时立即终止其余分支;也可以在 options 里写 parallelsAlwaysFailFast() 达到同样效果。
pipeline {
agent none
// options { parallelsAlwaysFailFast() } // 与下面 failFast true 等效
stages {
stage('Run Tests') {
failFast true // 某个分支失败就终止其它正在跑的分支
parallel {
stage('Test On Windows') {
agent {
node {
label "windows"
customWorkspace 'D:\\Jenkins'
}
}
steps { bat "run-tests.bat" }
post { always { junit "**/TEST-*.xml" } }
}
stage('Test On Linux') {
agent { label "linux" }
steps { sh "run-tests.sh" }
post { always { junit "**/TEST-*.xml" } }
}
}
}
}
}
脚本式:串行还是并行,运行时再决定
声明式的并行分支在编写时就固定了。如果要根据运行时条件(比如「unshelve 的内容含二进制就串行,否则并行」)动态切换,就得用 script 块手动拼 parallel:
pipeline {
agent any
stages {
stage("Build") {
steps {
script {
env.unshelveBinary = false
def stages = [:]
stages['stage 1'] = {
stage('stage 1') { echo 'this is stage 1' }
}
stages['stage 2'] = {
stage('stage 2') { echo 'this is stage 2' }
}
// 含二进制则串行执行,否则并行
if (unshelveBinary) {
for (stage in stages) {
stage.value.call()
}
} else {
parallel stages
}
}
}
}
}
}
用循环动态生成并行分支
分支数量由列表决定时用循环拼 map。注意别用 for in,闭包捕获的是循环变量引用,会全部取到最后一个值;要用带索引的传统 for 并在循环体内 def 出局部变量:
pipeline {
agent any
stages {
stage('Parallel Stages') {
steps {
script {
def tasks = ['Task1', 'Task2', 'Task3']
def branches = [:]
for (int i = 0; i < tasks.size(); i++) { // 不要用 for in
def taskName = tasks[i] // 必须在循环体内 def
branches[taskName] = {
stage(taskName) {
echo "Running ${taskName} in parallel"
}
}
}
parallel branches
}
}
}
}
}
并行 + 指定不同节点
要让每个并行分支落到不同的物理节点,脚本式里有个坑:在 node() 块内部再写 agent { node { label ... } } 是无效的、不支持的,得直接用 node(nodeLabel) { ... } 包住:
pipeline {
options { timestamps() }
agent {
node {
label "build-node-1"
customWorkspace "E:\\test"
}
}
stages {
stage('Parallel Stages') {
steps {
script {
def branches = [:]
def nodes = ['build-node-2', 'build-node-3']
for (int i = 0; i < nodes.size(); i++) {
def nodeLabel = nodes[i]
def stageName = "Stage ${i + 1} on ${nodeLabel}"
branches[stageName] = {
node(nodeLabel) { // 直接 node(),不能用 agent{node{}}
stage(stageName) {
echo "Running on ${nodeLabel}"
sleep 10
}
stage("${stageName} Test") {
echo "Test"
sleep 5
}
}
}
}
parallel branches
}
}
}
// 声明式并行 + 不同节点
stage('Parallel On Nodes') {
parallel {
stage("taskName1") {
agent { node { label 'build-node-2' } }
steps {
echo "Running ${env.NODE_NAME} in parallel"
sleep 10
}
}
stage("taskName2") {
agent { node { label 'build-node-3' } }
steps {
echo "Running ${env.NODE_NAME} in parallel"
sleep 10
}
}
}
}
}
}
不带 stage 的 parallel 步骤
如果不需要把并行任务显示成独立 stage,直接用 parallel(...) 步骤传一个 map 即可:
def a = "test"
pipeline {
agent { node { label "build-node-1" } }
stages {
stage('Parallel without stage') {
steps {
script {
parallel(
"Task1": {
if (a == "test") { sleep time: 1, unit: 'MINUTES' }
},
"Task2": {
if (a == "test") { sleep time: 1, unit: 'MINUTES' }
}
)
}
}
}
}
}