掌握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单例对象,代表正在运行的控制器。Hudson:Jenkins的别名。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."
}
控制台脚本的最佳实践
在执行系统级更改时,请遵循以下最佳实践以保持稳定性:
- 使用
.save(): 每当你修改配置对象(如作业或视图)时,必须在该对象上调用.save(),以便更改在Jenkins重启后持久化。配置仅在内存中保存,直到保存。 - 检查对象是否存在: 始终使用检查(
if (object)或try-catch)包装API调用,以防止因拼错作业或代理名称而导致控制台崩溃。 - 避免持久循环: 脚本同步运行。除非你确定它们会快速完成,否则不要直接在控制台中执行长时间运行的循环或进程,因为这会阻塞控制台UI。
- 利用内置方法: Jenkins Groovy对象通常有特定的辅助方法(如
doCancel()或doDoDisconnect())。尽可能使用这些方法,而不是手动操作内部状态。 - 使用静默模式(如果适用): 当执行生成过多构建状态更新的批量操作时,考虑是否暂时禁用事件通知功能,但这通常需要比标准管理更深的系统访问权限。
更安全的试运行模式
对于任何批量更改,首先添加一个试运行标志:
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管理作业中,在那里它可以像普通代码一样被审查。