Jenkins는 설정과 스케줄링을 맡는 컨트롤러와 실제 빌드를 실행하는 에이전트로 나뉩니다. 빌드 절차는 저장소의 Jenkinsfile에 Declarative Pipeline으로 적고, Multibranch Pipeline이 브랜치와 PR마다 이 파일을 읽어 잡을 만듭니다. 이 글은 이 구조와 주요 문법을 공식 문서 기준으로 정리하고, 직접 띄운 Jenkins 2.568.3에서 빌드를 컨트롤러에서 돌리면 무엇이 위험한지 확인합니다.
1. Jenkins 구성 요소
시리즈 첫 글인 [CI/CD] CI/CD와 GitOps 개념 정리에서 나눈 흐름 중 Jenkins는 CI를 맡습니다. GitHub Webhook을 받아 코드를 빌드하고 테스트한 뒤 이미지를 만들어 레지스트리에 올리고, 매니페스트 저장소의 이미지 태그를 바꿔 커밋하는 부분까지입니다. Jenkins는 jenkins.war 하나로 실행되는 Java 애플리케이션이고, 코어(core)는 웹 UI, 설정, 잡 실행 같은 기본 기능만 제공합니다. Git 연동, Pipeline, Credentials 같은 기능은 대부분 플러그인이 더합니다.
| 용어 | 의미 |
| 컨트롤러 | Controller, 설정 저장, 플러그인 로드, UI 제공, 빌드를 어디서 돌릴지 정하는 중심 프로세스 |
| 노드 | Node, 빌드를 실행할 수 있는 머신, 컨트롤러 안의 built-in 노드와 에이전트가 모두 노드 |
| 에이전트 | Agent, 컨트롤러에 연결되어 지시받은 작업을 실행하는 머신이나 컨테이너 |
| 실행기 | Executor, 노드에서 작업 하나를 실행하는 슬롯, 노드의 실행기 수만큼 작업이 동시에 실행됨 |
| 라벨 | Label, linux, docker처럼 노드를 묶는 이름, 잡은 라벨로 실행할 노드를 고름 |
| 잡과 빌드 | Job은 Jenkins가 할 일의 정의, Build는 잡을 한 번 실행한 결과 |
예전 문서의 master는 지금의 controller입니다. Jenkins 용어집도 master를 폐기된 용어로 설명합니다. 다만 설정 XML과 API에는 slave 같은 옛 이름이 아직 남아 있습니다.
2. 컨트롤러와 에이전트
컨트롤러는 웹 UI와 API로 요청을 받고, 빌드 요청을 큐(Build Queue)에 넣었다가 라벨이 맞는 노드의 빈 실행기에 배정합니다. 잡 설정, 빌드 기록과 로그, Credentials는 모두 컨트롤러의 JENKINS_HOME 디렉터리에 저장됩니다.

