Published on

Jenkins 빌드가 멈출 때 - 에이전트 오프라인 진단

Authors

서론

Jenkins에서 빌드가 Queue에서 빠지지 않거나(‘Waiting for next available executor’), 실행은 시작했는데 콘솔 로그가 더 이상 진행되지 않는 상황을 겪으면 대부분 “파이프라인 문제”부터 의심하게 됩니다. 하지만 실제 현장에서는 에이전트(Agent/Node) 오프라인 혹은 “온라인인데 사실상 죽어있는 상태(half-dead)”가 훨씬 자주 원인입니다.

이 글은 다음을 목표로 합니다.

  • 빌드 멈춤을 에이전트 오프라인 관점에서 빠르게 분류
  • Jenkins UI/시스템 로그/에이전트 로그에서 확증 증거를 찾는 순서 제시
  • SSH 에이전트, JNLP(WebSocket) 에이전트, Docker, Kubernetes 에이전트별 대표 장애 패턴 정리
  • 재발 방지(타임아웃, 헬스체크, 자동 재연결/자동 교체)까지 연결

증상으로 먼저 분류하기

에이전트 오프라인 이슈는 보통 아래 3가지 증상으로 나타납니다.

1) Queue에만 쌓이고 시작을 못함

  • 메시지 예: Waiting for next available executor / There are no nodes with the label ‘linux’
  • 원인 후보
    • 해당 라벨 노드가 모두 오프라인
    • executor가 0이거나 모두 busy
    • 노드가 online 표시여도 실제 연결이 끊겨 executor 할당이 실패

2) 빌드가 시작했는데 콘솔이 멈춤

  • 예: git fetch에서 멈춘 듯 보이지만 사실은 에이전트가 죽어 stdout이 더 이상 올라오지 않음
  • 원인 후보
    • 에이전트 프로세스(JNLP) 크래시/OOM
    • 디스크 full로 workspace 작업 중단
    • 네트워크 단절(컨트롤러↔에이전트)

3) 특정 단계에서만 간헐적으로 멈춤

10분 안에 하는 1차 진단 루틴(체크리스트)

아래 순서대로 보면 “에이전트 오프라인”을 빠르게 확정/배제할 수 있습니다.

1) Jenkins UI에서 노드 상태와 원인 메시지 확인

  • Manage JenkinsNodes(또는 Manage Nodes and Clouds)
  • 해당 노드 클릭 → 상태/로그

확인 포인트

  • Offline인지, Temporarily Offline인지
  • 오프라인 사유(관리자가 수동으로 오프라인 처리했는지)
  • Launch agent via ... 설정(SSH/JNLP/Inbound 등)

2) Controller 시스템 로그에서 연결 실패 흔적 확인

  • Manage JenkinsSystem Log
  • 또는 컨트롤러 파일 로그(배포 방식에 따라 다름)

자주 보이는 패턴

  • SSH: java.io.IOException: Failed to connect / Connection timed out
  • JNLP: Agent disconnected / EOFException / ChannelClosedException
  • WebSocket: 프록시/로드밸런서에서 idle timeout으로 끊김

3) 에이전트 머신/컨테이너에서 프로세스와 리소스 확인

에이전트가 VM/물리 머신이면 다음을 바로 확인합니다.

# 에이전트가 살아있는지
ps aux | egrep 'agent.jar|remoting' | grep -v grep

# 디스크/메모리/로드
df -h
free -m
uptime

# 최근 OOM/디스크 관련 커널 로그
dmesg -T | tail -n 50

특히 **디스크 100%**는 빌드가 “멈춘 것처럼” 보이게 만드는 단골 원인입니다. 로그 로테이션 이후에도 디스크가 안 줄어드는 케이스(삭제된 파일을 프로세스가 붙잡고 있음)까지 포함해 점검이 필요하면 아래 글의 패턴이 그대로 적용됩니다.

4) 네트워크 단절 여부(컨트롤러↔에이전트)

컨트롤러에서 에이전트로(또는 그 반대) 연결이 가능한지 확인합니다.

  • SSH 에이전트라면(컨트롤러→에이전트)
