Published on

Jenkins 에이전트 오프라인 원인 6가지와 복구

Authors

서버가 멀쩡해 보이는데 Jenkins 에이전트가 갑자기 Offline으로 바뀌면, 원인 범위가 생각보다 넓습니다. 네트워크 단절 같은 단순 이슈부터, JVM/OS 자원 고갈, systemd 재시작 루프, SSH 키/권한 문제, JNLP(WebSocket) 핸드셰이크 실패, 컨테이너/쿠버네티스 환경의 노드 드레인까지 다양합니다.

이 글은 “증상 → 확인 포인트 → 즉시 복구 → 재발 방지” 흐름으로, 현장에서 가장 자주 만나는 오프라인 원인 6가지를 정리합니다. (에이전트 유형은 SSH, JNLP(Inbound), Docker/Kubernetes 기반까지 포괄)

먼저: Offline 유형부터 분류하기

에이전트가 Offline일 때 Jenkins UI의 노드 상세에서 보이는 문구가 힌트입니다.

  • Agent is offline / Disconnected : 연결 끊김(네트워크, 프로세스 종료 등)
  • Connection was broken / Channel is closing down : JVM/프로세스 크래시, 강제 종료, OOM
  • Authentication failed : SSH 키/권한/사용자 문제
  • Handshake failed / JNLP4-connect 실패 : 인바운드 에이전트 설정/시크릿/프로토콜 문제
  • Launch failed : 에이전트 실행 커맨드/경로/Java 문제

빠른 체크: 컨트롤러 로그와 에이전트 로그 위치

  • Jenkins Controller 로그: Manage Jenkins → System Log, 또는 컨테이너/서비스 로그
  • SSH 에이전트: 컨트롤러에서 SSH 연결 시도 로그 + 에이전트 서버의 /var/log/auth.log(Ubuntu) / /var/log/secure(RHEL)
  • JNLP(Inbound) 에이전트: 에이전트 프로세스 stdout/stderr(서비스/컨테이너 로그)

원인 1) 네트워크 단절/DNS 문제/방화벽(가장 흔함)

전형적인 증상

  • Jenkins에서 간헐적으로 Offline ↔ Online 반복
  • SSH는 되는데 Jenkins만 끊기거나, 반대로 Jenkins에서만 접속 실패
  • java.io.IOException: Connection timed out / No route to host / UnknownHostException

확인 포인트

  • 컨트롤러 → 에이전트 방향(SSH outbound) 또는 에이전트 → 컨트롤러 방향(JNLP inbound) 중 어느 쪽인지 확인
  • DNS: 컨트롤러/에이전트에서 서로의 FQDN이 해석되는지
  • 방화벽/보안그룹/NACL: 포트 열림 여부
    • SSH: 22
    • JNLP(TCP): 기본 50000(설정에 따라 다름)
    • WebSocket 사용 시: 443/80(리버스 프록시 경유)

즉시 복구 절차

  1. 컨트롤러에서 에이전트로 포트 체크(SSH 예시)
# 컨트롤러에서 실행
nc -vz agent01.example.com 22
  1. JNLP 포트 체크(에이전트가 컨트롤러로 붙는 구조)
# 에이전트에서 실행
nc -vz jenkins.example.com 50000
  1. DNS 확인
getent hosts jenkins.example.com
getent hosts agent01.example.com

재발 방지

  • 인바운드 에이전트는 가능하면 WebSocket 모드로 전환(프록시/방화벽 친화적)
  • 리버스 프록시(Nginx/ALB) 뒤라면 idle timeout(예: ALB 60s)로 장기 연결이 끊기는지 점검
  • 쿠버네티스/EKS 환경이면 노드/파드 네트워크 이슈도 함께 의심(Ingress/Service 레벨 문제는 EKS Ingress 503인데 Pod 정상일 때 점검 가이드도 참고)

원인 2) 에이전트 프로세스(JVM) 크래시 또는 OOM으로 종료

에이전트는 보통 agent.jar(또는 remoting.jar) 기반 Java 프로세스입니다. 메모리가 부족하거나(특히 빌드가 무거울 때), OS가 압박을 받으면 OOM으로 죽고 Offline이 됩니다.

전형적인 증상

  • Jenkins에는 Channel terminated / ClosedChannelException
  • 에이전트 호스트에서 Java 프로세스가 사라짐
  • 커널 로그에 OOM Killer 흔적

확인 포인트

에이전트 호스트에서 OOM 로그 확인:

