본문 바로가기
CI.CD/Jenkins

[Jenkins] Timeout의 미학: 좀비 파이프라인(무한 루프) 방어전

반응형

"어? 어제 퇴근할 때 걸어놓은 배포가 오늘 아침까지 안 끝나고 계속 돌아가고 있네?" CI/CD 인프라를 운영하다 보면 누구나 한 번쯤 마주치게 되는 소름 돋는 상황입니다. 쉘 스크립트 안에 잘못 작성된 while(true) 무한 루프 버그, 혹은 외부 API 서버가 죽어서 응답을 주지 않아 무한 대기(Hang)에 빠지는 경우 등 원인은 다양합니다. 문제는 이렇게 '에러를 뿜으며 실패(Failure)하지도 않고, 그렇다고 성공(Success)하지도 않은 채 영원히 멈춰있는 좀비(Zombie) 빌드'들이 젠킨스의 가장 큰 적이라는 점입니다.

이번 [Jenkins 실무 완벽 가이드] 시리즈에서는 소중한 젠킨스의 워커(Worker) 자원을 갉아먹는 이 좀비 빌드들을 가차 없이 사살해버리는 'Timeout(타임아웃) 제어를 통한 파이프라인 무한 대기 방어 기술'에 대하여 다뤄보겠습니다.

🧟‍♂️ 1. 좀비 빌드의 심각성 (리소스 고갈의 공포)

젠킨스는 기본적으로 작업을 수행하기 위해 실행기(Executor)라는 일꾼 쓰레드를 사용합니다. 만약 젠킨스 설정에서 일꾼을 5명(Executor=5)으로 세팅해 두었다면, 동시에 5개의 파이프라인만 돌 수 있습니다. 그런데 앞서 말한 좀비 빌드가 하나 생기면, 일꾼 1명이 그 작업에 매달린 채 영원히 퇴근하지 못하게 됩니다.

이런 좀비 빌드가 5개가 쌓이면 어떻게 될까요? 가용할 수 있는 일꾼 5명이 모두 멈춰버리는(Deadlock) 최악의 사태가 발생합니다. 다른 개발자들이 아무리 배포 버튼을 눌러도 "대기열(Queue)에서 실행기를 기다리는 중입니다..."라는 메시지만 뜨고 전사의 모든 CI/CD가 셧다운(Shutdown) 됩니다. 인프라 관리자가 수동으로 빨간색 X 버튼을 눌러 죽이기 전까지 시스템은 복구되지 않습니다. 자동화 시스템이 수동 감시를 요구하는 모순이 발생하는 것입니다.


⏳ 2. 전역 타임아웃 (Global Timeout) 설정하기

이 끔찍한 현상을 원천 차단하는 가장 쉽고 확실한 방법은, 파이프라인 전체 생존 시간에 유통기한을 강제로 부여하는 것입니다. Declarative Pipeline 최상단의 options 블록에 timeout 지시어를 선언합니다.

pipeline {
    agent any
    
    // 🌟 파이프라인 전체를 감싸는 절대 방어막 (전역 타임아웃)
    options {
        // 이 파이프라인이 시작된 지 1시간(HOURS)이 지나면? 묻지도 따지지도 않고 강제 종료(Aborted)!
        timeout(time: 1, unit: 'HOURS') 
    }
    
    stages {
        stage('Build') {
            steps { echo "빌드를 시작합니다..." }
        }
        // ... 수많은 스테이지들 ...
    }
}

이렇게 options에 타임아웃을 걸어두면, 빌드가 어떤 이유로든 1시간을 초과하는 순간 젠킨스 데몬이 개입하여 "Timeout has been exceeded"라는 붉은 로그를 남기며 해당 파이프라인의 숨통을 강제로 끊어버립니다(Aborted). 그리고 일꾼(Executor)을 회수하여 대기열에 있던 다음 작업에 정상적으로 투입합니다.


⏱️ 3. 지역 타임아웃 (Stage-level Timeout)의 정교한 통제

전역 타임아웃만으로도 훌륭하지만, 실무에서는 더 정교한 통제가 필요할 때가 많습니다. 전체 빌드는 1시간이 걸려도 괜찮지만, "외부 서버로 API를 쏘고 응답을 기다리는 특정 스테이지는 무조건 3분 안에 끝나야 한다(안 그러면 API 서버가 죽은 것임)"라는 조건이 있을 수 있습니다. 이때는 특정 steps 내부를 timeout 블록으로 감싸주면 됩니다.

pipeline {
    agent any
    stages {
        stage('Fragile External API Call') {
            steps {
                echo "불안정한 외부 결제망 API와 연동 테스트를 시작합니다..."
                
                // 🌟 특정 로직에만 씌우는 정밀 방어막 (지역 타임아웃)
                // 이 블록 안의 작업이 5분을 초과하면, 이 스테이지에서 즉시 에러를 발생시키며 중단됩니다.
                timeout(time: 5, unit: 'MINUTES') {
                    // 서버 응답이 없으면 curl이 무한 대기(Hang)에 빠질 수 있는 위험한 스크립트
                    sh 'curl http://very-slow-and-unstable-api.com/sync'
                }
                
                echo "API 연동 테스트가 무사히 5분 안에 끝났습니다!"
            }
        }
    }
}

이 기법은 24단계에서 배웠던 Input Step(수동 승인 팝업)과 결합할 때 최고의 시너지를 냅니다. 팀장님이 퇴근하고 승인 버튼을 안 눌러줘도, timeout으로 감싸두면 30분 뒤에 알아서 팝업이 사라지고 깔끔하게 취소 처리됩니다.


🎯 4. 마무리 및 다음 단계

지금까지 젠킨스 자원을 고갈시키는 가장 무서운 적, '좀비 빌드(무한 루프/무한 대기)'의 원인을 짚어보고, 전역(Global)과 지역(Stage) 레벨에서 Timeout 방어막을 쳐서 인프라의 안정성을 극강으로 끌어올리는 비법에 대해 3,000자의 밀도 높은 내용으로 살펴보았습니다. 이제 여러분의 서버는 그 어떤 결함 있는 코드나 외부 장애 앞에서도 스스로를 방어하며 굳건히 돌아갈 것입니다.

타임아웃 덕분에 멈춰있던 빌드가 취소(Aborted)로 끝났습니다. 이제 개발자는 왜 실패했는지 에러 로그(Console Output)를 열어볼 것입니다. 그런데 10만 줄의 텍스트가 전부 흑백이고, 이 에러가 정확히 몇 시 몇 분에 발생했는지 시간(Time)조차 안 찍혀 있다면? 원인 분석에 엄청난 시간을 허비하게 됩니다. 이어지는 30단계 포스팅에서는 밋밋하고 읽기 힘든 젠킨스 로그에 생명력을 불어넣고 디버깅 시간을 10배 단축시켜 주는 구세주, 'Timestamps 적용과 빌드 로그 가독성 혁신 기법'에 대해 아주 치밀하게 파헤쳐 보겠습니다!

반응형