느린 Jenkins 빌드 문제 해결: 일반적인 병목 현상과 해결 방법

Jenkins 빌드의 일반적인 성능 문제를 식별하고 해결하세요. 이 문제 해결 가이드는 로그 분석, 실행기 구성 최적화, 빌드 캐싱 메커니즘 활용, 파이프라인 스크립트 간소화를 통해 더 빠르고 효율적인 CI/CD 프로세스를 위한 실용적인 단계를 제공합니다.

느린 Jenkins 빌드 문제 해결: 일반적인 병목 현상과 해결 방법

느린 Jenkins 빌드는 피드백을 지연시키기 때문에 문제가 됩니다. 개발자가 작은 변경 사항을 푸시하고 20분을 기다린 후 첫 1분 안에 테스트가 실패했다는 것을 알게 됩니다. 조정을 시작하기 전에 대기 시간, 에이전트 시작 시간, 체크아웃 시간, 종속성 설정, 테스트 시간, 패키징 및 배포를 분리하세요. 이들은 각각 다른 문제이며 다른 해결 방법이 필요합니다.

목표는 대시보드에서 Jenkins를 더 빠르게 보이게 하는 것이 아닙니다. 목표는 다음 유용한 신호가 더 빨리 도착하도록 하는 것입니다.

1. 초기 진단: 시간은 어디에 사용되고 있나요?

수정 사항을 적용하기 전에 속도 저하의 원인을 정확히 파악해야 합니다. Jenkins는 초기 진단을 위한 훌륭한 내장 도구를 제공합니다.

빌드 로그 분석

가장 즉각적인 리소스는 느린 빌드의 콘솔 출력입니다. 순차적 단계 간의 타임스탬프에서 큰 간격을 찾아보세요.

  • 오래 실행되는 단계 식별: 어떤 빌드 단계(예: mvn clean install, 스크립트 실행, 종속성 다운로드)가 가장 많은 시간을 소비하는지 확인합니다.
  • 외부 호출: 네트워크 활동이 포함된 단계(예: 외부 종속성 가져오기, 원격 아티팩트 저장소에 연결)에 주의하세요. 이는 종종 Jenkins 자체가 아닌 외부 종속성입니다.

빌드 시간 그래프 사용

Jenkins Blue Ocean 또는 클래식 UI 파이프라인은 종종 단계 기간의 시각적 분석을 표시합니다. 이 시각적 도구를 사용하여 어떤 단계가 불균형적으로 긴지 확인하세요.

팁: 특정 단계가 여러 빌드에서 일관되게 예상보다 오래 걸린다면, 그것이 주요 최적화 대상입니다.

2. Jenkins 인프라 병목 현상

빌드 단계 자체는 빠르지만 작업 간 대기 시간이 길다면 문제는 Jenkins 컨트롤러(마스터) 또는 에이전트(슬레이브) 인프라에 있을 가능성이 높습니다.

실행기 가용성 및 과부하

가장 일반적인 인프라 문제는 빌드 용량 부족입니다.

실행기 이해

실행기는 Jenkins 노드에서 작업을 실행할 수 있는 병렬 슬롯입니다. 노드에 5개의 실행기가 있으면 5개의 작업을 동시에 실행할 수 있습니다.

  • 증상: CPU/메모리 사용률이 낮아 보여도 빌드가 지속적으로 대기열에 쌓입니다.
  • 해결 방법: 기본 빌드 노드의 실행기 수를 늘리거나 팜에 노드/에이전트를 더 추가합니다.

구성 확인 (에이전트 관리): 에이전트 구성 화면을 확인하세요. '실행기 수'가 해당 에이전트에 할당된 하드웨어에 적절하게 설정되어 있는지 확인합니다.

컨트롤러 부하

Jenkins 컨트롤러 노드가 어려움을 겪고 있으면 에이전트가 비어 있어도 작업을 제대로 예약할 수 없습니다.

  • 증상: 느린 UI 응답성, 지연된 빌드 예약, 컨트롤러의 시스템 모니터에서 보고된 높은 CPU/메모리 사용량.
  • 해결 방법: 리소스 집약적인 작업(예: 컴파일)을 에이전트에 오프로드합니다. 컨트롤러가 빌드가 아닌 관리 작업에 주로 전념할 수 있는 충분한 리소스(CPU, 충분한 RAM)를 가지고 있는지 확인합니다.

디스크 I/O 성능

