一、实践-Jenkins声明式管道中的动态并行阶段

1.1 实践-Jenkins声明式管道中的动态并行阶段2023

在 Jenkins Pipeline 中,parallel 是一个用于在不同的 agent 节点上并行执行多个步骤的语句。

通过使用 parallel,可以在减少构建时间的同时提高其效率。

1.1.1 Parallel使用方法

在 script 标签中定义一个 HashMap 变量 jobs,用于存储需要并行执行的任务。

def jobs = [:]

在 jobs 中添加需要并行执行的任务。每个任务都需要定义一个唯一的名称、代理和一组步骤。

jobs["job1"] = {
    stage("job1") {
        steps{
           script {
                sh "echo 123"
            }
        }
    }
}

jobs["job2"] = {
    stage("job2") {
        steps{
           script {
                sh "echo 123"
            }
        }
    }
}

在 parallel 语句中使用 jobs 变量并行执行这些任务。

parallel jobs

以下是一个完整的例子,

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                script {
                    def jobs = [:]
                    jobs["job1"] = {
                        stage("job1") {
                            sh "echo 123"
                        }
                    }
                    jobs["job2"] = {
                        stage("job2") {
                            sh "echo 123"
                        }
                    }
                    parallel jobs
                }
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying'
            }
        }
    }
}

Alt Image Text

1.2 CollectEntries用法

collectEntries 是 Groovy 的一个方法,用于将一个键值对集合转换成一个 Map。

该方法可以对集合进行遍历处理,并将每个元素转换成一个 Map 的一个键值对,从而生成一个新的 Map。

在 Jenkins Pipeline 中,collectEntries 可以用于处理多个项目,将其转换为一个 Map 并进行进一步处理。

pipeline {
    agent any
    stages {
        stage('Collect entries') {
            steps {
                script {
                    def projects = [
                        'Project 1': 5,
                        'Project 2': 3,
                        'Project 3': 1,
                        'Project 4': 2
                    ]

                    def priorityMap = projects.collectEntries {
                        [it.key, it.value * 10]
                    }

                    // Output the new map
                    echo "${priorityMap}"
                }
            }
        }
    }
}

在这个 Pipeline 示例中,我们定义了一个具有四个项目的简单列表,并使用 collectEntries 将其转换为一个新的 Map。

在 collectEntries 中,我们对每个 Map 元素(key-value对)进行了处理,将其值(value)乘以 10 生成新的值,并将其作为新 Map 的值(value)。

// Example function that would return an array of jobs to deploy
def getjobsToDeploy() {
    def jobList = []
    for (int i = 0; i < 5; i++) {
        jobList << "buildjob" + i.toString()
    }
  return jobList
}

// Convert array of jobs to map of stages
def getDeployStages() {
  return getjobsToDeploy().collectEntries { job ->
    [
      (job): {
        stage("deploy ${job}") {
          sh "ls "
        }
      }
    ]
  }
}

def getTestStages() {
  return getjobsToDeploy().collectEntries { job ->
    [
      (job): {
        stage("test ${job}") {
          sh "ls "
        }
      }
    ]
  }
}

pipeline {
  agent any
  stages {
    // other stages...
    stage('Deploy') {
      steps {
        script {
          // Directly pass the map of stages to `parallel`
          parallel getDeployStages()

        }
      }
    }

    stage('Test') {
      steps {
        script {
          parallel getTestStages() 
        }
      }
    }
  }
}

二、微服务模式下如何实现多模块并行构建发布

2.1 微服务模式下如何实现多模块并行构建发布

在微服务项目中具有很多个服务模块,为了便于管理需要将项目规范化,使用GitLabGroup管理整个项目,每个project对应一个微服务,每个微服务对应一个Jenkins job。开始发布的时候需要我们在手动选择各个服务然后发布。模块很多的时候就不太方便了。

创建一个Jenkins项目用来统一管理,绑定GitLab group信息,然后调用Gitlab接口获取project信息。用户选择要发布的微服务,然后根据服务名称并行触发每个要发布服务的流水线进行构建和发布。

我们先来看看如何使用pipeline中的parallel语法,这里列举了两个例子:并行任务、并行阶段

2.2 并行任务

首先我们需要定义每个任务的名称和对应的操作,可以通过一个Map类型参数实现。

在这里定义了一个空的map,然后为这个map增加了两个任务分别为build01build02,它们的操作是执行shell命令。

最后通过parallel运行。

def tasks = [:]

//定义要并行的任务名称和任务
tasks["build01"] =  {sh "ls"} 
tasks["build02"] =  {sh "ls"}

//并行
parallel tasks

2.3 并行阶段

并行stage也是一种经常用到的运行方式,我们可以使用这种方式解决不必要的时间浪费,尤其在各个平台运行自动化测试的时候,我们不必等到windows平台验证完成再进行Linux系统中的验证。而是直接在各个平台中同时运行,加快了测试的速度。

我们还可以使用failFast参数配置当其中一个任务失败其他任务是否一样失败,参数值为布尔类型(true false)

这个例子是jenkins官方提供的,大概的内容是并行在不同系统环境下进行自动化测试。

pipeline {
      agent none
      stages {
            stage('Run Tests') {
              parallel {
                  stage('Test On Windows') {
                      agent {
                          label "windows"
                      }
                      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"
                          }
                      }
                  }
              }
          }
      }
}

2.4 主控Job实现

首先我们用静态数据来存储要发布的项目名称存储为一个List,定义一个Map用来存储并行任务。定义buildStatus展示项目错误信息(这里做了信息格式化)。

使用build进行项目触发。这里会把每个失败的任务存储展示到流水线日志中。

def jobs = ["test2","demo-test-service"]
def parallelMap = [:]
def buildStatus = [:]

jobs.each{ 
    println(it)

    parallelMap[it] = {
        try {
            build job: "${it}", 
            parameters: [string(name: 'branchName', value: 'Dev')]
        } catch(e){
            println("${it}  " + e)
            buildStatus[it] = e
        }
    }
}

parallel parallelMap

//判断状态

for ( i in buildStatus.keySet()){
     currentBuild.description = "构建信息"
     currentBuild.description += ("\n项目名称->"  + i  + "错误信息:->"+ buildStatus[i])
}

运行效果

Alt Image Text

基本上每个模块对应一个pipeline项目。有时候我们需要设置一个集中控制项目来根据设置的参数有序触发各个模块服务对应的流水线进行发布。在各个模块之间没有发布顺序的时候,我们可以使用Pipeline的Parallel语法进行项目的并行构建。加快发布速度,减少重复手工操作成本。