【问题标题】:How to trigger a Jenkins Job on multiple nodes from a Pipeline (only one job executing)如何从管道触发多个节点上的 Jenkins 作业(仅执行一个作业)
【发布时间】:2019-05-22 01:12:27
【问题描述】:

我有一个 Jenkins 作业,配置为 Scripted Jenkins Pipeline,其中:

  1. 从 GitHub 签出代码
  2. 合并开发者变更
  3. 构建调试映像

然后它应该分成 3 个独立的并行进程 - 其中一个构建代码的发布版本并对其进行单元测试。 其他 2 个进程应该是相同的,调试映像被刷新到目标上并运行各种测试。

目标在 Jenkins 中被标识为 slave_1slave_2,并且都被分配了标签 131_ci_targets

我正在使用“并行”来触发发布构建以及测试作业的多个实例。我将在下面发布我的脚本管道的(略微编辑的)副本以供完整参考,但对于这个问题,我已经尝试了以下所有 3 个选项。

  1. 使用单个build 调用,并将LabelParamaterValueallNodesMatchingLabel 设置为true。在此 TEST_TARGETS 是标签 131_ci_targets
parallel_steps = [:]
parallel_steps["release"] = { // Release build and test steps
}

parallel_steps["${TEST_TARGETS}"] = {
    stage("${TEST_TARGETS}") {
        build job: 'Trial_Test_Pipe',
              parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                           string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                           [$class: 'LabelParameterValue',
                            name: 'RUN_NODE', label: "${TEST_TARGETS}",
                            allNodesMatchingLabel: true,
                            nodeEligibility: [$class: 'AllNodeEligibility']]]
    }
} // ${TEST_TARGETS}

stage('Parallel'){
    parallel parallel_steps
} // Parallel
  1. 使用单个build 调用与NodeParamaterValue 和所有节点的列表。在此 TEST_TARGETS 再次是标签,而 test_nodes 是 2 个字符串的列表:[slave_1, slave_2]
parallel_steps = [:]
parallel_steps["release"] = { // Release build and test steps
}

test_nodes = hostNames("${TEST_TARGETS}")

parallel_steps["${TEST_TARGETS}"] = {
    stage("${TEST_TARGETS}") {
        echo "test_nodes: ${test_nodes}"
        build job: 'Trial_Test_Pipe',
              parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                           string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                           [$class: 'NodeParameterValue',
                            name: 'RUN_NODE', labels: test_nodes,
                            nodeEligibility: [$class: 'AllNodeEligibility']]]
    }
} // ${TEST_TARGETS}

stage('Parallel'){
    parallel parallel_steps
} // Parallel

3:使用多个阶段,每个阶段都有一个 build 调用,NodeParamaterValue 和一个仅包含 1 个从属 ID 的列表。 test_nodes 是字符串列表:[slave_1, slave_2],而第一个调用通过slave_1,第二个调用通过slave_2

        for ( tn in test_nodes ) {
            parallel_steps["${tn}"] = {
                stage("${tn}") {
                    echo "test_nodes: ${test_nodes}"
                    build job: 'Trial_Test_Pipe',
                          parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                       string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                       [$class: 'NodeParameterValue',
                                        name: 'RUN_NODE', labels: [tn],
                                        nodeEligibility: [$class: 'IgnoreOfflineNodeEligibility']]],
                          wait: false
                }
            } // ${tn}
        }

假设slave_1slave_2 都已定义、在线且有可用的执行程序,上述所有操作将仅触发slave_2 上的“Trial_Test_Pipe”的一次运行。

Trial_Test_Pipe 作业是另一个 Jenkins 流水线作业,未选中“不允许并发构建”复选框。

任何想法:

  • 为什么作业只会触发其中一个运行,而不是两个?
  • 正确的解决方案可能是什么?

现在供参考:这是我的完整(ish)脚本 Jenkins 工作:

import hudson.model.*
import hudson.EnvVars
import groovy.json.JsonSlurperClassic
import groovy.json.JsonBuilder
import groovy.json.JsonOutput
import java.net.URL

def BUILD_SLAVE=""

// clean the workspace before starting the build process
def clean_before_build() {
    bat label:'',
        script: '''cd %GITHUB_REPO_PATH%
                   git status
                   git clean -x -d -f
                   '''
}

// Routine to build the firmware
// Can build Debug or Release depending on the environment variables
def build_the_firmware() {
    return
    def batch_script = """
        REM *** Build script here
        echo "... Build script here ..."
        """

    bat label:'',
        script: batch_script
}

// Copy the hex files out of the Build folder and into the Jenkins workspace
def copy_hex_files_to_workspace() {
    return
    def batch_script = """
        REM *** Copy HEX file to workspace:
        echo "... Copy HEX file to workspace ..."
        """

    bat label:'',
        script: batch_script
}