느린 디스크 입출력(I/O)은 Git 리포지토리 복제 또는 대용량 아카이브 압축 해제와 같은 대용량 파일 작업이 포함된 단계에 영향을 미칩니다.

  • 모범 사례: Jenkins 작업 공간 및 Jenkins 홈 디렉토리, 특히 빌드 에이전트에서 빠른 스토리지(SSD 또는 높은 처리량의 네트워크 스토리지)를 사용합니다.

3. 파이프라인 스크립트 최적화

비효율적인 선언적 또는 스크립트 파이프라인은 불필요한 오버헤드를 유발할 수 있습니다.

작업 공간 관리

오래된 아티팩트로 가득 찬 대규모 작업 공간은 복제 또는 정리와 같은 후속 작업을 느리게 할 수 있습니다.

  • ws() 단계 현명하게 사용: 스크립트 파이프라인을 사용하는 경우 전체 작업 공간에 대한 작업에 주의하세요.
  • 작업 공간 정리: 성공적인 완료 후 작업 공간을 정리하도록 작업을 구성하거나 cleanWs() 단계를 신중하게 사용합니다. 경고: 증분 빌드 또는 실행 간 아티팩트 캐싱에 의존하는 경우 작업 공간을 정리하지 마세요.

중복 작업 (종속성 다운로드)

동일한 종속성을 반복적으로 다운로드하면 시간이 낭비됩니다.

  • 종속성 캐싱: 에이전트 환경 내에서 빌드 도구별 캐싱 전략(예: Maven 로컬 리포지토리, npm 캐시)을 구현합니다. 가능하면 캐시 디렉토리가 영구적이고 공유되는지 확인합니다.
// 예: 에이전트에서 Maven 리포지토리 지속성 보장
steps {
    sh 'mvn -B clean install -Dmaven.repo.local=/path/to/shared/maven/cache'
}

독립적인 단계 병렬화

파이프라인의 단계가 독립적인 경우 선언적 파이프라인에서 parallel 블록을 사용하여 동시에 실행합니다.

pipeline {
    agent any
    stages {
        stage('Build & Test') {
            parallel {
                stage('Unit Tests') {
                    steps { sh './run_tests.sh' }
                }
                stage('Static Analysis') {
                    steps { sh './run_sonar.sh' }
                }
            }
        }
        stage('Package') {
            // Build & Test 단계가 모두 완료된 후 실행
            steps { sh './create_jar.sh' }
        }
    }
}

4. 빌드 캐싱 메커니즘 활용

대규모 구성 요소(예: Docker 이미지 또는 컴파일된 소스 파일)를 재사용하는 빌드의 경우 캐싱이 속도에 중요합니다.

Docker 레이어 캐싱

파이프라인이 Docker 이미지를 빌드하는 경우 레이어 캐싱을 효과적으로 활용하세요.

  1. 순서가 중요합니다: 자주 변경되는 단계(예: COPY . .)는 드물게 변경되는 단계(예: 기본 종속성 설치)보다 Dockerfile에서 나중에 배치합니다.
  2. Docker 에이전트 사용: Docker를 실행하는 Jenkins 에이전트를 사용할 때 빌드 프로세스가 전체 풀/빌드를 시도하기 전에 기존 로컬 이미지 캐시를 활용하는지 확인합니다.

증분 빌드

해당되는 경우 빌드 도구가 증분 빌드용으로 구성되었는지 확인합니다(예: Gradle의 빌드 캐시 또는 특정 컴파일러 플래그 사용).

5. 에이전트 구성 및 리소스 할당

에이전트는 실제 작업이 수행되는 곳입니다. 올바르게 프로비저닝되고 구성되었는지 확인합니다.

하드웨어 크기 조정

빌드 중 CPU 포화도가 높으면 에이전트에 더 많은 처리 능력이 필요합니다. 빌드가 리소스(예: 메모리)를 자주 기다리는 경우 RAM을 확장합니다.

에이전트 시작 방법

  • 정적 에이전트: 시작 속도가 빠르지만 확장에 덜 유연합니다.
  • 동적 에이전트 (예: Kubernetes 또는 EC2 에이전트): 설정에 시간이 조금 더 걸리지만 이러한 에이전트는 리소스가 필요할 때 정확하게 확장되도록 하여 피크 시간 동안 긴 대기열을 방지합니다.

모범 사례: 동적 확장의 경우 새 에이전트의 시작 시간이 작업이 대기열에서 시간 초과되는 시간보다 충분히 빠른지 확인합니다. 에이전트 프로비저닝에 10분이 걸리지만 작업이 3분만 기다리는 경우 확장은 즉각적인 병목 현상에 도움이 되지 않습니다.

