파이프라인을 성공적으로 구축하고 자동화의 기쁨을 누리는 것도 잠시, 프로젝트의 덩치가 커지면서 빌드 시간이 점점 늘어나기 시작합니다. 프론트엔드 빌드 5분, 백엔드 스프링 빌드 5분, 유닛 테스트 5분, E2E 테스트 10분... 코드를 하나 푸시하고 나서 무려 25분을 기다려야 배포가 완료된다면 개발팀의 생산성은 바닥을 칠 것입니다.
이번 포스트에서는 "시간이 돈"인 현대 소프트웨어 개발 환경에서 가장 적은 노력으로 빌드 시간을 드라마틱하게 반토막 내버리는 '병렬(Parallel) 처리 기법'에 대하여 다뤄보겠습니다.
1. 직렬 빌드의 치명적인 병목 현상
기본적으로 젠킨스의 stages 블록 안에 적힌 stage들은 위에서 아래로 순차적(직렬)으로 실행됩니다. 앞의 작업이 끝나야만 뒤의 작업이 시작될 수 있죠. 그런데 곰곰이 생각해 봅시다. 프론트엔드 React 코드를 압축(Build)하는 작업과 백엔드 Java 코드를 테스트하는 작업은 서로 아무런 연관성(의존성)이 없습니다. 프론트 빌드가 끝날 때까지 백엔드가 얌전히 기다려줄 이유가 전혀 없다는 뜻입니다.
만약 서로 독립적인 3개의 작업이 각각 10분씩 걸린다면, 직렬 처리 시 총 30분이 걸립니다. 하지만 이 3개를 동시에(병렬로) 출발시킨다면? 가장 늦게 끝나는 10분 만에 3개의 작업이 모두 완료되는 마법이 펼쳐집니다. 이것이 바로 parallel 지시어의 위력입니다.
2. Parallel 블록의 기본 구조와 문법
Declarative Pipeline에서 병렬 처리를 구현하는 방법은 직관적입니다. stage 내부에서 steps 대신 parallel 블록을 선언하고, 그 안에 또 다른 stage들을 나란히 나열해 주면 됩니다.
pipeline {
agent any
stages {
stage('Frontend & Backend Parallel Build') {
// 이 블록 안의 stage들은 동시에(동시다발적으로) 실행됩니다.
parallel {
stage('React Build') {
steps {
echo "프론트엔드 빌드 시작..."
sh 'npm run build' // 3분 소
}
}
stage('Spring Boot Test') {
steps {
echo "백엔드 단위 테스트 시작..."
sh './gradlew test' // 5분 소요
}
}
stage('Code Quality Check') {
steps {
echo "정적 분석 시작..."
sh 'sonar-scanner' // 2분 소요
}
}
}
}
stage('Deploy') {
// 위의 3개 병렬 작업이 *모두* 끝난 5분 뒤에야 비로소 이 스테이지가 시작됩니다.
steps {
echo " 병렬 작업 완료! 운영 배포 시작!"
}
}
}
}
앞선 기술 개요에서 다룬 Blue Ocean UI에서 위 코드를 돌려보면, 하나의 스테이지에서 3가닥으로 선이 갈라지며(Fork) 예쁜 병렬 노드 그래프가 그려지는 것을 확인할 수 있습니다.
3. 실무 필수 옵션: failFast true (가장 빠른 손절)
병렬 처리를 할 때 초보자들이 놓치는 가장 중요한 옵션이 바로 failFast 입니다. 위의 예제에서 3개의 작업이 병렬로 돌고 있다고 가정해 봅시다. 그런데 시작하자마자 1분 만에 'React Build'에서 치명적인 에러가 발생해 빨간불이 떴습니다. 프론트엔드가 터졌으니 어차피 최종 배포(Deploy)는 불가능합니다. 그렇다면 나머지 'Spring Boot Test'와 'Code Quality Check'를 굳이 남은 4분 동안 끝까지 땀 뻘뻘 흘리며 돌릴 필요가 있을까요?
아닙니다. 즉시 나머지 병렬 작업들을 강제로 죽여버리고(Kill) 서버 리소스(CPU/Memory)를 반환해야 합니다. 이를 위해 failFast true 옵션을 선언합니다.
stage('Parallel Tests') {
// 병렬 작업 중 단 하나라도 실패하면, 나머지 돌고 있는 작업들을 즉시 Abort 시킵니다.
failFast true
parallel {
stage('Unit Test') { ... }
stage('E2E Test') { ... }
stage('Load Test') { ... }
}
}
4. 주의사항: 리소스 폭발(OOM) 방어 전략
"우와! 그럼 10개 작업을 전부 병렬로 돌리면 1분 만에 빌드가 다 끝나겠네요?"라고 생각하실 수 있습니다. 하지만 현실은 냉혹합니다. 병렬 처리는 시간을 줄이는 대신 서버의 CPU와 메모리를 그만큼 동시에 갉아먹습니다.
만약 메모리가 4GB밖에 안 되는 젠킨스 서버에서 무거운 Spring Boot 컴파일 3개를 병렬로 돌려버리면 어떻게 될까요? 서버가 버티지 못하고 OOM(Out Of Memory) 에러를 뿜으며 즉시 뻗어버립니다. 따라서 병렬 처리를 도입할 때는 반드시 젠킨스 워커(Worker) 노드의 스펙이 넉넉한지 모니터링해야 합니다. 실무에서는 이러한 리소스 부족 문제를 해결하기 위해, 아예 병렬 스테이지마다 agent 지시어를 별도로 선언하여 서로 다른 물리적 서버(또는 다른 K8s 파드)로 작업을 분산 할당(Routing)시키는 고급 기법을 사용하기도 합니다.
5. 마무리 및 주요 발전 방향
지금까지 파이프라인의 숨겨진 시간 단축 치트키인 `parallel` 블록의 구조와 리소스 낭비를 막는 `failFast` 옵션의 100% 활용법에 대해 다루어 보았습니다. 이제 여러분의 파이프라인은 앞만 보고 달리던 경주마에서, 양손 양발을 모두 사용하여 작업을 순식간에 끝내버리는 문어발로 진화했습니다.
하지만 속도만 빠르다고 장땡이 아닙니다. 만약 병렬로 돌고 있는 쉘 스크립트 하나가 무한 루프(Infinite Loop) 버그에 빠지면 어떻게 될까요? 파이프라인은 성공도 실패도 하지 않은 채 몇 시간이고 멈춰서(Hang) 서버 리소스를 갉아먹게 됩니다. 또는 외부 API가 잠깐 끊겼다고 바로 실패(Fail) 처리를 해버리면 너무 억울하겠죠. 본 설명에서는 "30분 지나면 강제 종료해라!", "실패하면 3번까지만 다시 시도해봐라!"라고 젠킨스에게 명령하는 극강의 안정성 확보 기법, 'Timeout과 Retry 제어'에 대해 아주 치밀하게 파헤쳐 보겠습니다!
우리는 지금까지 하나의 프로젝트를 위한 완벽한 Jenkinsfile을 작성하는 법을 배웠습니다. 환경변수를 세팅하고, Slack 알림을 붙이고, 병렬 빌드까지 최적화했습니다. 그런데 만약 회사가 성장하여 마이크로서비스 아키텍처(MSA)를 도입하게 되고, 관리해야 할 서버가 50개로 늘어난다면 어떻게 될까요? 50개의 프로젝트 레포지토리에 똑같이 생긴 수백 줄짜리 Jenkinsfile을 복사해서 붙여넣기(Copy & Paste) 하실 건가요? 만약 보안팀에서 "내일부터 배포 알림 메시지 포맷을 변경하세요"라고 지시한다면, 50개의 레포지토리를 돌며 일일이 코드를 수정하고 50번의 커밋을 해야 하는 유지보수 지옥이 열립니다.
본 글에서는 인프라 관리자를 이 끔찍한 복붙 지옥에서 구원해 줄 '공유 라이브러리(Shared Library)'를 통한 전사 표준 파이프라인 공통화 기법에 대해 다뤄보겠습니다.
1. Shared Library(공유 라이브러리)란 무엇인가?
개발자들이 중복되는 코드(API 호출, 날짜 포맷 변환 등)를 하나의 함수로 빼서 utils.js 같은 공통 파일로 관리하는 것(DRY 원칙)처럼, 데브옵스 엔지니어도 파이프라인의 중복되는 로직(Slack 알림 보내기, 도커 빌드하기, 빌드 후 청소하기 등)을 하나의 모듈로 묶어 중앙 집중적으로 관리할 수 있습니다.
젠킨스는 이를 위해 별도의 GIT 레포지토리를 파서 거기에 공통 로직(Groovy 스크립트)을 뭉쳐놓고, 각 프로젝트의 Jenkinsfile에서는 그 레포지토리를 import하여 딱 한 줄짜리 함수만 호출해서 쓰게 해주는 Global Shared Library 기능을 제공합니다.
2. Shared Library 디렉토리 구조의 이해
공유 라이브러리로 사용할 새로운 GIT 레포지토리(예: my-company-jenkins-lib)를 파고, 아래와 같이 정해진 폴더 구조를 지켜야 합니다.
my-company-jenkins-lib/
src/ # (고급) 복잡한 객체지향 Groovy 클래스들이 들어가는 곳
vars/ # (핵심) 전역 함수(Global Variable)들이 위치하는 곳
notifySlack.groovy
standardPipeline.groovy
resources/ # 파이프라인에서 읽어 들일 공통 설정 파일 (예: 빌드 스크립트 템플릿 등)
가장 많이 쓰이는 곳은 vars/ 디렉토리입니다. 이곳에 notifySlack.groovy라는 파일을 만들고 안에 def call() { ... } 함수를 선언하면, 전사 모든 파이프라인에서 notifySlack()이라는 이름의 함수를 글로벌 변수처럼 호출할 수 있게 됩니다.
3. 글로벌 설정과 파이프라인 연동 기법
이 라이브러리를 젠킨스에 등록해 봅시다.
- [Jenkins 관리] -> [시스템 설정(Configure System)]으로 들어가 Global Pipeline Libraries 섹션을 찾습니다.
- Name에
my-shared-lib같은 직관적인 이름을 줍니다. (나중에Jenkinsfile에서 호출할 식별자입니다.) - Default version에
main브랜치를 입력합니다. - Retrieval method에 GIT을 선택하고, 방금 만든 공유 라이브러리의 GIT 주소를 넣고 저장합니다.
이제 개별 프로젝트의 Jenkinsfile 최상단에 @Library('my-shared-lib') _ 라는 주술 같은 어노테이션(Annotation) 딱 한 줄만 선언하면, 이 젠킨스가 빌드를 시작하기 전에 공유 라이브러리 저장소를 먼저 클론받아 메모리에 싹 올려버립니다. (맨 뒤의 언더스코어 _는 "라이브러리 전체를 즉시 임포트하라"는 그루비 문법입니다.)
4. 실무 적용 극한의 예제 (통합 파이프라인 함수화)
실무에서는 알림 함수 하나만 빼는 것을 넘어서, 아예 파이프라인 전체의 껍데기를 라이브러리에 선언해 버립니다.
1. 공유 라이브러리 (vars/standardNodePipeline.groovy)
// Map 형태로 인자(config)를 받습니다.
def call(Map config) {
pipeline {
agent any
stages {
stage('Build') {
steps {
echo "${config.projectName} 빌드 중..."
sh "npm install && npm run build"
}
}
}
post {
always {
echo "${config.slackChannel} 로 완료 알림 전송!"
}
}
}
}
2. 각 MSA 서비스들의 실제 Jenkinsfile (혁신적인 변화!)
@Library('my-shared-lib') _
// 복잡한 pipeline { ... } 블록은 싹 다 지워버리고, 딱 이거 하나만 남깁니다!
standardNodePipeline(
projectName: 'payment-api-service',
slackChannel: '#team-billing'
)
이것이 바로 MSA 환경 데브옵스의 궁극적인 모습입니다. 50개의 서비스가 각자 위처럼 단 5줄짜리 Jenkinsfile만 가지고 있게 됩니다. 만약 새로운 보안 스캔 툴을 도입해서 전사 파이프라인에 적용해야 한다면? 데브옵스 담당자가 중앙의 standardNodePipeline.groovy 파일 딱 한 곳만 수정하면, 50개의 모든 서비스가 다음 빌드부터 자동으로 새로운 보안 스캔을 타게 됩니다. 소름 돋는 유지보수성의 향상입니다.
5. 마무리 및 주요 발전 방향
지금까지 수십수백 개의 파이프라인 파편화를 방지하고, 단 한 곳에서 전사 배포 정책을 중앙 통제할 수 있게 만들어주는 'Shared Library(공유 라이브러리)'의 막강한 구조와 활용법에 대해 3,000자 분량으로 밀도 높게 다루어 보았습니다. 이 기법을 도입하는 순간 여러분은 일반 젠킨스 사용자를 넘어 전사 인프라 아키텍트의 반열에 오르게 될 것입니다.
자, 파이프라인의 구조도 완벽해졌고 속도도 빨라졌습니다. 하지만 개발자가 짠 코드가 "버그 투성이" 거나 "해킹에 뚫리는 보안 취약점"을 가지고 있다면 아무리 배포를 자동화해 봐야 쓸모가 없습니다. 오히려 쓰레기(Bad Code)를 운영 서버에 아주 빠르게 자동 발사하는 기계로 전락할 뿐입니다.
다음 글에서는 젠킨스가 스스로 코드를 검열하고 빠꾸를 먹이는 정적 코드 분석의 신, 'SonarQube(소나큐브) 품질 게이트 연동법'에 대해 다뤄보겠습니다.
'CI. CD > Jenkins' 카테고리의 다른 글
| [Jenkins] 쿠버네티스(KubernetesK8s) 완벽 연동 무중단 배포 자동화 (0) | 2026.10.07 |
|---|---|
| [Jenkins] SonarQube 연동 불량 코드 자동 차단 시스템 Quality Gate (0) | 2026.10.05 |
| [Jenkins] 멀티브랜치 파이프라인 무한 증식 브랜치 자동화 (0) | 2026.10.04 |
| [Jenkins] 젠킨스 워크스페이스 완벽 청소 가이드 (디스크풀 대응) (0) | 2026.10.04 |
| [Jenkins] When 조건문으로 스테이지 완벽 제어하기 동적 파이프라인 구축 (0) | 2026.10.04 |