// Updated from stackOverflow answer: https://stackoverflow.com/a/54145233/1589770
@NonCPS
def hostNames(label) {
    nodes = []
    jenkins.model.Jenkins.instance.computers.each { c ->
        if ( c.isOnline() ){
            labels = c.node.labelString
            labels.split(' ').each { l ->
                if (l == label) {
                    nodes.add(c.node.selfLabel.name)
                }
            }
        }
    }
    return nodes
}

try {
    node('Build_Slave') {
        BUILD_SLAVE = "${env.NODE_NAME}"
        echo "build_slave=${BUILD_SLAVE}"

        stage('Checkout Repo') {
            // Set a desription on the build history to make for easy identification
            currentBuild.setDescription("Pull Request: ${PULL_REQUEST_NUMBER} \n${TARGET_BRANCH}")

            echo "... checking out dev code from our repo ..."
        } // Checkout Repo

        stage ('Merge PR') {
            // Merge the base branch into the target for test
            echo "... Merge the base branch into the target for test ..."
        } // Merge PR

        stage('Build Debug') {
            withEnv(['LIB_MODE=Debug', 'IMG_MODE=Debug', 'OUT_FOLDER=Debug']){
                clean_before_build()
                build_the_firmware()
                copy_hex_files_to_workspace()

                archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
            }
        } // Build Debug

        stage('Post Build') {
            if (currentBuild.resultIsWorseOrEqualTo("UNSTABLE")) {
                echo "... Send a mail to the Admins and the Devs ..."
            }
        } // Post Merge

    } // node

    parallel_steps = [:]
    parallel_steps["release"] = {
        node("${BUILD_SLAVE}") {
            stage('Build Release') {
                withEnv(['LIB_MODE=Release', 'IMG_MODE=Release', 'OUT_FOLDER=build\\Release']){
                    clean_before_build()
                    build_the_firmware()
                    copy_hex_files_to_workspace()

                    archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
                }
            } // Build Release
            stage('Unit Tests') {
                echo "... do Unit Tests here ..."
            }
        }
    } // release

    test_nodes = hostNames("${TEST_TARGETS}")

    if (true) {
        parallel_steps["${TEST_TARGETS}"] = {
            stage("${TEST_TARGETS}") {
                echo "test_nodes: ${test_nodes}"
                build job: 'Trial_Test_Pipe',
                      parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                   string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                   [$class: 'LabelParameterValue',
                                    name: 'RUN_NODE', label: "${TEST_TARGETS}",
                                    allNodesMatchingLabel: true,
                                    nodeEligibility: [$class: 'AllNodeEligibility']]]
            }
        } // ${TEST_TARGETS}
    } else if ( false ) {
        parallel_steps["${TEST_TARGETS}"] = {
            stage("${TEST_TARGETS}") {
                echo "test_nodes: ${test_nodes}"
                build job: 'Trial_Test_Pipe',
                      parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                   string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                   [$class: 'NodeParameterValue',
                                    name: 'RUN_NODE', labels: test_nodes,
                                    nodeEligibility: [$class: 'AllNodeEligibility']]]
            }
        } // ${TEST_TARGETS}
    } else {
        for ( tn in test_nodes ) {
            parallel_steps["${tn}"] = {
                stage("${tn}") {
                    echo "test_nodes: ${test_nodes}"
                    build job: 'Trial_Test_Pipe',
                          parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                       string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                       [$class: 'NodeParameterValue',
                                        name: 'RUN_NODE', labels: [tn],
                                        nodeEligibility: [$class: 'IgnoreOfflineNodeEligibility']]],
                          wait: false
                }
            } // ${tn}
        }
    }

    stage('Parallel'){
        parallel parallel_steps
    } // Parallel
} // try
catch (Exception ex) {
    if ( manager.logContains(".*Merge conflict in .*") ) {
        manager.addWarningBadge("Pull Request ${PULL_REQUEST_NUMBER} Experienced Git Merge Conflicts.")
        manager.createSummary("warning.gif").appendText("<h2>Experienced Git Merge Conflicts!</h2>", false, false, false, "red")
    }

    echo "... Send a mail to the Admins and the Devs ..."

    throw ex
}