# 컨트롤러에서
nc -vz <agent-ip> 22
ssh -vvv jenkins@<agent-ip>
  • JNLP inbound라면(에이전트→컨트롤러)
# 에이전트에서
nc -vz <controller-host> 50000
curl -vk https://<controller-host>/login

Kubernetes/EKS에서 Pod 기반 에이전트라면, “DNS는 되는데 HTTPS만 실패” 같은 케이스도 빈번합니다.

원인별로 더 깊게 파고들기

1) 라벨/Executor/Throttle 문제로 ‘사실상 오프라인’처럼 보이는 경우

에이전트가 온라인이어도 다음 조건이면 빌드는 계속 대기합니다.

  • 파이프라인이 agent { label 'linux' }를 요구하지만 해당 라벨 노드가 없음
  • 노드 executor가 0
  • Disable executors로 막혀 있음
  • Throttle Concurrent Builds/folder-level 제한

확인 및 재현 가능한 점검

pipeline {
  agent { label 'linux' }
  stages {
    stage('Check') {
      steps {
        sh 'echo hello'
      }
    }
  }
}

위 파이프라인이 Queue에서 멈춘다면, 가장 먼저 linux 라벨을 가진 노드가 실제로 executor를 제공하는지 확인합니다.

2) SSH 에이전트: 키/known_hosts/서버 설정 변경

SSH 방식은 “어제까지 되다가 오늘부터 안 됨”이 잦습니다.

대표 원인

  • 서버 재설치로 host key 변경 → known_hosts mismatch
  • PasswordAuthentication no 등 sshd 설정 변경
  • 보안그룹/NACL 변경으로 22 차단
  • Jenkins SSH credential 교체 누락

컨트롤러에서 verbose SSH로 원인을 고정합니다.

ssh -vvv -i /path/to/key jenkins@<agent-ip>

Jenkins 노드 설정에서 Host Key Verification Strategy가 엄격한 경우, host key 변경 시 오프라인이 됩니다. 운영 환경에서는 “무조건 느슨하게”가 정답은 아니고, **변경 프로세스(재설치 시 host key 업데이트)**를 표준화하는 게 안전합니다.

3) JNLP(Inbound) 에이전트: remoting/인증/시간 불일치

Inbound 에이전트는 에이전트가 컨트롤러로 붙어야 합니다.

대표 원인

  • 에이전트 토큰/secret 변경
  • 컨트롤러 URL 변경(리버스 프록시, HTTPS 전환)
  • 시간 불일치로 TLS/토큰 검증 문제
  • 방화벽에서 50000 포트 또는 WebSocket 경로 차단

에이전트 실행 커맨드 예시(컨테이너/VM)

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

로그에서 아래가 보이면 거의 확정입니다.

  • Handshake failed / Invalid secret / 403 Forbidden
  • Could not resolve host(DNS)
  • Read timed out(네트워크/프록시)

4) Kubernetes Pod 에이전트: Pod는 떠 있는데 연결이 안 되는 경우

Kubernetes 플러그인을 쓰면 “Pod Pending/CrashLoopBackOff/Running인데 Offline” 등 다양한 형태가 나옵니다.

(1) Pod Pending

  • 원인: 리소스 부족, 스케줄링 불가(taint/toleration), 이미지 pull 실패
kubectl get pod -n jenkins -w
kubectl describe pod <pod> -n jenkins

(2) Pod Running인데 Jenkins에서는 Offline

  • 원인: JNLP 컨테이너가 죽었거나, 컨트롤러로 outbound가 막힘
kubectl logs -n jenkins <pod> -c jnlp --tail=200
kubectl exec -n jenkins <pod> -c jnlp -- sh -lc 'nc -vz jenkins.example.com 443'

여기서 TLS handshake timeout이 보이면 네트워크 계층 이슈일 가능성이 큽니다.

5) 디스크/워크스페이스 문제: “멈춤”으로 보이는 고전 케이스

에이전트 디스크가 꽉 차면 Git checkout, Docker build, npm/yarn 캐시 등에서 진행이 멈춘 듯 보입니다.

점검

df -h
# Jenkins workspace 경로가 /var/lib/jenkins/workspace 인지 등 확인
ls -alh /var/lib/jenkins/workspace | head

