Published on

Jenkins 에이전트 오프라인·대기열 지옥 탈출

Authors

Jenkins를 운영하다 보면 어느 날 갑자기 빌드가 Queue 에 쌓이고, 에이전트는 offline 으로 떨어지며, 파이프라인은 끝없이 Waiting for next available executor 만 반복하는 순간이 옵니다. 이 상태가 길어지면 배포가 멈추고, 개발팀은 “Jenkins가 죽었다”는 결론을 내립니다.

하지만 대부분은 Jenkins 자체 장애가 아니라 에이전트 연결 경로, 라벨 매칭, 리소스 부족, 인증서/프록시, Kubernetes 스케줄링, 플러그인/버전 불일치 같은 운영 이슈가 겹쳐서 생깁니다. 이 글은 “대기열 지옥”을 재현 가능한 체크리스트로 쪼개고, 빠르게 정상화하는 방법을 정리합니다.

1) 증상부터 분류: 오프라인과 대기열은 원인이 다르다

먼저 화면에서 보이는 증상을 2가지로 분리합니다.

1-1. 에이전트가 오프라인인 경우

  • 노드가 offline 으로 표시됨
  • Launch agent 실패 로그가 남음
  • 에이전트 프로세스는 떠 있는데도 Jenkins에서 끊김

핵심은 컨트롤러에서 에이전트로의 연결이 성립하지 않거나, 연결이 유지되지 않는 것입니다.

1-2. 에이전트는 온라인인데 빌드가 대기열에 쌓이는 경우

  • 노드는 online 이지만 executor가 0이거나 모두 바쁨
  • 라벨이 맞지 않아 할당 불가
  • 리소스/스케줄링 때문에 새 에이전트가 뜨지 않음

이 경우는 스케줄링/자원/라벨/동시성 정책 문제가 많습니다.

2) 가장 먼저 확인할 5가지: “즉시 효과” 체크리스트

아래는 현장에서 가장 빈도가 높은 순서입니다.

2-1. 라벨 불일치: 대기열의 1순위 범인

파이프라인이 특정 라벨을 요구하는데, 그 라벨을 가진 노드가 없거나 executor가 0이면 무한 대기가 발생합니다.

Jenkinsfile 예:

pipeline {
  agent { label 'linux-docker' }
  stages {
    stage('Build') {
      steps {
        sh 'uname -a'
      }
    }
  }
}

확인 포인트:

  • Manage JenkinsNodes 에서 해당 라벨을 가진 노드가 실제로 존재하는가
  • 노드 설정에서 # of executors 가 0으로 되어 있지 않은가
  • Restrict where this project can be run 같은 제한이 걸려 있지 않은가

2-2. executor 고갈: “온라인인데도 못 도는” 상황

노드가 온라인이어도 executor가 꽉 차면 대기열이 쌓입니다.

대응:

  • 단기: executor 수를 늘리거나, 병렬 빌드를 줄이기
  • 중기: 빌드 시간을 줄이기(캐시, 아티팩트 재사용)
  • 장기: 오토스케일 에이전트(예: Kubernetes 플러그인)로 확장

2-3. 에이전트 런처 방식 문제: SSH vs JNLP(WebSocket)

에이전트가 오프라인이면 런처부터 봐야 합니다.

  • SSH 런처: 컨트롤러에서 에이전트로 22 포트 접근 필요
  • JNLP 인바운드: 에이전트가 컨트롤러로 붙음(방화벽에 유리)
  • WebSocket: HTTP 기반이라 네트워크 제약이 적고 프록시 환경에서 유리

권장 방향:

  • 사내망/보안망/클라우드 혼재 환경에서는 인바운드 + WebSocket 조합이 운영 난이도가 낮습니다.

2-4. DNS/HTTPS/프록시 문제: “연결은 되는데 작업 중 끊김”

에이전트가 온라인으로 보이다가도, 실제 빌드 단계에서 git clone 이나 패키지 다운로드가 실패하면서 작업이 꼬이고 재시도 루프가 생깁니다. 그 결과 executor가 장시간 점유되어 대기열이 늘어납니다.

특히 Kubernetes/EKS에서 흔한 패턴은 “DNS는 되는데 외부 HTTPS만 실패” 같은 네트워크 정책/라우팅/프록시 이슈입니다. 비슷한 증상은 아래 글의 점검 흐름이 그대로 도움이 됩니다.

2-5. 인증서/CA 문제: 에이전트가 TLS에서 죽는 케이스

사내 프록시나 MITM 장비가 있으면, 에이전트가 외부로 나갈 때 CERTIFICATE_VERIFY_FAILED 로 실패하고 빌드가 지속적으로 깨질 수 있습니다.