【问题讨论】:

    标签: jenkins groovy jenkins-pipeline


    【解决方案1】:

    所以...我有一个解决方案...例如,我知道该怎么做,以及为什么上述解决方案之一不起作用。

    获胜者是 选项 3 ...它不起作用的原因是未评估外壳内的代码(stage 部分)直到阶段实际运行。因此,字符串直到那时才展开,并且由于 tn 到那时固定为 slave_2,这就是两个并行流上使用的值。

    在此处的 Jenkins 示例中 ... [https://jenkins.io/doc/pipeline/examples/#parallel-from-grep] ... 附件是从函数 transformIntoStep 返回的,通过这样做,我能够强制对字符串进行早期评估,因此可以在两者上运行并行步骤奴隶。

    如果您在这里寻找答案,我希望这会有所帮助。如果你是,并且它有,请随时给我一个提升。干杯:)

    我的最终脚本 jenkinsfile 看起来像这样:

    import hudson.model.*
    import hudson.EnvVars
    import groovy.json.JsonSlurperClassic
    import groovy.json.JsonBuilder
    import groovy.json.JsonOutput
    import java.net.URL
    
    BUILD_SLAVE=""
    parallel_steps = [:]
    
    // clean the workspace before starting the build process
    def clean_before_build() {
        bat label:'',
            script: '''cd %GITHUB_REPO_PATH%
                       git status
                       git clean -x -d -f
                       '''
    }
    
    // Routine to build the firmware
    // Can build Debug or Release depending on the environment variables
    def build_the_firmware() {
        def batch_script = """
            REM *** Build script here
            echo "... Build script here ..."
            """
    
        bat label:'',
            script: batch_script
    }
    
    // Copy the hex files out of the Build folder and into the Jenkins workspace
    def copy_hex_files_to_workspace() {
        def batch_script = """
            REM *** Copy HEX file to workspace:
            echo "... Copy HEX file to workspace ..."
            """
    
        bat label:'',
            script: batch_script
    }
    
    // Updated from stackOverflow answer: https://stackoverflow.com/a/54145233/1589770
    @NonCPS
    def hostNames(label) {
        nodes = []
        jenkins.model.Jenkins.instance.computers.each { c ->
            if ( c.isOnline() ){
                labels = c.node.labelString
                labels.split(' ').each { l ->
                    if (l == label) {
                        nodes.add(c.node.selfLabel.name)
                    }
                }
            }
        }
        return nodes
    }
    
    def transformTestStep(nodeId) {
        return {
            stage(nodeId) {
                build job: 'Trial_Test_Pipe',
                      parameters: [string(name: 'TARGET_BRANCH', value: TARGET_BRANCH),
                                   string(name: 'FRAMEWORK_VERSION', value: FRAMEWORK_VERSION),
                                   [$class: 'NodeParameterValue',
                                    name: 'RUN_NODE', labels: [nodeId],
                                    nodeEligibility: [$class: 'IgnoreOfflineNodeEligibility']]],
                      wait: false
            }
        }
    }
    
    def transformReleaseStep(build_slave) {
        return {
            node(build_slave) {
                stage('Build Release') {
                    withEnv(['LIB_MODE=Release', 'IMG_MODE=Release', 'OUT_FOLDER=build\\Release']){
                        clean_before_build()
                        build_the_firmware()
                        copy_hex_files_to_workspace()
    
                        archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
                    }
                } // Build Release
                stage('Unit Tests') {
                    echo "... do Unit Tests here ..."
                }
            }
        }
    }
    
    
    try {
        node('Build_Slave') {
            BUILD_SLAVE = "${env.NODE_NAME}"
            echo "build_slave=${BUILD_SLAVE}"
    
            parallel_steps["release"] = transformReleaseStep(BUILD_SLAVE)
    
            test_nodes = hostNames("${TEST_TARGETS}")
            for ( tn in test_nodes ) {
                parallel_steps[tn] = transformTestStep(tn)
            }
    
            stage('Checkout Repo') {
                // Set a desription on the build history to make for easy identification
                currentBuild.setDescription("Pull Request: ${PULL_REQUEST_NUMBER} \n${TARGET_BRANCH}")
    
                echo "... checking out dev code from our repo ..."
            } // Checkout Repo
    
            stage ('Merge PR') {
                // Merge the base branch into the target for test
                echo "... Merge the base branch into the target for test ..."
            } // Merge PR
    
            stage('Build Debug') {
                withEnv(['LIB_MODE=Debug', 'IMG_MODE=Debug', 'OUT_FOLDER=Debug']){
                    clean_before_build()
                    build_the_firmware()
                    copy_hex_files_to_workspace()
    
                    archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
                }
            } // Build Debug
    
            stage('Post Build') {
                if (currentBuild.resultIsWorseOrEqualTo("UNSTABLE")) {
                    echo "... Send a mail to the Admins and the Devs ..."
                }
            } // Post Merge
    
        } // node
    
        stage('Parallel'){
            parallel parallel_steps
        } // Parallel
    
    } // try
    catch (Exception ex) {
        if ( manager.logContains(".*Merge conflict in .*") ) {
            manager.addWarningBadge("Pull Request ${PULL_REQUEST_NUMBER} Experienced Git Merge Conflicts.")
            manager.createSummary("warning.gif").appendText("<h2>Experienced Git Merge Conflicts!</h2>", false, false, false, "red")
        }
    
        echo "... Send a mail to the Admins and the Devs ..."
    
        throw ex
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多