掌握Jenkins Groovy脚本控制台:高级系统管理指南

解锁Jenkins管理的隐藏力量,使用Groovy脚本控制台。本综合指南为系统管理员提供了专家级、可操作的Groovy脚本,用于即时执行复杂任务,如批量配置更新、即时代理管理(断开/重连)以及强制中止正在运行的构建。学习如何直接与Jenkins对象模型交互,实现无与伦比的效率和故障排除能力。

掌握Jenkins Groovy脚本控制台:高级系统管理指南

Jenkins Groovy脚本控制台对于无法通过UI干净完成的任务非常有用:查找卡住的构建、检查代理状态、检查作业配置或进行精心限定的批量更改。它也是如果你粘贴一个你不理解的脚本,最容易损坏Jenkins控制器的方法之一。

将控制台视为生产服务器上的root访问权限。先阅读,打印你要更改的内容,尽可能在非生产控制器上测试,然后才写入。


理解Jenkins脚本控制台

Jenkins脚本控制台(Manage Jenkins -> Script Console)使用Groovy直接访问正在运行的Jenkins控制器的对象模型。你可以检查作业、构建、节点、视图、凭据元数据、插件状态以及许多其他运行时对象。

为什么要使用脚本控制台?

  • 即时执行: 立即运行脚本,无需等待作业触发或流水线启动。
  • 系统调试: 访问GUI未公开的内部状态、日志和配置细节。
  • 批量操作: 快速修改多个作业、重新配置代理或清除整个实例中的旧数据。
  • 原型脚本: 在将Groovy逻辑嵌入共享库或声明式流水线之前进行测试。

安全预防措施:直接访问的力量

警告: 在控制台中执行的脚本在Jenkins主节点上具有完全管理权限。编写不当的脚本可能会损坏配置、删除构建或导致Jenkins实例崩溃。始终先在非生产环境中彻底测试复杂脚本。


基本Groovy对象和API访问

控制台的力量来自于直接访问核心Jenkins对象。这些对象在Groovy执行环境中隐式可用:

  • Jenkins.instance:核心Jenkins单例对象,代表正在运行的控制器。
  • HudsonJenkins的别名。
  • Jenkins.instance.getItemByFullName('JobName'):访问特定作业。
  • Jenkins.instance.getComputer('AgentName'):访问特定代理(节点)。

访问Jenkins实例

要验证你是否具有访问权限,最简单的命令是打印Jenkins版本:

println "Jenkins Version: ${Jenkins.instance.version}"
println "Running as user: ${Jenkins.instance.getAuthentication().getName()}"

在当前的Jenkins版本中,你可能会看到使用Jenkins.get()而不是Jenkins.instance的示例。两种模式都出现在实际脚本中。对于新脚本,Jenkins.get()通常更清晰:

import jenkins.model.Jenkins

def jenkins = Jenkins.get()
println "Root URL: ${jenkins.getRootUrl()}"

实用的管理脚本

以下是几个可操作的脚本,展示了通过脚本控制台进行高级管理控制。

1. 批量更新作业配置

此脚本遍历现有的自由风格作业,并向描述添加后缀。注意空安全处理;许多作业没有描述。

import hudson.model.FreeStyleProject

final String SUFFIX = " [Automated Update]"

def count = 0

Jenkins.instance.getAllItems(FreeStyleProject.class).each { job ->
    def current = job.getDescription() ?: ""
    if (!current.endsWith(SUFFIX)) {
        job.setDescription(current + SUFFIX)
        job.save()
        println "Updated description for: ${job.getName()}"
        count++
    }
}
println "\nFinished. Total jobs updated: ${count}"

2. 管理Jenkins代理(节点)

管理员经常需要将代理下线进行维护,或手动断开行为异常的节点。

临时断开代理连接

此脚本断开代理连接,阻止新构建在其上启动,但允许正在运行的构建完成。

import hudson.model.Computer

final String AGENT_NAME = "my-specific-agent"

def agent = Jenkins.get().getComputer(AGENT_NAME)

if (agent) {
    // 设置为临时离线
    agent.setTemporarilyOffline(true, "Maintenance started by Admin Script.")
    println "Agent '${AGENT_NAME}' set to temporarily offline."
} else {
    println "Agent '${AGENT_NAME}' not found."
}

强制代理离线并断开正在运行的任务

如果必须立即关闭代理,你可以强制其离线并断开任何正在运行的构建,这将根据配置将其标记为失败或中止。

import hudson.model.Computer

final String AGENT_NAME = "unresponsive-node-01"

