Jenkins如何在多个标签之间选择pipeline agent?

How does Jenkins choose a pipeline agent between multiple labels?

我有一个 Jenkins 管道,我想 运行 在 参数指定的代理或 master 上。实现这个的管道代码是:

pipeline {
  agent { label "${params.agent} || master" }
  ...
}

我从以下帖子中推测,|| 运算符需要在(双)引号内:

https://serverfault.com/questions/1074089/how-to-apply-multiple-labels-to-jenkins-nodes

当我运行这份工作时,似乎总是运行在master
当我在 agent 语句中切换 ${params.agent}master 的顺序时,它似乎仍然总是 运行 on master.
如果我从 agent 语句中删除“ || master”,那么 params 指定代理上的作业 运行s。

问题: 我观察到 Jenkins“更喜欢”master 是巧合,还是语法有问题导致 Jenkins 默认为 master?
有没有办法让 Jenkins 更喜欢 not-master 以便我可以测试 agent 语句的有效性?

h pipeline/job 有一个受信任的代理列表,更多的工作在代理上成功,代理位于列表的顶部,pipeline/agent 将从列表中选择顶部代理。

如果你的pipeline已经运行在master上好几次都成功了,即使你给另一个agent选择,pipeline总是先选择最信任的agent

所以,当 Jenkins 遇到这条线时

  agent { label "${params.agent} || master" }

它将执行以下操作之一:

  1. 在与该标签匹配的节点之一上安排您的工作;或
  2. 卡住,直到有一个节点匹配该标签,或者直到中止。

关于选项 1,没有 gua运行tee 它会做一个 round-robin,一个 运行dom 选择,或者更喜欢一些节点而不是其他节点,等等. 实际上,当几个节点匹配时,Jenkins 会优先选择 运行 你过去的管道的节点。这是一个合理的行为——如果该节点上已经有一个工作区,一些操作(如 git checkout)可能会发生得更快,从而节省时间。

关于选项 2,这也是一种合理的行为。我们实际上使用它来安排 non-existing 标签上的作业,同时操纵标签以生成匹配的标签。

所以,您的语法没有任何问题,Jenkins 的行为符合设计。

如果你想实现一些自定义规则——比如“总是尝试不同的节点”,或者“尽可能少地使用 master”——你必须编写代码。

管道示例(注意我还没有检查过):

import hudson.model.Hudson

properties([
    parameters([
        string(name: 'DEPLOY_ON', defaultValue: 'node_name',
                description: 'try to run on this node, or master'),
    ])
])

resulting_node_name = ''

pipeline {
    agent { node { label 'master' } }
    stages {
        stage ('Do on master') {
            steps {
                script {
                    resulting_node_name = params.DEPLOY_ON
                    // note: this gets node by name, but you can get by label if you wish
                    def slave = Jenkins.instance.getNode(resulting_node_name)
                    
                    if (slave == null) {
                        currentBuild.result = 'FAILURE'
                    }

                    def computer = slave.computer
                    if (computer == null || computer.getChannel() == null || slave.name != params.DEPLOY_ON) {
                        println "Something wrong with the slave object, setting master"
                        resulting_node_name = 'master'
                    }

                    printSlaveInfo(slave)

                    computer = null
                    slave = null
                }
            }
        }

        stage('Do on actual node') {
            agent { node { label resulting_node_name } }
            steps {
                script {
                    println "Running on ${env.NODE_NAME}"
                }
            }

        }
    }
}

@NonCPS
def printSlaveInfo(slave) {
    // some info that you can use to choose the least-busy, best-equipped, etc.
    println('====================')
    println('Name: ' + slave.name)
    println('getLabelString: ' + slave.getLabelString())
    println('getNumExectutors: ' + slave.getNumExecutors())
    println('getRemoteFS: ' + slave.getRemoteFS())
    println('getMode: ' + slave.getMode())
    println('getRootPath: ' + slave.getRootPath())
    println('getDescriptor: ' + slave.getDescriptor())
    println('getComputer: ' + slave.getComputer())
    def computer = slave.computer
    println('\tcomputer.isAcceptingTasks: ' + computer.isAcceptingTasks())
    println('\tcomputer.isLaunchSupported: ' + computer.isLaunchSupported())
    println('\tcomputer.getConnectTime: ' + computer.getConnectTime())
    println('\tcomputer.getDemandStartMilliseconds: ' + computer.getDemandStartMilliseconds())
    println('\tcomputer.isOffline: ' + computer.isOffline())
    println('\tcomputer.countBusy: ' + computer.countBusy())
    println('\tcomputer.getLog: ' + computer.getLog())
    println('\tcomputer.getBuilds: ' + computer.getBuilds())
    println('====================')
}