실용적인 느린 빌드 플레이북

  1. 로그 분석: 어떤 파이프라인 단계가 가장 많은 시간을 소비하는지 확인합니다.
  2. 실행기 확인: 에이전트 실행기 수가 예상되는 동시 부하와 일치하는지 확인합니다.
  3. I/O 최적화: 작업 공간과 캐시가 빠른 스토리지에 있는지 확인합니다.
  4. 종속성 캐싱: Maven, npm 또는 기타 종속성 캐시에 대한 지속성을 구현합니다.
  5. 병렬화: 독립적인 파이프라인 단계를 동시에 실행하도록 다시 작성합니다.
  6. 프로파일링 도구: 빌드 도구(Maven, Gradle)가 증분 빌드 기능을 사용하고 있는지 확인합니다.

인프라 용량부터 스크립트 효율성까지 이러한 잠재적 병목 현상을 체계적으로 해결함으로써 느리고 실망스러운 빌드를 CI/CD 워크플로우의 빠르고 안정적인 구성 요소로 전환할 수 있습니다.

느린 빌드를 읽는 더 정직한 방법

느린 모든 Jenkins 빌드를 Jenkins 문제로 취급하는 것은 오후를 낭비하는 가장 빠른 방법입니다. 때로는 Jenkins가 병목 현상입니다. 종종 그것은 단지 메신저일 뿐입니다. 파이프라인은 대기열에서 기다리거나, 에이전트 시작 시간이 오래 걸리거나, Git 체크아웃이 지연되거나, 빌드 도구가 인터넷을 다시 다운로드하거나, 테스트가 직렬화되거나, 다운스트림 배포 단계가 다른 시스템을 기다리기 때문에 느리게 보일 수 있습니다.

느린 작업을 볼 때 총 시간을 네 개의 버킷으로 나눕니다: 대기 시간, 에이전트 프로비저닝 시간, 작업 공간 설정 시간, 실제 빌드/테스트 시간. Jenkins는 빌드 페이지와 파이프라인 단계 보기에 이 중 일부를 표시하지만 콘솔 로그가 여전히 가장 유용한 기록입니다. 타임스탬프가 누락된 경우 추가합니다. 그런 다음 느린 실행과 정상 실행을 비교합니다. 두 타임라인이 갈라지는 첫 번째 지점을 찾고 있습니다.

예를 들어, 느린 실행이 첫 번째 셸 명령이 시작되기 전에 8분을 소비한다면 Maven을 조정해도 도움이 되지 않습니다. 실행기 가용성, 레이블 일치, 클라우드 에이전트 프로비저닝 및 보류 중인 작업을 확인합니다. 느린 실행이 빠르게 시작되지만 git fetch에 5분을 소비한다면 리포지토리 크기, refspec, 태그, 네트워크 경로 및 작업 공간 재사용을 확인합니다. 체크아웃은 빠르지만 npm ci가 매번 느리다면 에이전트의 캐시 지속성 및 레지스트리 액세스를 검사합니다.

메모리에서 최적화하지 마세요. 최근 빌드 세 개를 선택합니다: 하나는 빠른 것, 하나는 일반적인 것, 하나는 느린 것. 각 단계의 기간을 기록합니다. 이 작은 표는 일반적으로 올바른 계층을 가리킵니다.

대기 시간: 빌드 시작 전의 병목 현상

대기 시간은 아직 아무것도 실패하지 않았기 때문에 무시하기 쉽습니다. 개발자는 빌드가 그냥 앉아 있는 것을 봅니다. Jenkins에서 긴 대기열은 일반적으로 네 가지 중 하나를 의미합니다: 실행기가 충분하지 않거나, 레이블이 너무 좁거나, 잠금이 작업을 직렬화하거나, 동적 에이전트가 나타나는 데 느립니다.

작업 페이지와 실행기 상태 패널에서 시작합니다. 많은 에이전트가 유휴 상태이지만 작업이 대기열에 있으면 레이블 표현식이 너무 엄격할 수 있습니다. linux && docker && java17 && large로 레이블이 지정된 작업은 모든 레이블과 일치하는 노드에서만 실행될 수 있습니다. 프로덕션 릴리스 빌드의 경우 의도적일 수 있지만 일반적인 풀 리퀘스트 검사의 경우 종종 우발적입니다. 일반 빌드에 Docker와 Java만 필요한 경우 실제 이유가 없는 한 하나의 특수 시스템에 연결하지 마세요.

