CI/CD 자동화를 구축해 놓으면 퇴근 후에도 마음이 편할 줄 알았습니다. 하지만 밤 11시에 슬랙으로 [빌드 실패] 빨간 알람이 울려 부랴부랴 랩탑을 열어보면, 내 코드의 버그가 원인이 아닙니다.
"Github에서 소스코드를 다운로드하다가 DNS를 못 찾음",
"NPM Registry 서버가 잠시 응답하지 않음",
"Docker Hub Rate Limit(속도 제한)에 걸려 이미지 pull 실패"
인프라 엔지니어가 통제할 수 없는 '외부 요인(네트워크 지연 및 타임아웃)' 때문에 파이프라인이 터지는 억울한 경우가 실무 빌드 실패의 절반 이상을 차지합니다. 억울하게 실패한 빌드를 데브옵스 담당자가 젠킨스 화면에 접속해 수동으로 'Rebuild' 버튼을 눌러주면 언제 그랬냐는 듯 쌩쌩하게 잘 돌아갑니다.
이번글에서는 인프라 담당자의 수면권을 보장하기 위해 이러한 일시적(Transient) 오류 발생 시 젠킨스가 스스로 기회를 다시 주는 'Retry(재시도) 블록의 강력한 회복 탄력성(Resilience) 기법'에 대하여 다뤄보겠습니다.
1. 100% 신뢰할 수 없는 네트워크의 본질
마이크로서비스(MSA)와 클라우드 인프라의 주요 원칙은 "네트워크는 언제나 실패할 수 있다(Network is not reliable)"는 것입니다. 여러분의 파이프라인이 curl로 외부 API를 찌르거나, apt-get install로 패키지를 받을 때 100번 중 1번은 패킷 로스나 BGP 라우팅 문제로 타임아웃이 발생합니다.
이때 단 한 번의 실패로 파이프라인 전체를 붉은색(Failure)으로 물들이고 슬랙에 에러 알람을 빵빵 울려대는 것은 매우 피곤한 일입니다. "한 번 실패했어? 그럼 3초만 쉬었다가 다시 한번 찔러봐. 그래도 안 되면 그때 실패 처리해." 이것이 바로 retry 지시어의 존재 이유입니다.
2. Retry 블록의 구조와 동작 원리
Declarative Pipeline에서는 실패하기 쉬운 불안정한 스텝들을 특정 횟수만큼 감싸주는 아주 우아한 문법을 제공합니다. steps 내부에서 retry(횟수) { ... } 블록을 사용합니다.
pipeline {
agent any
stages {
stage('Flaky Network Tasks') {
steps {
// 이 블록 안의 명령어가 실패(exit code 1 등)하면, 즉시 종료하지 않고 최대 3번까지 처음부터 다시 실행합니다.
retry(3) {
echo "외부 NPM 레지스트리에서 패키지 다운로드를 시도합니다..."
// 네트워크 순단으로 이 명령어가 실패할 확률이 5%라고 가정해봅시다.
// 3번 연속으로 실패(0.05 * 0.05 * 0.05)할 확률은 0.0125%로 극적으로 줄어듭니다.
sh 'npm install --no-optional'
}
echo "무사히 다운로드를 마쳤습니다!"
}
}
}
}
이렇게 retry(3)으로 감싸두면, 주요 시도에서 npm install이 타임아웃으로 실패하더라도 젠킨스는 파이프라인을 멈추지 않고 즉시 다음 시도(Retrying...)를 시작합니다. 다음 시도에서 성공하면 파이프라인은 녹색 불을 켜고 다음 스테이지로 당당하게 넘어갑니다.
3. Sleep과의 결합: 숨고르기 전략 (고급 실무)
retry를 쓸 때 주의할 점이 있습니다. 만약 상대방 API 서버가 순간적으로 트래픽이 몰려 잠시 죽어있는(503 Service Unavailable) 상태라고 칩시다. 이때 젠킨스가 재시도를 한답시고 0.1초 만에 미친 듯이 curl을 3번 연속으로 때려버리면 어떻게 될까요? 3번 모두 똑같이 503 에러를 맞고 장렬하게 전사하게 됩니다.
진정한 재시도는 '상대방이 회복할 수 있는 시간적 여유(Backoff)'를 주어야 합니다. 따라서 실무에서는 sleep 명령어와 결합하여 사용합니다.
stage('API Health Check') {
steps {
retry(5) { // 최대 5번 재시도
script {
try {
// API 서버가 살아있는지 핑(ping)을 때려봅니다.
sh 'curl -f http://my-unstable-api-server.com/health'
} catch (Exception e) {
// 실패했다면 즉시 재시도하지 말고, 10초(SECONDS) 동안 숨을 고른 뒤 다음 retry 루프로 넘어갑니다.
echo "API 서버가 불안정합니다. 10초 대기 후 재시도합니다..."
sleep(time: 10, unit: 'SECONDS')
error("강제로 에러를 발생시켜 retry 블록이 재시작되도록 유도")
}
}
}
}
}
4. 마무리 및 주요 발전 방향
지금까지 통제할 수 없는 외부 네트워크의 불안정성 앞에서 파이프라인이 유리 멘탈처럼 깨지는 것을 막아주고, 인프라 엔지니어의 수면을 보장해 주는 강력한 오뚝이 스킬, 'Retry 블록과 Sleep 백오프 전략'에 대해 다루어 보았습니다. 단 두 단어(retry, sleep)만으로 여러분의 파이프라인 신뢰도(Reliability)는 수직 상승했습니다.
파이프라인이 튼튼해지니 하루에도 수백 번씩 배포가 성공하며 로그가 쌓여갑니다. 그런데 어느 날 젠킨스 서버에서 No space left on device라는 무서운 리눅스 디스크 풀(Disk Full) 에러가 떨어지며 서버가 완전히 먹통이 되었습니다! 원인을 찾아보니 젠킨스가 지난 3년간의 모든 빌드 기록, 로그 텍스트, 거대한 .jar 아티팩트 파일들을 쓰레기통에 비우지 않고 서버 하드디스크에 영구히 저장하고 있었던 것입니다. 본 설명에서는 "30일 지난 과거의 영광(빌드 로그)은 미련 없이 불태워버려라!" 서버 용량 폭발을 막아주는 자동 청소부, 'Jenkins 로그 로테이션(Discard Old Builds)'에 대해 아주 심도 있는 실무 노하우와 함께 명확하게 살펴보겠습니다.
CI/CD 자동화를 성공적으로 안착시키고 나면 하루에도 수십, 수백 번의 빌드가 젠킨스 서버에서 수행됩니다. 팀원들은 클릭 한 번으로 배포되는 쾌감에 취해 끊임없이 파이프라인을 돌려댑니다. 그렇게 6개월 평화로운 시간이 흐른 어느 날, 젠킨스 웹 접속이 갑자기 튕기며 HTTP ERROR 500을 뿜어냅니다. 다급하게 서버 터미널에 접속해 df -h 명령어로 용량을 조회해 보는 순간 눈을 의심하게 됩니다. "사용량(Use%) 100% - No space left on device". 젠킨스의 /var/jenkins_home/jobs 디렉토리 아래에 지난 6개월간 쌓인 수만 개의 텍스트 빌드 로그와 거대한 .jar 아티팩트 파일들이 테라바이트(TB) 단위로 하드디스크를 가득 채워 서버가 질식사해 버린 것입니다. 본 글에서는 "과거의 쓰레기를 영원히 품고 사는 젠킨스의 나쁜 버릇"을 강제로 교정하여, 서버 용량 폭주를 원천 차단하는 'Discard Old Builds (로그 로테이션) 영구 설정법'에 대하여 다뤄보겠습니다.
1. 왜 젠킨스는 스스로 청소하지 않는가?
젠킨스의 기본 사상은 '보존(Preservation)'입니다. 당신이 별도의 설정을 명시하지 않는 한, 젠킨스는 #1번 빌드부터 방금 끝난 #5000번 빌드까지의 모든 콘솔 로그 텍스트, 플러그인 결과, 심지어 archiveArtifacts로 보관한 수십 MB짜리 압축 파일들을 영원히 디스크에 저장합니다.
하지만 냉정하게 생각해 봅시다. 1년 전 #42번 빌드가 성공했는지 실패했는지 로그를 다시 열어볼 일이 실무에서 단 한 번이라도 있을까요? 전혀 없습니다. 기껏해야 최근 일주일 내의 빌드 로그, 혹은 가장 최근에 성공한 10~20개 정도의 빌드 이력만이 디버깅을 위해 유의미한 가치를 가질 뿐입니다. 나머지는 그저 값비싼 클라우드 스토리지 비용을 낭비하는 거대한 디지털 좋은 평가를 받기 어렵습니다.
2. buildDiscarder 블록의 강력한 문법
이 재앙을 막기 위해 젠킨스는 Declarative Pipeline의 options 블록 안에 buildDiscarder (빌드 폐기)라는 아주 직관적이고 강력한 지시어를 제공합니다. 이를 통해 '개수 기준(Count-based)' 또는 '날짜 기준(Time-based)'으로 자동 청소 룰을 세팅할 수 있습니다.
pipeline {
agent any
// 실무 파이프라인의 숨겨진 필수 방어막 (로그 로테이션)
options {
// 방법 1: "최근 30개의 빌드 이력만 남기고, 과거 빌드는 영구 삭제해라!" (가장 추천)
buildDiscarder(logRotator(numToKeepStr: '30'))
// 방법 2: "몇 개가 쌓이든 상관없고, 무조건 14일(2주)이 지난 낡은 빌드 이력은 삭제해라!"
// buildDiscarder(logRotator(daysToKeepStr: '14'))
// 방법 3 (고급 실무 조합): "최대 30일치를 보관하되, 개수가 100개를 넘어가면 30일이 안 지났어도 오래된 순으로 삭제해라!"
// buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '100'))
}
stages {
stage('Build') {
steps { echo "이 빌드 로그도 30번 뒤에는 깔끔하게 지워집니다." }
}
}
}
3. 아티팩트(Artifact) 분리 폐기 전략
텍스트로 된 콘솔 로그 용량은 사실 애교 수준입니다. 진짜 디스크를 급격히 고갈시키는 주범은 빌드 산출물(.war, .zip, 도커 tar 파일)을 보관하는 아티팩트(Artifact)입니다. 텍스트 로그는 100개를 남겨서 히스토리를 보고 싶지만, 500MB짜리 거대한 파일은 가장 최근 5개만 남기고 다 지워버리고 싶을 수 있습니다. 이때 artifactNumToKeepStr 옵션을 추가로 사용합니다.
options {
// 텍스트 빌드 로그와 히스토리는 최근 100개를 보존하되,
// 디스크를 많이 먹는 거대한 아티팩트(첨부파일)는 최근 5개 빌드분만 남기고 싹 삭제해라!
buildDiscarder(logRotator(numToKeepStr: '100', artifactNumToKeepStr: '5'))
}
이 한 줄의 코드가 여러분 회사의 연간 클라우드 EBS 스토리지 비용 수백만 원을 절약해 줄 것입니다.
4. 마무리 및 주요 발전 방향
지금까지 과거에 집착하는 젠킨스의 기본 습성을 교정하여, 수 기가바이트의 낡은 로그와 아티팩트를 쿨하게 폐기하고 디스크 용량을 영구적으로 클린하게 유지하는 'buildDiscarder' 옵션의 극한 활용법에 대해 다루어 보았습니다. 내가 만든 파이프라인 최상단에 이 options 블록이 빠져있지 않은지 당장 내일 출근해서 확인해 보시기 바랍니다.
이제 인프라의 안정성과 용량 관리는 무적에 가까워졌습니다. 하지만 최근 데브옵스 트렌드는 '속도'와 '안정성'을 넘어 '보안(Security)'으로 진화하고 있습니다. 이른바 DevSecOps의 시대입니다.
우리가 구워낸 예쁜 도커 이미지 안에 만약 해커들이 뚫어놓은 치명적인 리눅스 커널 백도어가 숨어있다면 어떨까요?
다음 글에서는 "보안 스캔에 걸리면 배포를 그 자리에서 폭파시켜 버린다!" 컨테이너 보안 스캐너의 절대 강자 'Trivy(트리비) 연동 및 취약점 방어 자동화'에 대해 자세히 다뤄보겠습니다.
'CI. CD > Jenkins' 카테고리의 다른 글
| [Jenkins] Node.js 다중 버전 환경 완벽 통제 가이드 (0) | 2026.10.15 |
|---|---|
| [Jenkins] Trivy로 컨테이너 보안 취약점 차단하기 (0) | 2026.10.14 |
| [Jenkins] Timeout의 파이프라인 무한 루프 방어 (0) | 2026.10.12 |
| [Jenkins] Maven Gradle 빌드 캐시 최적화 빌드 시간 10배 단축의 비밀 (0) | 2026.10.08 |
| [Jenkins] Jenkins 백업 스크립트 복구 (0) | 2026.10.07 |