【问题标题】:In a declarative jenkins pipeline - can I set the agent label dynamically?在声明式詹金斯管道中 - 我可以动态设置代理标签吗?
【发布时间】:2022-04-01 11:02:50
【问题描述】:

有没有办法动态设置代理标签而不是纯字符串?

这项工作有 2 个阶段:

  1. 第一阶段 - 始终在“主”代理上运行。在这个阶段结束时,我会知道第二阶段应该在哪个代理上运行。
  2. 第二阶段 - 应在第一阶段确定的代理上运行。

我的(不工作)尝试如下所示:

pipeline {
    agent { label 'master' }
    stages {
        stage('Stage1') {
            steps {
                script {
                    env.node_name = "my_node_label"
                }
                echo "node_name: ${env.node_name}"
            }
        }

        stage('Stage2') {
            agent { label "${env.node_name}" }
            steps {
                echo "node_name: ${env.node_name}"
            }
        }
    }
}

第一个回显工作正常,并打印“my_node_label”。 第二阶段无法在标记为“my_node_label”的代理上运行,控制台打印:

没有标签为“null”的节点

也许它可以提供帮助 - 如果我只是将“${env}”放在标签字段中,我可以看到这是一个 java 类,因为它打印:

没有标签为“org.jenkinsci.plugins.workflow.cps.EnvActionImpl@79c0ce06”的节点

【问题讨论】:

  • 脚本块具有本地范围。这就是你不能在第二个脚本块上打印的原因
  • 不确定这是否属实,因为第一个“echo”命令工作正常并且它在脚本块之外。无论如何,在我的实际场景中, env.node_name 是 shell 脚本返回的结果。如果您知道任何解决方法 - 这会很有帮助。

标签: jenkins jenkins-declarative-pipeline


【解决方案1】:

我是这样做的:混合脚本化和声明性管道。首先,我使用脚本语法来查找例如我所在的分支。然后定义 AGENT_LABEL 变量。这个 var 可以在声明式管道的任何地方使用

def AGENT_LABEL = null

node('master') {
  stage('Checkout and set agent'){
     checkout scm
     ### Or just use any other approach to figure out agent label: read file, etc
     if (env.BRANCH_NAME == 'master') {
        AGENT_LABEL = "prod"
     } else {
        AGENT_LABEL = "dev"
     }
   }
}

pipeline {
    agent {
       label "${AGENT_LABEL}"
    }

    stages {
        stage('Normal build') {
           steps {
              echo "Running in ${AGENT_LABEL}"
              sh "hostname"
           }
        } 

        stage ("Docker build") {
           agent{
             dockerfile {
                dir 'Dockerfiles'
                label "${AGENT_LABEL}"
             }
            }
            steps{
                sh "hostname"
            }
        }
    }
}

