在Jenkins声明式流水线中,我能否动态设置代理标签?

46

有没有一种方法可以动态设置代理标签,而不是作为纯字符串?

该作业有两个阶段:

  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”的节点


脚本块具有局部作用域。这就是为什么你无法在第二个脚本块中打印它的原因。 - underscore
不确定这是正确的,因为第一个“echo”命令正常工作且在脚本块之外。 无论如何,在我的实际场景中,env.node_name是由一个shell脚本返回的结果。 如果您知道任何解决方法-那将会很有帮助。 - Gilad Shahrabani
8个回答

46

以下是我的制作方法:混合使用脚本化和声明式管道。首先,我使用脚本语法查找当前分支,然后定义AGENT_LABEL变量。这个变量可以在整个声明式管道中任何地方使用。

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"
            }
        }
    }
}

10
不必使用字符串插值(label "${AGENT_LABEL}"),直接写label AGENT_LABEL即可。 - aaronk6
2
为什么在第一段中要使用代理?只需在“agent”声明中使用闭包:pipeline { agent { label "${env.BRANCH_NAME.equalsIgnoreCase('master') ? 'prod' : 'dev'}" } - Max Cascone
我们能否使用您的方法来定义 agent { "$CONTENT" } 的内容,而不是 label "${AGENT_LABEL}",其中 CONTENT 是在 node/stage 中计算出来的? - Charles Moulliard

22
为了看到这是如何工作的,使用一个 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的此实例上安装node,所以在它找不到之后我中止了操作):

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,它出现得非常早。这解释了为什么您的变量值为空。该语句在脚本修改变量之前很长时间就已经被评估了。

我可以尝试使用延迟加载的 GString 以稍后获取变量。

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

很不幸,这会引发错误,因为它期望的是一个字符串类型。显然,它可以自行将非惰性 GString 强制转换为字符串,但不能转换惰性版本。所以当我强制进行字符串转换时,它当然会在那个时候求值该变量(这再次发生在管道代码实际运行之前)。

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和类型转换,这可能也可以工作,但我没有尝试过。


这个做到了你想要的吗? - Rob Hales
有点像,但我想知道这种解决方案是否有缺点: 在“Final”阶段,节点代理确实是正确的, 但是当我想在代理上运行shell脚本时,如果我在脚本块中执行它(就在节点块下面那个),它将在主代理上而不是从属代理上运行! 只有在节点块内部运行它时,它才会在正确的从属代理上运行... 这有什么缺点吗? 在“节点”块内部具有不同的语法/功能..? - Gilad Shahrabani
1
我可以在节点块内添加脚本块,这样做非常有效。 - Gilad Shahrabani
没有,完全没有任何缺点。"node"块是在声明式流水线之前使用的语法。对于您在此处的目的,这与在阶段上指定代理相同。不过我刚刚注意到,您应该在之前的阶段上放置一个代理,并在管道级别上使用“代理无”。或者您可以在管道级别上指定代理,“节点”仍然可以实现您想要的效果。但在我看来,如果您不在所有阶段上使用相同的代理,则每个阶段指定一个代理会更加清晰。但无论哪种方式,都不要在主机上的轻量级代理上运行阶段。 - Rob Hales

4

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

使用第二阶段中的标签“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}"
            }
        }
    }
}

1
这个例子的问题在于管道顶部的 agent { label 'master' } 会覆盖任何其他代理声明。如果您想在管道的任何时候指定特定的代理(或不使用代理),则必须从 agent none 开始,并在每个需要代理的阶段定义一个代理。 - Max Cascone

4

我使用三元运算符使我的内容动态更改。

对于下面的内容,如果Jenkins流水线名称以“prod”结尾,则使用的标签为“myagent-prd”。否则,只是“myagent”。

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

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

2
这对我有效:
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

虽然这段代码片段可能解决了问题,但是包括解释真的有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而这些人可能不知道您的代码建议的原因。 - awh112
你是说你可以在舞台内定义代理吗?我得去试一下,有人尝试过在脚本指令的循环中做这个吗? - Max Cascone

2
我希望工作流程从参数化作业开始,动态注入变量。我发现以下解决方案使用内联字符串操作非常有效:

String Parameter

pipeline {

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

1
这对我的需求来说不够动态 - 如问题所述:“在此阶段结束时,我将知道第二阶段应该在哪个代理上运行。”因此,当作业启动时,我还不知道节点名称。 - Gilad Shahrabani

0
对于我的工作管道任务,我需要解决类似的问题。我只想为整个管道设置一个代理! 我通过创建一个返回节点名称(Nodename)字符串的函数来解决这个问题,这样我可以直接在管道中调用它。
在我的情况下,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"
            }
        }
    }
}

0
有没有一种方法可以动态设置代理标签,而不是作为纯字符串?
因此,对于那些想立即使用相关代理而无需在Jenkins控制器上运行任何内容,并且仍然能够传递动态代理标签并保持其尽可能清晰的人们来说:
pipeline {
    agent {
        label env.JENKINS_AGENT_LABEL ?: 'pod-agent'
    }
    ...

在我的情况下,我使用环境变量JENKINS_AGENT_LABEL(您可以在此处使用任何内容),如果env.JENKINS_AGENT_LABEL等于null,那么它将使用我的默认代理标签(在我的情况下为'pod-agent')。 但是,如果env.JENKINS_AGENT_LABEL不等于null,那么Jenkins将使用从env.JENKINS_AGENT_LABEL获取的值的代理label

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接