# OOM Killer 이벤트 확인
journalctl -k --since "-6h" | egrep -i "oom|killed process|out of memory"

# 또는 dmesg
dmesg -T | egrep -i "oom|killed process|out of memory"

메모리 누수/스파이크 추적은 별도 정밀 진단이 필요합니다. Linux 관점에서의 로그 추적 방법은 Linux OOM Killer 로그 추적과 메모리 누수 진단에 더 자세히 정리되어 있습니다.

즉시 복구 절차

  • 에이전트 재기동(서비스/컨테이너)
  • 빌드 동시성 제한(특히 동일 에이전트에 과도한 병렬 빌드가 몰릴 때)

예: systemd로 에이전트를 운영한다면

sudo systemctl restart jenkins-agent
sudo systemctl status jenkins-agent --no-pager

재발 방지

  • 에이전트 JVM 옵션에 힙 상한 설정(예: -Xmx512m~워크로드에 맞게)
  • 빌드 툴(Gradle, Maven, Node, Docker build)의 메모리 옵션 분리
  • 컨테이너라면 resources.limits.memory 설정 및 OOMKilled 이벤트 모니터링

원인 3) systemd/프로세스가 재시작 루프에 빠짐

에이전트를 서비스로 띄워두는 경우, 설정 오류나 환경 변수 누락, 실행 파일 경로 문제로 시작→즉시 종료→재시작을 반복하며 Offline이 됩니다.

전형적인 증상

  • Jenkins에서는 계속 Offline
  • 호스트에서는 systemctl statusRestarting / Start request repeated too quickly

확인 포인트

sudo systemctl status jenkins-agent --no-pager
sudo journalctl -u jenkins-agent -n 200 --no-pager

재시작 루프의 근본 원인 추적 패턴은 systemd 서비스가 반복 재시작될 때 원인 추적법과 동일하게 접근하면 빠릅니다.

즉시 복구 절차

  • Unit 파일의 ExecStart 경로/권한/환경 변수 점검
  • Java 경로 문제라면 JAVA_HOME 또는 절대경로 사용

예시 unit(발췌):

[Service]
User=jenkins
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk"
ExecStart=/usr/lib/jvm/java-17-openjdk/bin/java -jar /opt/jenkins/agent.jar -jnlpUrl https://jenkins.example.com/computer/agent01/slave-agent.jnlp -secret @/opt/jenkins/secret -workDir /var/lib/jenkins
Restart=always
RestartSec=10

원인 4) SSH 기반 에이전트의 인증 실패(키/권한/알고리즘)

SSH Launch 방식은 단순하지만, 키 교체/권한 변경/SSH 서버 설정 강화로 쉽게 깨집니다.

전형적인 증상

  • Jenkins 노드 로그: Authentication failed / Permission denied (publickey)
  • /var/log/auth.log에 실패 기록 누적

확인 포인트

에이전트 서버에서 SSH 인증 로그:

sudo tail -n 200 /var/log/auth.log
# RHEL 계열
sudo tail -n 200 /var/log/secure

Jenkins 컨트롤러에서 같은 키로 수동 접속 테스트(컨트롤러에서 실행):

ssh -i /var/lib/jenkins/.ssh/id_rsa jenkins@agent01.example.com

즉시 복구 절차

  • Jenkins Credentials의 개인키가 최신인지 확인
  • 에이전트의 ~/.ssh/authorized_keys 갱신
  • 권한 교정
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

재발 방지

  • 키 로테이션 절차 문서화(교체 시 Jenkins Credentials 동시 갱신)
  • OpenSSH 업그레이드로 ssh-rsa 비활성화된 환경이면, ed25519 키로 전환

원인 5) JNLP(Inbound)/WebSocket 에이전트의 시크릿·URL·프록시 설정 오류

인바운드 에이전트는 -jnlpUrl, -secret, -name 등이 조금만 어긋나도 연결이 실패합니다. 또한 리버스 프록시가 WebSocket 업그레이드를 막거나, TLS 종단 설정이 꼬이면 핸드셰이크가 실패합니다.

전형적인 증상

  • 에이전트 로그에 Handshake failed / 403 / Invalid secret / Unknown client name
  • Jenkins UI에는 Agent failed to connect류 메시지

확인 포인트

  • 노드 상세 페이지에서 제공하는 정확한 실행 커맨드를 그대로 사용했는지
  • 컨트롤러 URL이 내부/외부에서 다르게 해석되며 리다이렉트가 발생하지 않는지
  • 프록시(Nginx/ALB)에서 WebSocket 헤더가 통과되는지