컨트롤러는 배정과 기록을 맡고, 빌드는 에이전트에서 실행합니다
에이전트를 붙이는 방법은 컨트롤러가 SSH로 접속해 에이전트를 띄우는 방식(SSH Build Agents 플러그인)과, 에이전트가 먼저 컨트롤러로 접속하는 inbound 방식(TCP 포트 또는 WebSocket)이 있습니다. Kubernetes 같은 클라우드(Cloud)로 필요할 때 에이전트를 만들 수도 있습니다. 저는 inbound라면 별도 에이전트 포트 없이 컨트롤러의 HTTP(S) 주소로 연결되는 WebSocket을 고릅니다(앞단 프록시가 WebSocket을 통과시켜야 함). 실행기 수는 Jenkins 문서 기준으로 노드당 하나가 가장 안전하고, 작업이 작다면 CPU 코어당 하나도 괜찮습니다.
빌드를 컨트롤러에서 돌리지 않는 이유
Jenkins 문서(Controller Isolation)는 빌드를 built-in 노드가 아닌 다른 노드에서 실행하라고 권합니다. 빌드에서 무엇이 실행될지는 잡 설정 권한자, 빌드 스크립트 작성자, 테스트 코드 작성자처럼 관리자보다 덜 신뢰하는 사람들이 정하는 경우가 많은데, built-in 노드의 빌드는 Jenkins 프로세스와 같은 권한으로 컨트롤러 파일 시스템에 접근하기 때문입니다. 그래서 built-in 노드의 실행기 수를 0으로 두라고 안내합니다. Architecting for Scale 문서도 빌드를 별도 노드로 옮기면 컨트롤러의 자원이 남아 스케줄링 성능이 좋아진다고 설명합니다.
헷갈리기 쉬운 점이 하나 있습니다. Pipeline의 sh 스텝은 에이전트에서 돌지만, Pipeline의 Groovy 코드 자체는 항상 컨트롤러에서 실행됩니다. Jenkins의 Pipeline 모범 사례 문서도 JsonSlurper로 파일을 파싱하거나 httpRequest로 외부 데이터를 받는 대신, sh 스텝으로 에이전트에서 처리하라고 권합니다. 저는 Jenkinsfile에는 흐름만 두고 실제 작업은 셸 스크립트나 Makefile로 넘깁니다.
3. 실습: built-in 노드와 에이전트 비교
GitHub 릴리스에서 받은 Jenkins 2.568.3 LTS WAR를 OpenJDK 21로 실행했습니다. 실습 환경에서는 플러그인 업데이트 센터에 접속할 수 없어 플러그인을 설치하지 못했고, Pipeline도 플러그인이라 코어에 있는 Freestyle 잡과 Jenkins CLI로 노드 동작만 확인했습니다. 문서의 권고대로 컨트롤러(jenkins)와 에이전트(jenkins-agent)는 서로 다른 OS 사용자로 띄웠습니다. 설치 마법사를 건너뛰면 인증이 꺼진 채 시작하므로 127.0.0.1에만 열었고, 실제 서버에서는 이렇게 띄우면 안 됩니다.
3.1 컨트롤러 실행
# 컨트롤러와 에이전트를 서로 다른 OS 사용자로 실행
useradd -r -M -s /usr/sbin/nologin jenkins
useradd -r -M -s /usr/sbin/nologin jenkins-agent
install -d -o jenkins -m 700 /srv/jenkins-lab/controller # JENKINS_HOME
install -d -o jenkins-agent -m 700 /srv/jenkins-lab/agent # 에이전트 작업 공간
install -d -o jenkins -m 755 /srv/jenkins-lab/logs
install -o jenkins -m 600 jenkins-url.xml \
controller/jenkins.model.JenkinsLocationConfiguration.xml # CLI에 필요한 URL
java -jar jenkins.war --version
# 실습용: 설치 마법사를 건너뛰고 127.0.0.1에서만 받음(인증 없음)
runuser -u jenkins -- env JENKINS_HOME=/srv/jenkins-lab/controller \
java -Djenkins.install.runSetupWizard=false -jar jenkins.war \
--httpListenAddress=127.0.0.1 --httpPort=8080 \
> logs/controller.log 2>&1 &
until grep -q "fully up and running" logs/controller.log; do sleep 2; done
grep -o "Jenkins is fully up and running" logs/controller.log
ls -C -T 0 -w 84 controller
echo "설치된 플러그인 수: $(ls controller/plugins | wc -l)"
2.568.3
Jenkins is fully up and running
config.xml nodeMonitors.xml
hudson.model.UpdateCenter.xml plugins
jenkins.install.InstallUtil.lastExecVersion secret.key
jenkins.install.UpgradeWizard.state secret.key.not-so-secret
jenkins.model.JenkinsLocationConfiguration.xml secrets
jenkins.telemetry.Correlator.xml userContent
jobs war
설치된 플러그인 수: 0
JENKINS_HOME에 설정(config.xml), 잡(jobs), 암호화 키(secrets)가 모두 있으므로 백업 대상도 이 디렉터리입니다. Jenkins 백업 문서는 secrets의 master.key를 일반 백업에 넣지 말고 따로 보관하라고 권합니다. 이 키와 백업을 함께 가진 사람은 모든 정보에 접근할 수 있기 때문입니다.
3.2 built-in 노드에서 빌드
설치 직후 노드 목록을 보고, built-in 노드에서만 도는 Freestyle 잡(peek)으로 컨트롤러의 secrets 디렉터리를 읽어 보았습니다.
J=http://127.0.0.1:8080
CLI="java -jar jenkins-cli.jar -s $J/"
# 설치 직후 노드: 컨트롤러 안의 Built-In Node 하나
curl -sg "$J/computer/api/json?tree=computer[displayName,numExecutors]" \
| jq -c '.computer[] | del(._class)'
# built-in 노드에서만 도는 Freestyle 잡을 만들어 실행
$CLI create-job peek < jobs/peek.xml
$CLI build peek -s -v
{"displayName":"Built-In Node","numExecutors":2}
Started peek #1
Started from command line by anonymous
Running as SYSTEM
Building in workspace /srv/jenkins-lab/controller/workspace/peek
[peek] $ /bin/sh -xe /tmp/jenkins7740749793331372254.sh
+ id -un
jenkins
+ ls /srv/jenkins-lab/controller/secrets
hudson.console.ConsoleNote.MAC
hudson.model.Job.serverCookie
hudson.model.User.DIRNAMES
jenkins.model.Jenkins.crumbSalt
jenkins.security.csp.ReportingContext.key
master.key
Finished: SUCCESS
Completed peek #1 : SUCCESS
설치 직후 built-in 노드에는 실행기가 2개 있어서 에이전트 없이도 빌드가 컨트롤러에서 돕니다. 이 빌드는 jenkins 사용자로 실행되어 master.key가 든 secrets 디렉터리를 그대로 읽었습니다. 잡 설정이나 저장소의 빌드 스크립트를 바꿀 수 있는 사람이면 누구나 같은 일을 할 수 있습니다.
3.3 에이전트 연결 후 빌드
built-in 노드의 실행기를 0으로 바꾸고 에이전트 노드 agent-1을 등록했습니다. 노드 설정은 라벨 linux, 실행기 1개, 이 라벨을 지정한 잡만 받는 EXCLUSIVE 모드, WebSocket inbound 연결입니다.
J=http://127.0.0.1:8080
CLI="java -jar jenkins-cli.jar -s $J/"
# 1) built-in 노드의 실행기를 0으로
echo 'jenkins.model.Jenkins.get().setNumExecutors(0)' | $CLI groovy =
# 2) 에이전트 노드 등록(label linux, EXCLUSIVE 모드)
$CLI create-node agent-1 < nodes/agent-1.xml
# 3) 연결 비밀 값을 에이전트 사용자만 읽는 파일로 저장(출력하지 않음)
GET='println(jenkins.model.Jenkins.get().getComputer("agent-1").jnlpMac)'
echo "$GET" | $CLI groovy = | tr -d '\n' \
| install -o jenkins-agent -m 600 /dev/stdin agent/.secret
# 4) 다른 OS 사용자로 에이전트 실행, WebSocket으로 컨트롤러에 연결
runuser -u jenkins-agent -- java -jar agent.jar -url $J/ \
-name agent-1 -secret @/srv/jenkins-lab/agent/.secret -webSocket \
-workDir /srv/jenkins-lab/agent > logs/agent.log 2>&1 &
for i in $(seq 60); do grep -q "INFO: Connected" logs/agent.log && break; sleep 1; done
grep -E "INFO: (WebSocket connection open|Connected)" logs/agent.log
sleep 3
curl -sg "$J/computer/api/json?tree=computer[displayName,numExecutors,offline]" \
| jq -c '.computer[] | del(._class)'
INFO: WebSocket connection open
INFO: Connected
{"displayName":"Built-In Node","numExecutors":0,"offline":false}
{"displayName":"agent-1","numExecutors":1,"offline":false}
연결 비밀값은 다른 사용자가 ps로 볼 수 있는 명령줄 대신 @파일로 넘겼습니다. 이제 label linux를 지정한 잡(app)을 실행했습니다.
CLI="java -jar jenkins-cli.jar -s http://127.0.0.1:8080/"
# label linux 에이전트에서 도는 잡
$CLI create-job app < jobs/app.xml
$CLI build app -s -v
# 실행은 에이전트에서, 빌드 기록과 로그는 컨트롤러에 저장
ls controller/jobs/app/builds/1
Started app #1
Started from command line by anonymous
Running as SYSTEM
Building remotely on agent-1 (linux) in workspace /srv/jenkins-lab/agent/workspace/app
[app] $ /bin/sh -xe /tmp/jenkins411893663121045045.sh
+ id -un
jenkins-agent
+ pwd
/srv/jenkins-lab/agent/workspace/app
+ ls /srv/jenkins-lab/controller/secrets
ls: cannot access '/srv/jenkins-lab/controller/secrets': Permission denied
+ true
Finished: SUCCESS
Completed app #1 : SUCCESS
build.xml
changelog.xml
log
빌드는 agent-1에서 jenkins-agent 사용자로 돌았고, 같은 명령으로 컨트롤러의 secrets를 읽으려 하자 Permission denied가 났습니다. 반면 빌드 기록(build.xml)과 콘솔 로그(log)는 컨트롤러에 저장되었습니다. 빌드를 에이전트로 옮겨도 로그는 컨트롤러 디스크에 쌓이므로 오래된 빌드를 지우는 buildDiscarder 설정이 필요합니다.
3.4 빌드가 시작되지 않는 경우
built-in 실행기를 0으로 두면 빌드를 받을 곳은 에이전트뿐입니다. 에이전트가 끊기거나 잡의 라벨이 틀리면 어떻게 되는지 확인했습니다.
J=http://127.0.0.1:8080
CLI="java -jar jenkins-cli.jar -s $J/"
# 에이전트 프로세스를 멈춘 뒤 빌드 요청
pkill -u jenkins-agent java; sleep 3
$CLI build app
# 어떤 노드에도 없는 label(docker)을 요구하는 잡
$CLI create-job image-build < jobs/image-build.xml
$CLI build image-build
sleep 8
# 큐에 남은 빌드와 대기 이유
curl -sg "$J/queue/api/json?tree=items[task[name],why]" \
| jq -r '.items[] | "\(.task.name): \(.why)"'
image-build: There are no nodes with the label ‘docker’
app: ‘agent-1’ is offline
두 빌드 모두 실패하지 않고 큐에서 계속 기다립니다. 실패가 아니니 알림도 오지 않습니다. 그래서 저는 에이전트 오프라인과 큐 대기 시간을 모니터링 항목으로 두고, 장애 확인은 큐의 why 값부터 봅니다.
4. Freestyle 잡과 Pipeline
Freestyle 잡은 웹 UI에서 빌드 단계를 설정하는 전통적인 잡이고, 설정은 컨트롤러의 config.xml에 저장됩니다. Pipeline은 빌드 절차를 Jenkinsfile이라는 코드로 정의합니다. Jenkins 문서는 Pipeline을 CD 파이프라인을 구현하는 플러그인 묶음으로 설명하고, Jenkinsfile을 소스 저장소에 커밋하는 것을 Pipeline-as-code의 기본으로 봅니다. Pipeline은 컨트롤러가 재시작되어도 이어서 실행될 수 있습니다(durable).
저는 새 CI는 모두 Jenkinsfile로 둡니다. UI에서 바꾼 설정은 리뷰 기록이 남지 않지만, Jenkinsfile은 코드와 같은 PR에서 리뷰되고 되돌릴 수 있기 때문입니다. Freestyle에서는 여러 잡과 플러그인을 엮어야 했던 병렬 실행, 조건, 승인 대기 같은 흐름도 Pipeline에서는 문법으로 표현됩니다.
Declarative와 Scripted
Jenkinsfile은 두 가지 문법으로 쓸 수 있습니다. 둘 다 같은 Pipeline 엔진 위에서 돌고 같은 스텝과 공유 라이브러리(Shared Library)를 씁니다. 차이는 문법의 자유도입니다.
| 구분 | Declarative | Scripted |
| 시작 블록 | pipeline { } | node { } |
| 구조 | 정해진 섹션과 지시어로 작성, 자유로운 Groovy 코드는 script 블록 안에만 | Groovy 문법 대부분 사용 |
| 조건과 오류 | when, post 지시어 | if/else, try/catch/finally |
| 제약 | pipeline 블록 코드 크기 제한 이슈가 있음 | 해당 제한 없음 |
저는 Declarative를 권합니다. 구조가 정해져 있어 다른 사람이 읽기 쉽고, Pipeline: Declarative 플러그인이 있으면 Jenkins CLI의 declarative-linter로 실행 전에 문법을 검사할 수 있기 때문입니다. 복잡한 로직이 필요하면 Scripted로 바꾸기보다 셸 스크립트나 공유 라이브러리로 뺍니다.
5. Jenkinsfile 구조와 주요 지시어
Declarative Pipeline은 pipeline 블록 안에 agent, stages, post 같은 섹션과 environment, options, when 같은 지시어를 둡니다. stage는 위에서부터 차례로 실행되고, 끝나면 post의 조건 블록이 정해진 순서대로 평가됩니다.

