지금까지 우리는 하나의 거대한 Jenkinsfile 안에 빌드, 테스트, 소나큐브 검사, 도커 이미지 푸시, K8s 배포까지 모든 로직을 한곳에 집중시키는 단일 파이프라인(Monolithic Pipeline) 방식을 배웠습니다. 프로젝트 규모가 작을 때는 이것이 편합니다. 하지만 회사가 성장하여 수십 개의 마이크로서비스(MSA)가 얽히고, "A 서비스와 B 서비스가 모두 성공적으로 빌드된 후에야 C라는 통합 결제 서버를 빌드하고 배포하라"는 극악의 워크플로우(Workflow) 요구사항이 떨어지면 어떻게 될까요? 이 모든 것을 파일 하나에 욱여넣으면 코드는 2천 줄이 넘어가고, 중간에 작은 에러 하나만 터져도 2천 줄짜리 거대 공장이 통째로 멈춰버리는 끔찍한 사태가 발생합니다.
이번 글에서는 "크고 뚱뚱한 것을 작고 날렵하게 쪼개어 연결하라"는 소프트웨어 공학의 진리를 젠킨스에 적용한
'Upstream(상위) / Downstream(하위) Job 연쇄 호출 트리 아키텍처'에 대하여 다뤄보겠습니다.
1. Upstream과 Downstream의 개념 이해
강물이 상류에서 하류로 흐르듯, 젠킨스의 작업(Job)들도 의존성(Dependency)의 흐름을 가집니다.
- Upstream (상위 Job): 나보다 먼저 실행되어야 하는 원인 제공자입니다. (예: 소스코드 컴파일 Job)
- Downstream (하위 Job): 상위 Job이 무사히 성공한 직후, 그 바통을 이어받아 실행되는 결과 수신자입니다. (예: 운영 서버 배포 Job)
이렇게 파이프라인을 기능별로 잘게 쪼개어 독립적인 Job으로 분리해 두면, "배포 로직"만 고치고 싶을 때 수천 줄의 코드를 건드릴 필요 없이 딱 'Deploy Job' 하나만 열어서 수정하고 독립적으로 테스트해 볼 수 있는 엄청난 유지보수성의 향상을 가져옵니다.
2. build job 스텝: 바통 터치의 핵심 문법
A 파이프라인(Upstream)이 끝나기 steps 안에 build 라는 내장 명령어를 사용하면 됩니다.
// [Upstream 파이프라인: 빌드 및 테스트 전용]
pipeline {
agent any
stages {
stage('Unit Test') {
steps { echo "유닛 테스트 100% 통과!" }
}
stage('Trigger Downstream Deploy') {
steps {
echo "테스트가 완벽하므로, 즉시 하위 배포(Deploy) 파이프라인을 방아쇠(Trigger) 당깁니다!"
// 핵심 문법: 젠킨스에 등록된 'Production-Deploy-Job' 이라는 다른 Job을 연쇄 호출합니다.
build job: 'Production-Deploy-Job',
wait: false // 하위 Job이 끝날 때까지 여기서 멈춰서 안 기다림 (비동기 방치)
}
}
}
}
위 코드에서 wait: false 옵션은 실무에서 매우 중요합니다. 만약 true(기본값)로 둔다면, 상위 파이프라인은 하위 배포 작업이 끝날 때까지 젠킨스의 귀중한 워커(Executor) 쓰레드 1개를 멍하니 점유한 채 기다리게 됩니다(자원 낭비). wait: false를 줌으로써 상위 잡은 "내 할 일 끝! 바통 넘겼다!" 하고 쿨하게 퇴근(Success)하며 쓰레드를 반환합니다.
3. 런타임 파라미터(매개변수) 전달하기 (고급)
실무에서는 단순히 호출만 하고 끝나는 게 아닙니다. 상위 Job이 방금 구워낸 '도커 이미지 태그 번호'나 'GIT 커밋 해시값'을 하위 Job에게 매개변수(Parameter)로 넘겨주어야 하위 Job이 "아, V42 버전을 배포하라는 거구나!" 하고 알 수 있습니다.
stage('Call Downstream with Params') {
steps {
// 상위 Job의 빌드 번호(42)를 하위 Job의 'TARGET_VERSION' 이라는 파라미터로 예쁘게 포장해서 던집니다.
build job: 'Production-Deploy-Job',
wait: false,
parameters: [
string(name: 'TARGET_VERSION', value: "${env.BUILD_NUMBER}"),
booleanParam(name: 'ENABLE_SLACK_NOTIFY', value: true)
]
}
}
이제 하위 배포 Job은 params.TARGET_VERSION 변수를 까보면 42라는 값을 받아 완벽하게 배포를 수행하게 됩니다.
4. 마무리 및 주요 발전 방향
지금까지 뚱뚱하고 유지보수하기 힘든 거대 파이프라인을, 역할별로 작고 예쁘게 쪼개어 서로 런타임 데이터를 주고받으며 연쇄 폭발을 일으키게 만드는 'Upstream / Downstream 아키텍처'에 대해 다루어 보았습니다. 이 패턴을 마스터하면 수십 개의 MSA 서버 배포를 하나의 거미줄처럼 아름답게 엮어낼 수 있습니다.
파이프라인을 여러 개로 쪼개다 보니 한 가지 문제가 생겼습니다. 쪼개진 5개의 파이프라인마다 최상단에 def slackNotify() { ... } 같은 똑같은 커스텀 함수들이 중복으로 적혀있는 것입니다. 코드가 복붙(Copy & Paste)의 늪에 빠지고 말았습니다. 본 설명에서는 "전사의 모든 파이프라인 공통 함수를 딱 한 곳에서 묶어서 통제한다!" 앞서 살짝 맛보았던 공유 라이브러리의 심화 버전, 'Jenkins 글로벌 라이브러리 vs 폴더 라이브러리의 완벽 해부'에 대해 실무 핵심 노하우와 함께 자세히 살펴보겠습니다.
에서 우리는 slackNotify(), buildDocker()와 같은 공통 함수들을 뽑아내어 재사용하는 '공유 라이브러리(Shared Library)'의 마법을 맛보았습니다. 이 신세계를 접한 여러분의 회사는 젠킨스 [관리 설정] 메뉴에 'Global Pipeline Libraries'를 떡하니 등록하고, 모든 부서(결제팀, 회원팀, 정산팀)가 이 공용 라이브러리 하나를 끌어다 쓰기 시작했습니다. 그런데 어느 날, 회원팀의 신입 데브옵스 엔지니어가 도커 빌드 함수 로직을 살짝 수정하고 커밋(Commit)했습니다. 그 순간, 회원팀뿐만 아니라 결제팀, 정산팀 등 회사 내의 모든 200개 파이프라인이 동시에 줄줄이 빨간불을 뿜으며 빌드 실패로 폭발해버렸습니다! 글로벌(전역) 변수의 무서움을 뼈저리게 깨닫는 순간입니다.
본 글에서는 "하나의 거대한 도구함(Global)을 쓰다 터지는 대형 사고를 막고, 팀별로 프라이빗한 도구함(Folder)을 지급하는" 공유 라이브러리 격리 설계 아키텍처에 대하여 다뤄보겠습니다.
1. Global Library의 명암(Light and Shadow)
[Jenkins 관리] -> [System Configuration] 메뉴에서 등록하는 Global Pipeline Libraries는 가장 직관적이고 강력합니다. 전사의 모든 Jenkinsfile 최상단에 @Library('my-global-lib') _ 한 줄만 적으면 누구나 공통 함수를 쓸 수 있습니다.
하지만 영향 범위(Blast Radius)가 전사적이라는 것이 치명적인 단점입니다. 공용 함수의 파라미터 하나만 잘못 바꿔도 회사의 모든 배포가 마비됩니다. 또한 젠킨스 마스터 권한을 가진 시스템 관리자만이 이 글로벌 라이브러리 세팅을 만질 수 있기 때문에, 일개 개발팀이 "우리 팀만 쓸 커스텀 함수 좀 라이브러리에 추가해 주세요"라고 할 때마다 관리자에게 티켓을 끊고 결재를 받아야 하는 엄청난 병목(Bottleneck)이 발생합니다.
2. 구원자: Folder-level Shared Library
이러한 권한 문제와 폭발 위험을 막기 위해 젠킨스 Folder 플러그인이 등장합니다. 젠킨스 메인 화면에서 New Item을 누르고 Folder 타입으로 'Payment-Team' 이라는 폴더를 만듭니다. 결제팀의 모든 Job(A-api, B-web 등)은 이 폴더 안으로 이사시킵니다.
그리고 놀랍게도, 이 'Payment-Team' 폴더를 클릭하고 좌측의 [Configure(구성)] 메뉴에 들어가 보면 저 아래에 Folder Properties -> Pipeline Libraries 설정 칸이 숨어있습니다!
결제팀 전용 Github 레포지토리(예: payment-shared-lib.git)를 이곳에 등록하고 payment-lib이라는 이름을 붙여줍니다. 이제 이 폴더 안에 있는 Job들만 해당 라이브러리를 쓸 수 있습니다. 회원팀 폴더에 있는 Job이 @Library('payment-lib') _을 적어봤자 젠킨스는 "그런 라이브러리 모름" 하고 에러를 뱉어냅니다. 완벽한 격리(Isolation)가 달성된 것입니다.
3. 실무 하이브리드 아키텍처 코드 예제
가장 이상적인 대기업 실무 세팅은 '글로벌'과 '폴더'를 동시에 섞어 쓰는 하이브리드(Hybrid) 방식입니다. 전사 공통 로직(슬랙 알림 템플릿, 사내망 방화벽 우회 로직 등)은 Global Library에 두고 관리자가 꽉 잡습니다. 반면 팀별 커스텀 배포 로직(결제팀만의 독특한 DB 마이그레이션 함수 등)은 Folder Library에 두고 각 팀장에게 권한을 위임합니다.
// 1. 관리자가 등록해 둔 전사 공용 라이브러리 임포트 (Slack, SonarQube 등)
@Library('corp-global-lib') _
// 2. 결제팀 폴더에만 등록된 결제팀 전용 프라이빗 라이브러리 임포트
@Library('payment-team-lib') _
pipeline {
agent any
stages {
stage('Database Migration') {
steps {
// 결제팀 라이브러리에 정의된 팀 전용 커스텀 함수 실행!
// 이 함수 코드를 수정해도 회원팀 파이프라인에는 1도 영향을 주지 않습니다.
paymentDbMigrate(target: 'PROD', fallback: true)
}
}
stage('Notify') {
steps {
// 전사 공통 라이브러리 함수를 사용하여 사내 표준 양식으로 슬랙 발송
corpStandardSlackNotify(status: 'SUCCESS')
}
}
}
}
4. 마무리 및 주요 발전 방향
지금까지 글로벌 라이브러리가 가진 "수정 한 번에 전사 마비"라는 시한폭탄의 스위치를 제거하고, 폴더 단위 라이브러리 할당을 통해 각 개발팀에게 자율성(Autonomy)과 안전성(Safety)을 동시에 부여하는 'Shared Library Scope 아키텍처'에 대해 다루어 보았습니다. 이 셋업을 마친 당신은 진정한 사내 플랫폼 엔지니어(Platform Engineer)입니다.
파이프라인 로직도 완벽하고, 권한 분리도 끝났고, 라이브러리도 깔끔합니다. 그런데 회사가 더 성장해서 하루에 파이프라인이 500개씩 동시에 돕니다. 젠킨스 서버 한 대가 메모리 100%, CPU 100%를 치면서 비명을 지르다가 장렬하게 서버가 다운(Down)되어 버렸습니다. 단일 서버의 물리적 한계에 부딪힌 것입니다!
다음에는 "마스터는 지시만 하고, 노동은 자식(Agent)들이 나누어서 병렬 처리한다!"
Jenkins Master-Slave(Agent) 분산 빌드 아키텍처 완벽 이해에 대해 실무 핵심 노하우와 함께 자세히 살펴보겠습니다.
'CI. CD > Jenkins' 카테고리의 다른 글
| [Jenkins] Node.js 다중 버전 환경 완벽 통제 가이드 (0) | 2026.10.15 |
|---|---|
| [Jenkins] Trivy로 컨테이너 보안 취약점 차단하기 (0) | 2026.10.14 |
| [Jenkins] Retry 블록 네트워크 장애를 극복하는 파이프라인 (0) | 2026.10.14 |
| [Jenkins] Timeout의 파이프라인 무한 루프 방어 (0) | 2026.10.12 |
| [Jenkins] Maven Gradle 빌드 캐시 최적화 빌드 시간 10배 단축의 비밀 (0) | 2026.10.08 |