대응

  • Workspace 정리(빌드 후 clean)
  • 아티팩트 보관 정책 조정
  • Docker 이미지/레이어 prune 자동화

파이프라인에서 최소한의 방어막을 깔아두는 것도 방법입니다.

pipeline {
  agent any
  options {
    timeout(time: 30, unit: 'MINUTES')
  }
  stages {
    stage('Preflight') {
      steps {
        sh '''
          set -e
          df -h
          # 여유 공간이 5GB 미만이면 실패
          avail=$(df -Pk . | tail -1 | awk '{print $4}')
          if [ "$avail" -lt $((5*1024*1024)) ]; then
            echo "Not enough disk space" >&2
            exit 1
          fi
        '''
      }
    }
    stage('Build') {
      steps {
        sh 'make build'
      }
    }
  }
}

6) 컨트롤러 과부하/GC/스레드 고갈로 에이전트가 끊기는 경우

에이전트가 문제처럼 보이지만 컨트롤러가 병목이면 다음이 발생합니다.

  • 연결은 유지되는데 UI가 느리고, 빌드 로그 스트리밍이 끊김
  • hudson.remoting.ChannelClosedException 빈발
  • 대규모 폴링/웹훅 폭주, 플러그인 이슈로 스레드 고갈

대응

  • 컨트롤러 JVM 메모리/GC 로그 확인
  • 플러그인 업데이트/비활성화 검토
  • 빌드 실행은 에이전트로 분산하고 컨트롤러는 “오케스트레이션”에 집중

재발 방지: ‘오프라인’을 빨리 감지하고 자동 복구하기

1) 타임아웃과 재시도(파이프라인 레벨)

에이전트가 죽으면 빌드는 영원히 기다릴 수 있습니다. 최소한의 안전장치:

pipeline {
  agent { label 'linux' }
  options {
    timeout(time: 20, unit: 'MINUTES')
    disableConcurrentBuilds()
  }
  stages {
    stage('Checkout') {
      steps {
        retry(2) {
          checkout scm
        }
      }
    }
    stage('Test') {
      steps {
        sh 'pytest -q'
      }
    }
  }
}

2) 에이전트는 ‘가축’처럼: 불안정하면 교체 가능한 구조

  • VM 고정 에이전트보다, 가능하면 Kubernetes ephemeral agent로 전환
  • Docker 기반이라면 빌드 컨테이너를 매번 새로 띄우고, 캐시는 외부로

3) 연결 경로 단순화: WebSocket 고려

방화벽/프록시가 복잡한 환경에서는 JNLP TCP(50000)보다 WebSocket이 운영 난이도를 낮추기도 합니다. 다만 L7 프록시 idle timeout(예: 60s/300s) 설정이 낮으면 오히려 더 자주 끊길 수 있으니, 컨트롤러 앞단(ALB/Nginx/Ingress)의 타임아웃을 점검해야 합니다.

4) 관측성: “오프라인”을 이벤트로 만들기

  • 노드 오프라인 이벤트를 Slack/Email로 알림
  • Kubernetes라면 Pod 이벤트/CrashLoop를 알림
  • 컨트롤러/에이전트 로그를 중앙 수집(ELK/CloudWatch 등)

결론

Jenkins 빌드가 멈출 때 “파이프라인이 문제다”라고 단정하기 전에, 에이전트 오프라인/반쯤 죽은 상태를 먼저 의심하는 것이 평균 복구 시간을 크게 줄입니다.

핵심은 단순합니다.

  1. UI에서 노드 상태와 라벨/Executor부터 확인
  2. 컨트롤러 로그로 연결 실패 패턴을 확정
  3. 에이전트 리소스(디스크/OOM)와 네트워크(SSH/JNLP/HTTPS)를 재현 가능한 커맨드로 검증
  4. 타임아웃·재시도·ephemeral agent로 재발을 구조적으로 줄이기

이 루틴을 팀의 런북으로 정리해두면, “가끔 멈추는 Jenkins”가 아니라 “원인을 빠르게 추적하고 자동 복구되는 Jenkins”에 가까워집니다.