【讨论】:

  • 不做字符串插值(label "${AGENT_LABEL}"),你可以直接写label AGENT_LABEL
  • 为什么在第一节中使用代理?只需在 agent 声明中使用闭包:pipeline { agent { label "${env.BRANCH_NAME.equalsIgnoreCase('master') ? 'prod' : 'dev'}" }
  • 我们是否可以使用您的方法定义 agent { "$CONTENT" } 而不是 label "${AGENT_LABEL}" 其中 CONTENT is calculated within the node/stage` 的内容?
【解决方案2】:

要查看其工作原理,请使用GString 对象执行println 并同时返回agentName 的变量。您可以从输出中看到,此行在任何其他管道代码之前进行了很好的评估。

agentName = "Windows"
agentLabel = "${println 'Right Now the Agent Name is ' + agentName; return agentName}"

pipeline {
    agent none

    stages {
        stage('Prep') {
            steps {
                script {
                    agentName = "Linux"
                }
            }
        }
        stage('Checking') {
            steps {
                script {
                    println agentLabel
                    println agentName
                }
            }
        }
        stage('Final') {
            agent { label agentLabel }

            steps {
                script {
                    println agentLabel
                    println agentName
                }
            }
    }

    }
}

控制台输出(注意我在这个实例上实际上并没有标记为 Windows 的节点,所以我在找不到它后中止了它):

Started by user Admin
[Pipeline] echo
Right Now the Agent Name is Windows
[Pipeline] stage
[Pipeline] { (Prep)
[Pipeline] script
[Pipeline] {
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Checking)
[Pipeline] script
[Pipeline] {
[Pipeline] echo
Windows
[Pipeline] echo
Linux
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Final)
[Pipeline] node
Still waiting to schedule task
There are no nodes with the label ‘Windows’
Aborted by Admin
[Pipeline] // node
[Pipeline] }
[Pipeline] // stage
[Pipeline] End of Pipeline
ERROR: Queue task was cancelled
Finished: ABORTED

注意Right Now the Agent Name is Windows 行是如何出现在输出的早期的。这解释了为什么您的值为 null。该语句在您的脚本修改变量之前很久就被评估。

以后我可能会尝试使用懒惰的GString 来获取变量。

agentLabel = "${-> println 'Right Now the Agent Name is ' + agentName; return agentName}"

不幸的是,这会引发错误,因为它需要一个字符串类型。显然,它可以将非惰性 GString 强制转换为 String ,但不能强制惰性版本。因此,当我强制强制转换为 String 时,它当然会在那个时候评估变量(这也是在管道代码实际运行之前)。

agent { label agentLabel as String }

您可以通过回退到旧的节点分配方法来解决问题:

agentName = "Windows"
agentLabel = "${-> println 'Right Now the Agent Name is ' + agentName; return agentName}"

pipeline {
    agent none

    stages {
        stage('Prep') {
            steps {
                script {
                    agentName = "Linux"
                }
            }
        }
        stage('Checking') {
            steps {
                script {
                    println agentLabel
                    println agentName
                }
            }
        }
        stage('Final') {

            steps {
                node( agentLabel as String ) {  // Evaluate the node label later
                    echo "TEST"
                }
                script {
                    println agentLabel
                    println agentName
                }
            }
        }
    }
}

您可以从此控制台输出中看到,它现在正确地找到了 Linux 节点并完成了管道。 agentName == Windows 时的早期评估永远不会发生:

Started by user Admin
[Pipeline] stage
[Pipeline] { (Prep)
[Pipeline] script
[Pipeline] {
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Checking)
[Pipeline] script
[Pipeline] {
[Pipeline] echo
Right Now the Agent Name is Linux
[Pipeline] echo
Linux
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Final)
[Pipeline] echo
Right Now the Agent Name is Linux
[Pipeline] node
Running on Slave 1 in /home/jenkinsslave/jenkins/workspace/test
[Pipeline] {
[Pipeline] echo
TEST
[Pipeline] }
[Pipeline] // node
[Pipeline] script
[Pipeline] {
[Pipeline] echo
Right Now the Agent Name is Linux
[Pipeline] echo
Linux
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] End of Pipeline
Finished: SUCCESS

如果没有懒惰的GString 并稍后键入强制,这可能会起作用,但我没有尝试过。

【讨论】:

  • 这是你想要的吗?
  • 有点……但我想知道这个解决方案是否有缺点:在“最终”阶段,节点代理确实是正确的,但是当我想在代理上运行 shell 脚本时,如果我在脚本块(节点块正下方的那个)中执行此操作,它将在主代理而不是从属(!)上运行。只有当我在节点块内运行它时,它才会在正确的从属设备上运行......这有什么缺点吗? “节点”块内的不同语法/功能..?
  • 我可以在节点块内添加一个脚本块,效果很好。
  • 不,完全没有缺点。 “节点”块是在声明性管道之前使用的语法。出于您的目的,这与在舞台上指定代理相同。不过,我确实注意到,您可能应该在前面的阶段放置一个代理,而在管道级别“无代理”。或者您可以在管道级别指定代理,并且“节点”仍应执行您想要的操作。但在我看来,如果您没有在所有阶段都使用相同的代理,那么为每个阶段指定一个代理会更简洁一些。但无论哪种方式,都不要在 master 上的享元代理上运行阶段。
【解决方案3】:

这可能与脚本块的上下文有关。

这行得通,在第二阶段使用“docker”标签:

def hotLabel = 'docker'

pipeline {
    agent { label 'master' }
    stages {
        stage('Stage1') {
            steps {
                echo "node_name: ${hotLabel}"
            }
        }

        stage('Stage2') {
            agent { label "${hotLabel}" }
            steps {
                echo "node_name: ${hotLabel}"
            }
        }
    }
}

这没有(得到相同的没有带有标签'null'错误的节点):

def hotLabel = null

pipeline {
    agent { label 'master' }
    stages {
        stage('Stage1') {
            steps {
                script {
                    hotLabel = "docker"
                }
            }
        }

        stage('Stage2') {
            agent { label "${hotLabel}" }
            steps {
                echo "node_name: ${hotLabel}"
            }
        }
    }
}

【讨论】:

  • 这个例子的问题是管道顶部的agent { label 'master' } 覆盖了任何其他代理声明。如果您想在管道中的任何点指定特定代理(或不指定代理),则必须从 agent none 开始,并在需要的每个阶段定义一个代理。
【解决方案4】:

我使用三元运算符让我的动态变化。

对于下文,如果 Jenkins 管道名称以“prod”结尾,则使用的标签为“myagent-prd”。否则,它只是“myagent”。

def ENVIRONMENT_NAME="${JOB_NAME}".tokenize('-').last().toLowerCase()

pipeline {
    agent {
      label "myagent${ENVIRONMENT_NAME == "prod" ? "-prd" : "" }"
    }

【讨论】:

    【解决方案5】:

    这对我有用:

    env.agentName = ""
    
    branch_name = "10.1.0"
    pipeline {
        agent none
    
        stages {
            stage('Prep') {
                steps {
                    script {
                        println branch_name
                        if ("${branch_name}" == "9.2.0") {
                            env.agentName = "9.2agent"
                        } else {
                            env.agentName = "10.1agent"
                        }
                    }
                }
            }
    
            stage('Finish') {
                steps {
                    node (agentName as String) { println env.agentName }
                    script {
                        println agentName
                    }
                }
            }
    
        }
    }
    
    Output:
    SuccessConsole Output
    Started by user build
    Running in Durability level: MAX_SURVIVABILITY
    [Pipeline] stage
    [Pipeline] { (Prep)
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    10.1.0
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Finish)
    [Pipeline] node
    Running on 10.1agent in /home/build/jenkins/workspace/testlabel
    [Pipeline] {
    [Pipeline] echo
    rbreg6
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    rbreg6
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] End of Pipeline
    Finished: SUCCESS
    
    Changing the branch name to 9.2.0:
    Started by user build
    Running in Durability level: MAX_SURVIVABILITY
    [Pipeline] stage
    [Pipeline] { (Prep)
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    9.2.0
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Finish)
    [Pipeline] node
    Running on 9.2agent in /shared/build/workspace/testlabel
    [Pipeline] {
    [Pipeline] echo
    rbregistry
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    rbregistry
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] End of Pipeline
    Finished: SUCCESS
    

    【讨论】:

    • 虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
    • 你是说你可以在舞台内定义代理?我得试试这个,有没有人尝试在脚本指令的循环中这样做?
    【解决方案6】:

    我希望工作流源于参数化作业以动态注入变量。我发现以下解决方案只需使用内联字符串操作就可以很好地工作:

    pipeline {
    
       agent { label 'LBL && '+nodeLabel }
       ...
    }
    

    【讨论】:

    • 这对我的需求来说不够动态 - 正如问题中所写:“在这个阶段结束时,我将知道第二阶段应该在哪个代理上运行”。所以我还不知道作业开始时的节点名称。
    【解决方案7】:

    对于我工作的管道作业,我需要解决一个类似的问题。我只想为整个管道设置一个代理! 我通过创建一个函数来解决这个问题,该函数将节点名作为字符串返回,我可以直接在管道中调用它。

    在我的例子中,Nodename 是 Jenkins 环境变量 $JOB_BASE_NAME 的一部分。 但是您可以使用 Jenkins 脚本块中允许的任何逻辑,我想这是一个很大的优势。

    // determine agent to run tests on
    def agent_selector() {
        if (env.JOB_BASE_NAME.contains('Nodename1')) {
            return "Nodename1"
        } else if (env.JOB_BASE_NAME.contains('Nodename2')) {
            return "Nodename2"
        } else {
            println("Could not get Agent from 'JOB_BASE_NAME' !")
            error('Aborting Build.')
        }
    }
    // start of pipeline
    pipeline {
        agent {label agent_selector()}
        stages {
            stage('Stagestuff') {
                steps {
                    echo "Hello World"
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多