앞 stage가 실패하면 뒤 stage는 건너뛰고 post로 넘어가며, post는 always부터 cleanup까지 순서대로 평가합니다
| 지시어 | 위치와 역할 |
| pipeline | 최상위 블록, Declarative의 모든 내용이 이 안에 들어감 |
| agent | 실행할 곳, 최상위에 필수이고 stage마다 따로 지정 가능(any, none, label, docker 등) |
| stages | stage를 하나 이상 담는 섹션 |
| stage | 이름이 붙은 단계, 화면에 단계별로 표시 |
| steps | stage 안에서 실행할 스텝(sh, echo 등) |
| post | pipeline이나 stage가 끝난 뒤 결과 조건별로 실행 |
| environment | 환경 변수 정의, credentials()로 자격 증명 연결 |
| when | stage 실행 조건(branch, tag, changeRequest, expression 등) |
| options | 실행 옵션(timeout, buildDiscarder, disableConcurrentBuilds 등) |
이 시리즈의 CI 흐름을 Jenkinsfile로 옮기면 아래와 같습니다. Pipeline Syntax 문서에 맞춰 썼지만 실습 환경에 Pipeline 플러그인이 없어 실행하지 않은 예시이고, 레지스트리 주소와 자격 증명 ID도 예시 값입니다.
pipeline {
agent { label 'linux' }
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
disableConcurrentBuilds()
}
environment {
REGISTRY = 'registry.example.com'
IMAGE = 'registry.example.com/team/app'
}
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
stage('Image') {
when { branch 'main' }
environment {
REG = credentials('registry-creds')
}
steps {
sh '''
TAG=$(git rev-parse --short=12 HEAD)
echo "$REG_PSW" | docker login "$REGISTRY" \
-u "$REG_USR" --password-stdin
docker build -t "$IMAGE:$TAG" .
docker push "$IMAGE:$TAG"
'''
}
}
}
post {
failure {
echo "빌드 실패: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
cleanup {
deleteDir()
}
}
}
- agent의 label linux가 에이전트 라벨과 다르면 3.4처럼 빌드가 큐에서 계속 기다립니다.
- 최상위 timeout은 에이전트를 받은 뒤부터 시간을 재고, 자기 agent가 있는 stage에 두면 에이전트 대기 시간까지 포함합니다. 멈춘 빌드가 실행기를 붙잡지 않도록 저는 최상위 timeout을 항상 둡니다.
- when의 branch 조건은 Multibranch Pipeline에서만 동작하며, PR 빌드는 Build와 Test만 돌고 main 커밋만 이미지를 만듭니다. stage에 별도 agent가 있으면 beforeAgent true로 조건을 먼저 확인합니다.
- 사용자 이름과 비밀번호 자격 증명을 credentials()로 연결하면 REG_USR, REG_PSW 변수도 생깁니다. sh에는 작은따옴표 문자열을 넘겨 Groovy가 아니라 셸이 값을 풀게 합니다.
- 이미지 태그는 [GitHub] 브랜치, Pull Request, Webhook 정리에서 정리한 대로 커밋 SHA를 씁니다. 이 태그를 매니페스트 저장소에 반영하는 단계는 이후 연동 글에서 이어 붙입니다.
post에서 알림처럼 결과에 따라 달라지는 일은 failure나 fixed에, 작업 공간 정리처럼 반드시 해야 하는 일은 cleanup에 둡니다. cleanup은 다른 조건을 모두 평가한 뒤 실행되므로 알림 스텝이 쓸 파일을 먼저 지울 걱정이 없습니다.
6. Multibranch Pipeline
Multibranch Pipeline은 저장소를 스캔해서 Jenkinsfile이 있는 브랜치마다 잡을 자동으로 만들고, 각 잡은 자기 브랜치의 Jenkinsfile로 실행됩니다. 브랜치마다 잡을 손으로 만들 필요가 없고, 브랜치마다 Jenkinsfile이 달라도 됩니다.