잠금은 또 다른 조용한 지연 원인입니다. Lockable Resources 플러그인은 테스트가 공유 데이터베이스, 하드웨어 장치 또는 스테이징 네임스페이스에 대한 독점 액세스가 필요할 때 유용합니다. 너무 많은 작업이 잠금 내부에 있을 때 문제가 됩니다. 잠긴 섹션을 가능한 한 작게 유지합니다. 잠금 외부에서 아티팩트를 빌드하고, 잠금을 획득하고, 공유 리소스 단계만 실행하고, 해제합니다.

클라우드 에이전트의 경우 시작 시간을 별도로 측정합니다. 예약하는 데 2분이 걸리는 Kubernetes 포드는 괜찮을 수 있습니다. 실행할 때마다 대규모 사용자 지정 이미지를 가져오기 때문에 15분이 걸리는 포드는 괜찮지 않습니다. 공통 이미지를 미리 가져오고, 이미지 크기를 줄이거나, CI 트래픽을 예측할 수 있는 경우 작은 웜 풀을 유지합니다.

체크아웃 시간: Git이 전체 문제일 수 있음

느린 체크아웃은 리포지토리가 점진적으로 성장하기 때문에 오래된 Jenkins 설치에서 일반적입니다. 처음 몇 개의 대용량 바이너리는 아무도 눈치채지 못하다가 어느 날 모든 빌드가 수년간의 기록에 대한 비용을 지불합니다.

Git 플러그인 설정을 신중하게 사용하세요. 얕은 클론은 현재 커밋만 필요한 작업에 도움이 될 수 있지만 태그에서 버전을 계산하거나 이전 커밋과 비교하는 빌드를 손상시킬 수 있습니다. 태그를 가져오면 태그가 많은 리포지토리에서 놀라운 시간을 추가할 수도 있습니다. 작업에 태그가 필요하지 않은 경우 태그 가져오기를 비활성화합니다. 파이프라인이 여러 리포지토리를 체크아웃하는 경우 각 체크아웃 시간을 별도로 측정하여 느린 종속성 리포지토리가 일반 "SCM" 단계 내에 숨겨지지 않도록 합니다.

작업 공간 재사용은 절충안입니다. 작업 공간을 재사용하면 git fetch가 훨씬 빨라질 수 있지만 오래된 파일로 인해 이상한 오류가 발생할 수 있습니다. 모든 빌드 전에 작업 공간을 지우는 것은 깔끔하지만 대규모 모노리포의 경우 비용이 많이 들 수 있습니다. 실용적인 중간 지점은 .git 디렉토리를 유지하면서 추적되지 않은 파일을 제거하는 정리 체크아웃 명령을 사용하거나 실패한 빌드 및 예약된 정리를 위해 전체 작업 공간 지우기를 예약하는 것입니다.

바쁜 에이전트에서 체크아웃 속도는 디스크 문제일 수도 있습니다. 10개의 빌드가 동일한 작은 볼륨에서 대규모 리포지토리를 복제하는 경우 CPU는 괜찮아 보이지만 디스크 I/O는 포화 상태일 수 있습니다. 빌드가 실행되는 동안 iostat, 클라우드 볼륨 메트릭 또는 에이전트의 스토리지 대시보드를 확인합니다. 작업 공간을 더 빠른 로컬 SSD 스토리지로 이동하면 Jenkins 설정보다 빌드 시간이 더 많이 변경될 수 있습니다.

종속성 캐시는 소유권이 필요함

캐싱은 누군가가 소유할 때만 도움이 됩니다. 무작위로 사라지거나, 제한 없이 커지거나, 호환되지 않는 도구 버전을 혼합하는 캐시는 저장하는 것보다 더 많은 문제를 일으킬 수 있습니다.

Maven 및 Gradle의 경우 영구 로컬 리포지토리 또는 빌드 캐시는 반복적인 다운로드를 줄일 수 있습니다. 캐시는 폐기 가능한 작업 공간 외부에 있어야 합니다. 또한 동시 빌드에 안전해야 합니다. Maven의 로컬 리포지토리는 일반적인 종속성 읽기에 일반적으로 괜찮지만 중단된 다운로드는 잘못된 파일을 남길 수 있습니다. 체크섬 오류 또는 손상된 아티팩트가 표시되면 습관적으로 전체 캐시를 삭제하는 대신 특정 종속성 경로를 지웁니다.

npm의 경우 재현 가능한 설치를 위해 npm ci를 선호하고 운영 체제, CPU 아키텍처, Node 버전 및 lockfile이 안정적이라는 것을 알지 못하는 한 node_modules 대신 npm 패키지 캐시를 캐시합니다. 다른 에이전트 이미지에서 node_modules를 캐싱하는 것은 CI에서만 발생하는 네이티브 모듈 오류를 얻는 고전적인 방법입니다.

