- Published on
Jenkins 빌드가 멈출 때 - 에이전트 오프라인 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
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) 특정 단계에서만 간헐적으로 멈춤
- 예: Docker build/push, S3 업로드, 외부 API 호출
- 이 경우 에이전트 오프라인이 아니라 네트워크 egress 문제가 원인일 수도 있습니다. EKS 기반 에이전트라면 아래 글의 네트워크 진단 루틴이 바로 도움됩니다.
10분 안에 하는 1차 진단 루틴(체크리스트)
아래 순서대로 보면 “에이전트 오프라인”을 빠르게 확정/배제할 수 있습니다.
1) Jenkins UI에서 노드 상태와 원인 메시지 확인
Manage Jenkins→Nodes(또는Manage Nodes and Clouds)- 해당 노드 클릭 → 상태/로그
확인 포인트
Offline인지,Temporarily Offline인지- 오프라인 사유(관리자가 수동으로 오프라인 처리했는지)
Launch agent via ...설정(SSH/JNLP/Inbound 등)
2) Controller 시스템 로그에서 연결 실패 흔적 확인
Manage Jenkins→System 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 ForbiddenCould 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 빌드가 멈출 때 “파이프라인이 문제다”라고 단정하기 전에, 에이전트 오프라인/반쯤 죽은 상태를 먼저 의심하는 것이 평균 복구 시간을 크게 줄입니다.
핵심은 단순합니다.
- UI에서 노드 상태와 라벨/Executor부터 확인
- 컨트롤러 로그로 연결 실패 패턴을 확정
- 에이전트 리소스(디스크/OOM)와 네트워크(SSH/JNLP/HTTPS)를 재현 가능한 커맨드로 검증
- 타임아웃·재시도·ephemeral agent로 재발을 구조적으로 줄이기
이 루틴을 팀의 런북으로 정리해두면, “가끔 멈추는 Jenkins”가 아니라 “원인을 빠르게 추적하고 자동 복구되는 Jenkins”에 가까워집니다.