고급 시스템 관리를 위한 Jenkins Groovy 스크립트 콘솔 마스터하기

Groovy 스크립트 콘솔을 사용하여 Jenkins 관리의 숨겨진 힘을 활용하세요. 이 포괄적인 가이드는 시스템 관리자가 대량 구성 업데이트, 즉시 에이전트 관리(연결 해제/재연결), 실행 중인 빌드 강제 중단 등 복잡한 작업을 즉시 수행할 수 있는 전문가 수준의 실행 가능한 Groovy 스크립트를 제공합니다. Jenkins 객체 모델과 직접 상호 작용하여 비교할 수 없는 효율성과 문제 해결 능력을 배우세요.

고급 시스템 관리를 위한 Jenkins Groovy 스크립트 콘솔 마스터하기

Jenkins Groovy 스크립트 콘솔은 UI에서 깔끔하게 처리할 수 없는 작업(예: 멈춘 빌드 찾기, 에이전트 상태 확인, 작업 구성 검사, 신중하게 범위를 지정한 대량 변경)에 유용합니다. 또한 이해하지 못하는 스크립트를 붙여넣으면 Jenkins 컨트롤러를 손상시키는 가장 쉬운 방법 중 하나이기도 합니다.

콘솔을 프로덕션 서버의 루트 액세스처럼 취급하세요. 먼저 읽고, 변경하려는 내용을 출력하고, 가능하면 프로덕션이 아닌 컨트롤러에서 테스트한 다음에만 쓰기를 수행하세요.


Jenkins 스크립트 콘솔 이해하기

Jenkins 스크립트 콘솔(Jenkins 관리 -> 스크립트 콘솔)은 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 버전: ${Jenkins.instance.version}"
println "사용자로 실행 중: ${Jenkins.instance.getAuthentication().getName()}"

최신 Jenkins 릴리스에서는 Jenkins.instance 대신 Jenkins.get()을 사용하는 예제를 볼 수 있습니다. 두 패턴 모두 실제 스크립트에 나타납니다. 새 스크립트의 경우 Jenkins.get()이 일반적으로 더 명확합니다:

import jenkins.model.Jenkins

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

실용적인 관리 스크립트

다음은 스크립트 콘솔을 통한 고급 관리 제어를 보여주는 몇 가지 실행 가능한 스크립트입니다.

1. 작업 구성 대량 업데이트

이 스크립트는 기존 Freestyle 작업을 반복하고 설명에 접미사를 추가합니다. null-safe 처리를 확인하세요. 많은 작업에 설명이 없습니다.

import hudson.model.FreeStyleProject

final String SUFFIX = " [자동 업데이트]"

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 "다음 작업에 대한 설명 업데이트됨: ${job.getName()}"
        count++
    }
}
println "\n완료. 총 업데이트된 작업 수: ${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, "관리자 스크립트에 의해 시작된 유지 관리.")
    println "에이전트 '${AGENT_NAME}'이(가) 일시적으로 오프라인으로 설정되었습니다."
} else {
    println "에이전트 '${AGENT_NAME}'을(를) 찾을 수 없습니다."
}

에이전트 강제 오프라인 전환 및 실행 중인 작업 연결 해제

에이전트를 즉시 중단해야 하는 경우 강제로 오프라인으로 전환하고 실행 중인 빌드를 연결 해제할 수 있습니다. 그러면 구성에 따라 빌드가 실패 또는 중단으로 표시됩니다.

import hudson.model.Computer

final String AGENT_NAME = "unresponsive-node-01"

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

if (agent) {
    // 강제 오프라인 전환 및 실행 중인 작업 즉시 연결 해제
    agent.doDoDisconnect()
    println "에이전트 '${AGENT_NAME}'이(가) 강제로 연결 해제되었습니다."
} else {
    println "에이전트 '${AGENT_NAME}'을(를) 찾을 수 없습니다."
}

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 "빌드 ${JOB_NAME}#${BUILD_NUMBER}이(가) 취소되었습니다."
} else {
    println "빌드 ${JOB_NAME}#${BUILD_NUMBER}이(가) 실행 중이 아니거나 존재하지 않습니다."
}

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 "오래된 빌드 삭제 중: ${build.getDisplayName()}"
            build.delete()
            deletedCount++
        }
    }
    println "\n정리 완료. ${TARGET_JOB}에 대해 ${deletedCount}개의 빌드가 삭제되었습니다."
} else {
    println "작업 '${TARGET_JOB}'을(를) 찾을 수 없거나 표준 작업 유형이 아닙니다."
}