def agent = Jenkins.get().getComputer(AGENT_NAME)

if (agent) {
    // 强制离线并立即断开正在运行的任务
    agent.doDoDisconnect()
    println "Agent '${AGENT_NAME}' forcefully disconnected."
} else {
    println "Agent '${AGENT_NAME}' not found."
}

3. 操作正在运行的构建

当关键构建卡住或需要立即取消时,脚本控制台提供了最快的路径。

中止特定正在运行的构建

要中止由完整路径标识的构建(例如,PipelineJob/BuildNumber):

// 示例:中止名为'CriticalDeploy'的作业的#5构建
final String JOB_NAME = "CriticalDeploy"
final int BUILD_NUMBER = 5

def job = Jenkins.get().getItemByFullName(JOB_NAME)

def build = job?.getBuild(BUILD_NUMBER)

if (build && build.isBuilding()) {
    build.doCancel()
    println "Build ${JOB_NAME}#${BUILD_NUMBER} has been cancelled."
} else {
    println "Build ${JOB_NAME}#${BUILD_NUMBER} is not running or does not exist."
}

4. 清理旧构建记录

管理磁盘空间通常需要积极修剪旧构建。此脚本识别并删除指定作业中所有超过30天的构建。

import hudson.model.Job
import java.util.concurrent.TimeUnit

final String TARGET_JOB = "LegacyArchivingJob"
final int DAYS_TO_KEEP = 30

def job = Jenkins.get().getItemByFullName(TARGET_JOB)

if (job instanceof Job) {
    long cutoffTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(DAYS_TO_KEEP)
    int deletedCount = 0

    job.getBuilds().each { build ->
        if (build.getTimeInMillis() < cutoffTime) {
            println "Deleting old build: ${build.getDisplayName()}"
            build.delete()
            deletedCount++
        }
    }
    println "\nCleanup complete. Deleted ${deletedCount} builds for ${TARGET_JOB}."
} else {
    println "Job '${TARGET_JOB}' not found or is not a standard Job type."
}

控制台脚本的最佳实践

在执行系统级更改时,请遵循以下最佳实践以保持稳定性:

  1. 使用.save() 每当你修改配置对象(如作业或视图)时,必须在该对象上调用.save(),以便更改在Jenkins重启后持久化。配置仅在内存中保存,直到保存。
  2. 检查对象是否存在: 始终使用检查(if (object)try-catch)包装API调用,以防止因拼错作业或代理名称而导致控制台崩溃。
  3. 避免持久循环: 脚本同步运行。除非你确定它们会快速完成,否则不要直接在控制台中执行长时间运行的循环或进程,因为这会阻塞控制台UI。
  4. 利用内置方法: Jenkins Groovy对象通常有特定的辅助方法(如doCancel()doDoDisconnect())。尽可能使用这些方法,而不是手动操作内部状态。
  5. 使用静默模式(如果适用): 当执行生成过多构建状态更新的批量操作时,考虑是否暂时禁用事件通知功能,但这通常需要比标准管理更深的系统访问权限。

更安全的试运行模式

对于任何批量更改,首先添加一个试运行标志:

import jenkins.model.Jenkins
import hudson.model.Job

final boolean DRY_RUN = true
final String MATCH = "legacy-"

Jenkins.get().getAllItems(Job.class).findAll { job ->
    job.fullName.contains(MATCH)
}.each { job ->
    println "${DRY_RUN ? 'Would update' : 'Updating'} ${job.fullName}"

    if (!DRY_RUN) {
        job.setDescription((job.getDescription() ?: "") + "\nReviewed during cleanup.")
        job.save()
    }
}

先用DRY_RUN = true运行一次,将输出复制到你的变更工单中,然后才用false运行。这个小习惯可以防止大多数意外的广泛更改。

读取作业配置而不更改它

有时控制台最适合用作搜索工具。例如,查找仍然引用旧Git主机的流水线作业:

import jenkins.model.Jenkins
import org.jenkinsci.plugins.workflow.job.WorkflowJob

final String NEEDLE = "git.old.example.com"

Jenkins.get().getAllItems(WorkflowJob.class).each { job ->
    def definition = job.getDefinition()
    def text = definition?.getScript()
    if (text?.contains(NEEDLE)) {
        println "Found ${NEEDLE} in ${job.fullName}"
    }
}

此示例仅适用于内联流水线脚本。如果作业使用来自SCM的Jenkinsfile,Jenkins存储的是SCM定义而不是文件内容。这个区别很重要:控制台可以检查Jenkins配置,但不能神奇地读取每个远程仓库的每个分支,除非你的脚本明确这样做。