Docker 빌드의 경우 가장 가치 있는 캐시는 일반적으로 레이어 캐시입니다. Dockerfile에서 소스 코드 복사 단계 전에 안정적인 종속성 설치 단계를 배치합니다. Docker 데몬이 빌드 포드당 격리되어 있고 매번 비어 있는 상태로 시작되면 로컬 레이어 캐싱이별로 도움이 되지 않습니다. 이 경우 환경이 지원하는 경우 BuildKit 캐시 내보내기/가져오기 또는 레지스트리 지원 캐시를 사용합니다.

테스트 시간: 신중하게 병렬화

테스트는 종종 정상적인 파이프라인에서 가장 긴 부분입니다. 목표는 단순히 더 많은 것을 병렬로 실행하는 것이 아닙니다. 목표는 불안정한 결과를 만들지 않고 피드백을 단축하는 것입니다.

단위 테스트는 일반적으로 잘 병렬화됩니다. 통합 테스트는 데이터베이스, 포트, 대기열, 버킷 또는 외부 계정을 공유할 수 있기 때문에 더 까다롭습니다. 두 테스트 브랜치가 동일한 스키마에 쓰거나 동일한 대기열 이름을 재사용하는 경우 병렬 실행은 파이프라인을 더 빠르게 만들면서 동시에 덜 안정적으로 만들 수 있습니다. 가능하면 각 브랜치에 자체 네임스페이스, 데이터베이스 스키마, 임시 디렉토리 및 서비스 포트 범위를 제공합니다.

테스트 스위트를 파일 수가 아닌 측정된 기간으로 분할합니다. 10개의 작은 테스트 파일이 하나의 큰 브라우저 테스트보다 빠르게 실행될 수 있습니다. 많은 팀이 테스트 기간을 기록하고 각 병렬 브랜치가 거의 동일한 시간이 걸리도록 그룹을 균형 조정하여 더 나은 결과를 얻습니다.

또한 느린 실패를 주시하세요. 죽은 서비스를 10분 동안 기다렸다가 실패하는 테스트 단계는 명확한 상태 확인으로 30초 안에 실패하는 단계보다 더 나쁩니다. 긴 테스트 명령 전에 명시적인 준비 확인을 수행하고 중단될 수 있는 네트워크 호출에 시간 제한을 설정합니다.

컨트롤러 상태는 여전히 중요함

빌드 작업은 에이전트에 속하지만 컨트롤러는 여전히 작업을 예약하고, 로그를 제공하고, 파이프라인 로직을 평가하고, 플러그인을 로드하고, UI 트래픽을 처리합니다. 컨트롤러가 과부하되면 에이전트에 여유 용량이 있어도 모든 작업이 느리게 느껴집니다.

느린 UI 페이지, 지연된 콘솔 로그 업데이트, 긴 가비지 수집 일시 중지 및 높은 컨트롤러 CPU를 찾으세요. 대규모 파이프라인 로그, 너무 많이 보관된 빌드, 공격적인 폴링 및 무거운 플러그인이 모두 부하를 추가할 수 있습니다. 빌드 보존을 현실적으로 유지합니다. 사람들이 필요로 하는 아티팩트만 보관합니다. Jenkins 홈 볼륨이 어려움을 겪고 있는 경우 대규모 테스트 보고서와 로그를 외부 스토리지로 이동합니다.

컨트롤러에서 빌드를 실행하지 마세요. 작은 작업에는 해롭지 않게 보일 수 있지만 사고를 추론하기 더 어렵게 만듭니다. 컨트롤러는 조정해야 합니다. 에이전트는 컴파일, 테스트, 패키징 및 배포해야 합니다.

실용적인 작업 순서

팀이 Jenkins가 왜 느린지 물을 때 이 순서를 사용하세요:

  1. 대기 시간과 실행 시간을 측정합니다.
  2. 최근 빌드에서 가장 느린 단계를 찾습니다.
  3. 느린 실행과 정상 실행을 비교합니다.
  4. 지연이 대기, 체크아웃, 종속성 다운로드, 테스트, 패키징 또는 배포인지 확인합니다.
  5. 하나의 병목 현상을 수정하고 다시 측정합니다.

마지막 단계가 중요합니다. 체크아웃이 6분에서 1분으로 떨어지면 잠시 축하하고 계속 측정합니다. 다음 병목 현상이 보일 것입니다. CI 성능 작업은 일반적으로 하나의 마법 설정보다는 작고 검증된 개선 사항의 연속입니다.