Python 기반 도구를 쓰는 빌드라면 아래 글의 해결법(사내 CA 주입, 신뢰 저장소 설정)이 그대로 적용됩니다.

3) 로그로 원인 확정: 컨트롤러 로그와 에이전트 로그를 같이 본다

대기열 지옥 탈출의 핵심은 “감”이 아니라 로그로 원인을 확정하는 것입니다.

3-1. 컨트롤러(마스터) 로그에서 보는 포인트

  • 노드 연결 실패 메시지
  • Remoting 채널 끊김
  • 플러그인 예외

Docker로 Jenkins를 띄운 경우 예:

docker logs -f jenkins

Systemd 서비스라면 예:

journalctl -u jenkins -f

3-2. 에이전트 로그에서 보는 포인트

  • JVM 버전 불일치
  • Remoting jar 호환 문제
  • 네트워크 끊김, EOF 류 메시지

인바운드 에이전트 실행 예:

java -jar agent.jar \
  -url https://jenkins.example.com/ \
  -secret YOUR_SECRET \
  -name agent-01 \
  -workDir /var/jenkins

주의: URL이나 토큰을 출력 로그에 그대로 남기지 않도록 마스킹 정책을 권장합니다.

4) Jenkins 대기열이 “영원히” 빠지지 않는 대표 시나리오 7가지

4-1. 노드가 실제로는 죽었는데 Jenkins가 “온라인”으로 착각

네트워크가 반쯤 죽으면 UI는 온라인처럼 보일 수 있습니다.

대응:

  • 노드에 ping 만 하지 말고, 실제 작업(예: git ls-remote)을 수행하는 헬스체크를 별도로 둡니다.
  • 일정 시간 이상 무응답이면 노드를 강제로 offline 처리하고 새 노드를 띄우는 정책이 필요합니다.

4-2. Workspace 잠금/파일 락으로 스텝이 멈춤

동일 워크스페이스를 여러 빌드가 공유하면 락 경합이 생깁니다.

해결:

  • 파이프라인에서 disableConcurrentBuilds() 사용
  • 혹은 빌드별 워크스페이스 분리

예:

options {
  disableConcurrentBuilds()
}

4-3. Docker 데몬/소켓 권한 문제로 빌드가 무한 재시도

에이전트가 도커 빌드를 수행하는데 권한이 없으면, 스텝이 실패하고 재시도 로직이 executor를 오래 점유합니다.

확인:

id
ls -l /var/run/docker.sock

대응:

  • 도커 그룹 권한 정리 또는 rootless Docker 고려
  • Kubernetes라면 DinD 대신 Kaniko, BuildKit, podman 등 대안 검토

4-4. Kubernetes 에이전트가 Pending에서 못 뜸

Kubernetes 플러그인을 쓰면 “에이전트 오프라인”이 아니라 “에이전트가 생성되지 않음”이 원인일 때가 많습니다.

확인:

kubectl get pods -n jenkins -w
kubectl describe pod -n jenkins jenkins-agent-xxxx

자주 나오는 원인:

  • CPU/메모리 requests가 커서 스케줄링 불가
  • 이미지 풀 실패
  • 노드 셀렉터/테인트/톨러레이션 불일치
  • 서비스어카운트 권한 부족

권한 문제는 아래 글의 점검 흐름(토큰, RBAC, 인증 실패)을 참고하면 빠릅니다.

4-5. WebSocket/프록시 환경에서 연결이 자주 끊김

사내 프록시가 WebSocket 업그레이드를 막으면 에이전트가 간헐적으로 끊깁니다.

대응:

  • 프록시에서 WebSocket 허용
  • Jenkins URL과 에이전트 접속 경로를 단순화
  • 가능하면 L4 수준에서 안정적인 경로 제공

4-6. Remoting/JDK 버전 불일치

컨트롤러는 최신인데 에이전트는 구형 JDK를 쓰면, 연결은 되다가 특정 클래스 로딩에서 터지기도 합니다.

권장:

  • 컨트롤러와 에이전트의 최소 JDK 버전을 정책으로 고정
  • 에이전트 이미지를 표준화

4-7. “대기열 폭발” 자체가 정상 동작인 경우

CI 트래픽이 급증했는데 에이전트 풀을 고정으로 운영하면 대기열이 늘어나는 건 정상입니다.

해결은 운영 정책입니다.

  • 피크 시간대에만 자동 확장
  • 우선순위 큐(중요 브랜치 우선)
  • 빌드 단축(캐시, 병렬화 최적화)

5) 실전 복구 절차: 지금 당장 서비스 정상화

장애 상황에서 목표는 “완벽한 원인 분석”이 아니라 파이프라인을 다시 흐르게 만드는 것입니다.