无需猜测即可找到卡住的构建

在事件期间,第一个问题通常是“现在正在运行什么?”此脚本打印正在运行的构建及其持续时间和执行器:

import jenkins.model.Jenkins

Jenkins.get().getComputers().each { computer ->
    computer.executors.each { executor ->
        def executable = executor.currentExecutable
        if (executable) {
            def build = executable
            println "${computer.displayName} :: ${build.fullDisplayName} :: ${build.durationString}"
        }
    }
}

首先将其用作检查脚本。如果你需要中止某些内容,请针对一个已知的构建,而不是取消所有看起来旧的构建。长时间运行的数据库迁移、发布作业和手动审批流水线从外部看起来可能“卡住”。

对于流水线作业,你还可以检查构建是否因输入而暂停:

import jenkins.model.Jenkins
import org.jenkinsci.plugins.workflow.job.WorkflowRun
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction

Jenkins.get().getAllItems().each { job ->
    job.builds?.findAll { it instanceof WorkflowRun && it.isBuilding() }?.each { run ->
        def input = run.getAction(InputAction)
        if (input) {
            println "Waiting for input: ${run.fullDisplayName}"
        }
    }
}

这可以防止一个常见错误:中止一个有意等待审批的部署。

插件和版本检查

当UI缓慢或你需要快速清单时,控制台很方便。此打印已安装的插件和版本:

import jenkins.model.Jenkins

Jenkins.get().pluginManager.plugins
    .sort { it.shortName }
    .each { plugin ->
        println "${plugin.shortName}:${plugin.version}"
    }

不要将脚本控制台用作托管插件更新过程的替代品。插件升级可能会影响作业加载、流水线行为、凭据绑定和代理连接。控制台最适合检查、紧急诊断或小的针对性修复。

在编写脚本之前备份

在运行任何调用.save()、删除构建、断开代理或更改作业定义的脚本之前,请确保你有$JENKINS_HOME或控制器托管配置源的当前备份。如果你的Jenkins实例由JCasC、Job DSL、Helm值或其他Git支持的配置,请记住控制台编辑可能会被下一次协调覆盖。

在这些环境中,使用控制台确认问题,然后修复真相来源。仅控制台修复对于紧急情况是可以接受的,但请记录它,以便之后可以更新持久配置。

远程脚本控制台访问

许多管理员知道浏览器控制台,但Jenkins也可以通过CLI运行Groovy(如果该访问已启用并允许):

java -jar jenkins-cli.jar -s https://jenkins.example.com/ groovy = < script.groovy

这对于经过审查的脚本很有用,因为你可以将Groovy文件保存在版本控制中,通过同行评审运行它,并执行已批准的精确内容。它还使输出更容易捕获到事件工单中。

不要随意启用CLI或远程脚本执行。脚本控制台访问所需的权限是高度特权的。将其限制为受信任的管理员,在可用时使用审计日志记录,并优先使用短期的管理会话。如果你的组织使用基于角色的访问控制,请在假设控制台已锁定之前验证谁实际拥有Overall/Administer或等效权限。

对于可重复的维护,运行经过审查脚本的Jenkins作业通常比临时的浏览器控制台工作更好。控制台仍然是紧急工具;版本控制的自动化应处理你预期重复的任务。

在运行远程脚本之前,在输出顶部打印Jenkins URL和当前身份验证名称。这听起来很基础,但它可以捕获最严重的错误:针对错误的控制器或在错误的账户下运行生产修复。

import jenkins.model.Jenkins

def j = Jenkins.get()
println "Controller: ${j.getRootUrl()}"
println "User: ${j.getAuthentication().getName()}"

不应放入控制台的内容

避免长时间休眠、永远轮询、下载大型远程文件或执行广泛文件系统删除的脚本。控制台在控制器进程内运行。如果脚本消耗CPU、阻塞线程或填满内存,它会影响CI系统本身。

还要避免打印秘密。Jenkins凭据对象是故意保护的,但管理员仍然可以编写暴露敏感材料的脚本。如果你需要审计凭据,请打印ID、描述、域和使用引用。不要将秘密值打印到浏览器、构建日志或聊天中。

最好的控制台脚本是简短、无聊且可逆的。使用它们检查状态、执行狭窄的修复或自动化已知的管理任务。当脚本变得足够长以至于需要测试时,将其移动到共享管理仓库或Jenkins管理作业中,在那里它可以像普通代码一样被审查。