Jenkinsfile이 없는 docs 브랜치는 잡이 만들어지지 않고, PR은 PR-번호 형태의 잡이 됩니다
잡 안에서는 BRANCH_NAME(브랜치 이름)과 CHANGE_ID(PR 번호 같은 변경 요청 식별자)를 env로 읽을 수 있고, when의 branch와 changeRequest 조건도 이 정보를 씁니다. PR을 잡으로 만들려면 GitHub Branch Source 같은 브랜치 소스 플러그인이 필요하며, 이 플러그인의 Organization Folder는 GitHub 조직 전체를 스캔해 Multibranch Pipeline을 만들어 줍니다.
운영에서는 세 가지를 봅니다. 공식 문서는 Organization Folder가 아니면 브랜치 추가와 삭제를 자동으로 다시 스캔하지 않는다며 주기적 재스캔을 권하므로, 저는 Webhook과 주기적 스캔을 함께 둡니다. GitHub 필수 체크 이름은 플러그인이 보고하는 이름과 같아야 하며, 이는 [GitHub] 브랜치, Pull Request, Webhook 정리에서 다뤘습니다. 마지막으로 PR 빌드는 PR 작성자가 바꿀 수 있는 코드를 실행합니다. GitHub Branch Source는 신뢰하지 않는 포크의 PR에는 대상 브랜치의 Jenkinsfile을 쓰지만, 빌드 스크립트와 테스트 코드는 여전히 PR의 코드입니다. Jenkins 문서도 신뢰할 수 없는 Pipeline에 중요한 Credentials를 주지 말라고 경고하므로, push 권한이 있는 자격 증명은 main 빌드에서만 씁니다.
7. 플러그인과 Credentials
플러그인 관리
Jenkins 기능 대부분이 플러그인이라 플러그인 관리가 곧 Jenkins 운영입니다. 공식 문서에서 운영에 영향이 큰 동작은 아래와 같습니다.
| 동작 | 내용과 영향 |
| 의존성 | 업데이트 센터에서 설치하면 의존 플러그인도 함께 받음, 하나를 설치해도 여러 개가 들어올 수 있음 |
| 버전 선택 | 업데이트 센터는 최신 버전만 설치, 이전 버전은 .hpi 파일을 직접 설치 |
| 업데이트 반영 | 받은 업데이트는 컨트롤러를 재시작해야 적용 |
| 제거 | 다른 플러그인이 의존하는 플러그인 파일을 지우면 컨트롤러가 정상 부팅하지 못할 수 있음 |
저는 플러그인 업데이트를 코어 LTS 업데이트와 묶어 계획된 재시작 때 하고, 직전에 JENKINS_HOME을 백업합니다. 최신 버전만 설치되는 구조라 문제가 생겼을 때 되돌리는 가장 확실한 방법이 백업이기 때문입니다. 문서도 쓰지 않는 플러그인을 지우면 메모리가 줄고 이후 업데이트에서 충돌할 가능성도 줄어든다고 설명합니다.
Credentials 저장과 사용
Credentials는 Jenkins가 외부 시스템에 접속할 때 쓰는 비밀값으로, 컨트롤러에 암호화되어 저장되고 Pipeline에서는 ID로 참조합니다.
| 종류 | 용도 예 |
| 비밀 문자열 | Secret text, API 토큰이나 GitHub PAT |
| 사용자와 비밀번호 | Username and password, 레지스트리 로그인 |
| 비밀 파일 | Secret file, kubeconfig 같은 파일 |
| SSH 키 | SSH Username with private key, Git clone |
| 인증서 | Certificate, PKCS#12 인증서 |
범위(Scope)가 Global이면 잡과 Pipeline에서 쓸 수 있고, System이면 이메일 인증이나 에이전트 연결처럼 Jenkins 자신만 씁니다. ID는 한 번 정하면 바꿀 수 없으므로 registry-creds처럼 용도가 드러나는 규칙을 처음부터 정합니다. [GitHub] 브랜치, Pull Request, Webhook 정리에서 권장한 GitHub App은 GitHub Branch Source 플러그인이 제공하는 GitHub app 종류로 등록합니다.
연결한 비밀값을 로그에 출력하면 Jenkins는 ****로 가립니다. 하지만 문서는 이것이 실수로 인한 노출만 줄일 뿐 악의적인 사용자가 값을 빼내는 것은 막지 못한다고 적고 있고, Groovy 문자열 보간으로 넘긴 특수 문자가 셸에서 처리되어 값이 한 글자만 바뀌면 더 이상 가려지지 않는 사례도 보여 줍니다. 또 큰따옴표 문자열(GString)의 ${...}로 값을 끼워 넣으면 명령이 에이전트로 가기 전에 값이 풀려 프로세스 인자에 들어가고 ps로도 보입니다. 그래서 자격 증명은 작은따옴표 문자열로 넘겨 셸이 환경 변수에서 읽게 합니다. 또 3.2처럼 빌드가 컨트롤러에서 돌면 secrets까지 읽히므로, 실행기 0 설정은 Credentials 보호의 전제이기도 합니다.
운영에서 확인할 것
| 항목 | 확인하는 이유 |
| built-in 실행기 | 0이 아니면 빌드가 컨트롤러 파일과 master.key에 접근 가능 |
| 큐 대기 | 에이전트 장애나 라벨 오류가 실패가 아닌 무한 대기로 나타남 |
| 빌드 보관 | 로그와 기록이 컨트롤러 디스크에 쌓이므로 buildDiscarder로 개수 제한 |
| 플러그인 | 재시작 후 반영되고 최신 버전만 설치되므로 백업 후 진행 |
| 자격 증명 | 마스킹은 실수 방지용, 작은따옴표로 넘기고 신뢰할 수 없는 빌드에서 쓰지 않음 |
| 백업 | JENKINS_HOME 전체를 백업하고 master.key는 따로 보관 |
8. 정리
- 컨트롤러는 설정, 큐, 기록을 맡고 빌드는 에이전트가 실행합니다. 실습에서 built-in 노드의 빌드는 master.key가 있는 secrets를 읽었으므로 built-in 실행기는 0으로 두고, 그 대신 생기는 큐 대기를 모니터링합니다.
- 빌드 절차는 Jenkinsfile에 Declarative로 쓰고 컨트롤러에서 도는 Groovy는 흐름에만 쓰며, Multibranch Pipeline은 Webhook과 주기적 스캔을 함께 둡니다.
- 플러그인은 백업 후 계획된 재시작 때 올리고, Credentials는 ID로 참조하되 마스킹을 믿지 않고 작은따옴표로 넘깁니다.
다음 글인 [ArgoCD] ArgoCD 동작 원리 정리에서는 Jenkins가 바꾼 매니페스트를 ArgoCD가 클러스터에 반영하는 방식을 정리합니다. Jenkins에 GitHub App 자격 증명과 Multibranch Pipeline을 실제로 설정하는 과정은 이후 활용 글에서, 빌드한 이미지 태그를 매니페스트 저장소에 커밋하는 단계까지 이어 붙이는 과정은 연동 글에서 다룹니다.
참고
'DevOps > CI & CD' 카테고리의 다른 글
| [ArgoCD] ArgoCD 설치와 사용법 정리 (0) | 2026.09.27 |
|---|---|
| [GitHub] 브랜치, Pull Request, Webhook 정리 (0) | 2026.09.27 |
| [CI/CD] CI/CD와 GitOps 개념 정리 (0) | 2026.09.27 |