5-1. stuck 빌드 정리: executor 점유 해제

  • 오래된 빌드/멈춘 스텝을 중지
  • 필요하면 노드를 일시적으로 offline 후 다시 online

Groovy 콘솔에서 오래된 실행을 찾는 예(환경에 맞게 수정 필요):

import jenkins.model.*

def now = System.currentTimeMillis()
Jenkins.instance.computers.each { c ->
  c.executors.each { e ->
    def exec = e.currentExecutable
    if (exec != null) {
      println("RUNNING on ${c.name}: ${exec}")
    }
  }
}

주의: 운영 환경에서 Groovy 콘솔은 강력한 권한이므로 접근 통제와 감사 로그가 필요합니다.

5-2. 에이전트 “재기동”이 아니라 “재생성”

VM 기반 고정 에이전트는 시간이 지날수록 드리프트가 생깁니다.

  • 단기: 에이전트 서비스 재시작
  • 중기: 골든 이미지로 재배포
  • 장기: 불변 인프라(컨테이너 에이전트)로 전환

5-3. 네트워크/CA 문제는 빌드 단계에서 바로 검증

에이전트에 접속해서 아래를 실행해 외부 의존성이 정상인지 확인합니다.

# DNS
getent hosts github.com

# HTTPS
curl -I https://github.com

# Git
git ls-remote https://github.com/git/git

여기서 실패하면 Jenkins 문제가 아니라 에이전트 런타임 네트워크 문제일 확률이 큽니다.

6) 재발 방지: “대기열 지옥”을 구조적으로 없애는 설계

6-1. 에이전트 표준 이미지와 부트스트랩 스크립트

  • JDK, Git, 빌드 툴, CA 번들, 프록시 설정을 표준화
  • 빌드마다 셋업하지 말고 이미지에 넣어 시간을 줄임

Dockerfile 예:

FROM eclipse-temurin:17-jdk

RUN apt-get update \
  && apt-get install -y --no-install-recommends git curl ca-certificates \
  && rm -rf /var/lib/apt/lists/*

# 사내 CA가 있다면 여기에 추가(파일명은 예시)
# COPY corp-ca.crt /usr/local/share/ca-certificates/corp-ca.crt
# RUN update-ca-certificates

WORKDIR /work

6-2. 오토스케일 전략: “큐 길이 기반”으로 확장

  • 큐 길이, 평균 대기 시간, executor 사용률을 메트릭으로 수집
  • 임계치 초과 시 에이전트 풀 확장

Kubernetes라면 HPA만으로는 부족할 수 있어, KEDA 같은 이벤트 기반 스케일링이나 Jenkins 플러그인 설정을 조합합니다.

6-3. 타임아웃과 재시도 정책을 파이프라인에 명시

무한 대기는 운영자를 지치게 합니다. 스텝별 타임아웃과 제한된 재시도로 “실패를 빨리 실패”하게 만들어야 합니다.

pipeline {
  agent any
  options {
    timeout(time: 30, unit: 'MINUTES')
  }
  stages {
    stage('Checkout') {
      options {
        timeout(time: 5, unit: 'MINUTES')
      }
      steps {
        retry(2) {
          checkout scm
        }
      }
    }
  }
}

6-4. 관측성: Jenkins 큐와 노드 상태를 대시보드화

  • 큐 길이
  • 노드 온라인 수
  • executor 사용률
  • 빌드 평균 시간
  • 실패율(특히 네트워크 계열)

이 지표가 있어야 “느낌상 느려졌다”가 아니라 “어디가 병목인지”를 숫자로 말할 수 있습니다.

7) 결론: 오프라인과 대기열은 ‘운영 신호’다

Jenkins 에이전트 오프라인과 대기열 폭증은 단순 장애가 아니라, CI 시스템이 보내는 운영 신호입니다. 라벨/executor 같은 설정 문제부터 네트워크/인증서/스케줄링까지 원인을 체계적으로 분해하면, 대부분은 30분 이내에 정상화할 수 있습니다.

정리하면 다음 순서가 가장 효율적입니다.

  1. 대기열 항목에서 라벨 요구사항과 executor 상태 확인
  2. 에이전트 런처(SSH, JNLP, WebSocket)와 네트워크 경로 확인
  3. 에이전트에서 DNS, HTTPS, Git 같은 “실제 빌드 의존성” 검증
  4. Kubernetes라면 Pending 원인을 describe 로 확정
  5. 재발 방지를 위해 표준 이미지, 타임아웃/재시도, 오토스케일, 관측성 구축

이 흐름으로 접근하면 “Jenkins가 또 멈췄다”가 아니라 “어느 레이어가 병목인지”를 빠르게 특정하고, 대기열 지옥에서 벗어날 수 있습니다.