즉시 복구 절차

노드 상세의 커맨드를 그대로 복사해 실행(예: WebSocket 사용):

java -jar agent.jar \
  -url https://jenkins.example.com/ \
  -secret @/opt/jenkins/agent.secret \
  -name "agent01" \
  -workDir "/var/lib/jenkins" \
  -webSocket

Nginx를 쓴다면 WebSocket 업그레이드 설정 확인(예시):

location / {
  proxy_pass http://jenkins;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
}

재발 방지

  • 인바운드 에이전트는 가능하면 WebSocket로 표준화(포트 50000 의존 제거)
  • Jenkins URL/리버스프록시/인증서 체인을 변경할 때 에이전트 연결 테스트를 배포 파이프라인에 포함

원인 6) 디스크/워크스페이스 문제(용량 부족, inode 고갈, 권한 꼬임)

에이전트가 살아있어도, 워크스페이스에 체크아웃/빌드/아카이브 단계에서 실패가 누적되면 운영자가 “에이전트가 죽었다”고 오해하기 쉽습니다. 하지만 Jenkins는 특정 조건에서 에이전트를 일시적으로 사용 불가 상태로 표시하거나, 빌드가 반복 실패하며 사실상 Offline처럼 보이기도 합니다.

전형적인 증상

  • 빌드 로그: No space left on device, Disk quota exceeded, Permission denied
  • 에이전트는 Online인데 작업이 계속 대기/실패

확인 포인트

# 용량
df -h
# inode
df -i
# 워크스페이스 권한
ls -al /var/lib/jenkins
ls -al /var/lib/jenkins/workspace

즉시 복구 절차

  • 불필요한 워크스페이스/캐시 정리(특히 Docker 레이어, npm/gradle 캐시)
  • Jenkins의 Workspace Cleanup 플러그인 또는 파이프라인에서 정리 스텝 추가

파이프라인 예시(빌드 후 정리):

pipeline {
  agent { label 'linux-agent' }
  options { timestamps() }
  stages {
    stage('Build') {
      steps {
        sh 'make build'
      }
    }
  }
  post {
    always {
      sh 'docker system prune -af || true'
      cleanWs(deleteDirs: true, disableDeferredWipeout: true)
    }
  }
}

재발 방지

  • 에이전트 디스크 임계치 모니터링(예: 80%/90%)과 알림
  • 대형 리포/아티팩트는 별도 캐시/스토리지로 분리

공통 복구 플레이북(5분 내)

Offline을 발견했을 때, 아래 순서로 보면 대부분 1~2회전 안에 원인을 좁힐 수 있습니다.

  1. Jenkins 노드 화면에서 오프라인 사유/최근 로그 확인
  2. 연결 방식 확인(SSH vs Inbound/JNLP vs WebSocket)
  3. 네트워크: nc, curl, DNS 확인
  4. 에이전트 호스트에서 프로세스/로그 확인
    • ps aux | grep agent.jar
    • journalctl -u ...
    • OOM/디스크 확인
  5. 재기동 후에도 재발하면, 재시작 루프/리버스프록시/WebSocket/키 로테이션을 집중 점검

운영 팁: “Offline”을 줄이는 설계

  • 에이전트는 가능하면 불변(immutable) 하게: 컨테이너/이미지로 표준화하면 드리프트가 줄어듭니다.
  • 빌드 부하가 큰 작업은 전용 라벨로 격리(메모리/디스크 요구량이 다른 작업을 섞지 않기)
  • 컨트롤러/에이전트 모두에 대해
    • 시스템 로그 수집(journalctl, auth.log)
    • 자원 지표(CPU/메모리/디스크)
    • 네트워크 지표(연결 실패율, DNS 오류) 을 묶어두면, “왜 Offline인지”를 추측이 아니라 증거로 말할 수 있습니다.

위 6가지를 기준으로 분류하면, Jenkins 에이전트 Offline의 대부분은 네트워크/프로세스(OOM)/서비스 재시작/인증/JNLP 설정/디스크 중 하나로 떨어집니다. 현재 환경(SSH인지, JNLP인지, 쿠버네티스인지)과 Offline 화면의 문구/로그 한 줄만 있어도 더 구체적으로 케이스를 좁혀서 체크리스트를 맞춤형으로 정리해드릴 수 있습니다.