콘솔 스크립팅 모범 사례

시스템 수준 변경을 수행할 때는 안정성을 유지하기 위해 다음 모범 사례를 준수하세요:

  1. .save() 사용: 구성 객체(예: 작업 또는 보기)를 수정할 때마다 Jenkins가 다시 시작된 후에도 변경 사항이 유지되도록 해당 객체에서 .save()반드시 호출해야 합니다. 구성은 저장될 때까지 메모리에만 보관됩니다.
  2. 객체 존재 확인: 작업 또는 에이전트 이름을 잘못 입력할 경우 콘솔이 충돌하는 것을 방지하기 위해 항상 API 호출을 확인(if (object) 또는 try-catch)으로 감싸세요.
  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 ? '업데이트 예정' : '업데이트 중'} ${job.fullName}"

    if (!DRY_RUN) {
        job.setDescription((job.getDescription() ?: "") + "\n정리 중 검토됨.")
        job.save()
    }
}

DRY_RUN = true로 한 번 실행하고, 출력을 변경 티켓에 복사한 다음에만 false로 실행하세요. 이 작은 습관은 대부분의 우발적인 광범위한 변경을 방지합니다.

변경하지 않고 작업 구성 읽기

때로는 콘솔을 검색 도구로 사용하는 것이 가장 좋습니다. 예를 들어, 여전히 이전 Git 호스트를 참조하는 Pipeline 작업을 찾으려면:

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 "${job.fullName}에서 ${NEEDLE} 발견"
    }
}

이 예제는 인라인 Pipeline 스크립트에만 작동합니다. 작업이 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}"
        }
    }
}

먼저 검사 스크립트로 사용하세요. 무언가를 중단해야 하는 경우 오래된 것으로 보이는 모든 것을 취소하는 대신 알려진 하나의 빌드를 대상으로 하세요. 장기 실행 데이터베이스 마이그레이션, 릴리스 작업 및 수동 승인 파이프라인은 외부에서 "멈춘" 것처럼 보일 수 있습니다.

Pipeline 작업의 경우 빌드가 입력을 위해 일시 중지되었는지도 검사할 수 있습니다:

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 "입력 대기 중: ${run.fullDisplayName}"
        }
    }
}

이렇게 하면 의도적으로 승인을 기다리는 배포를 중단하는 일반적인 실수를 방지할 수 있습니다.

플러그인 및 버전 검사

UI가 느리거나 빠른 인벤토리가 필요한 경우 콘솔이 유용합니다. 설치된 플러그인과 버전을 출력합니다:

import jenkins.model.Jenkins

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

스크립트 콘솔을 관리형 플러그인 업데이트 프로세스의 대체품으로 사용하지 마세요. 플러그인 업그레이드는 작업 로딩, Pipeline 동작, 자격 증명 바인딩 및 에이전트 연결에 영향을 줄 수 있습니다. 콘솔은 검사, 긴급 진단 또는 소규모 대상 수리에 가장 적합합니다.

쓰기 스크립트 전에 백업

.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 "컨트롤러: ${j.getRootUrl()}"
println "사용자: ${j.getAuthentication().getName()}"

콘솔에 넣지 말아야 할 것

오랫동안 대기하거나, 영원히 폴링하거나, 대용량 원격 파일을 다운로드하거나, 광범위한 파일 시스템 삭제를 수행하는 스크립트는 피하세요. 콘솔은 컨트롤러 프로세스 내에서 실행됩니다. 스크립트가 CPU를 소모하거나, 스레드를 차단하거나, 메모리를 채우면 CI 시스템 자체에 영향을 미칩니다.

또한 비밀을 출력하지 마세요. Jenkins 자격 증명 객체는 의도적으로 보호되지만, 관리자는 여전히 민감한 자료를 노출하는 스크립트를 작성할 수 있습니다. 자격 증명을 감사해야 하는 경우 ID, 설명, 도메인 및 사용 참조를 출력하세요. 브라우저, 빌드 로그 또는 채팅에 비밀 값을 출력하지 마세요.

가장 좋은 콘솔 스크립트는 짧고, 지루하며, 되돌릴 수 있습니다. 상태를 검사하고, 좁은 수리를 수행하거나, 알려진 관리 작업을 자동화하는 데 사용하세요. 스크립트가 테스트가 필요할 정도로 길어지면 일반 코드처럼 검토할 수 있는 공유 관리 저장소 또는 Jenkins 관리 작업으로 이동하세요.