This job is stuck because you don't have any active runners online
or available with any of these tags assigned to them: neo4j
자사에서 서비스 운영 중 특정 프로젝트의 CI 파이프라인이 위 메시지와 함께 멈춰버렸습니다.
Job이 대기 상태로만 계속 머물러 있어서, Runner 자체에 문제가 있다는 것부터 확인해야 했습니다.
GitLab Runner 동작 방식부터 다시 짚어보기
원인을 찾기 전에 Runner가 어떻게 동작하는지부터 정리해봤습니다.
GitLab Runner는 GitLab 서버로부터 Job을 받아 실제로 실행하는 에이전트로, EC2 인스턴스에 설치되어 있습니다. 여기서 중요한 포인트는 GitLab 서버가 Runner에 접속하는 게 아니라, Runner가 주기적으로 GitLab 서버에 접속해서 실행할 Job이 있는지 확인하는 Pull 방식이라는 점입니다. 그래서 Runner EC2는 인바운드 포트를 열어둘 필요가 없습니다. 반대로 말하면, Runner가 오프라인이 되면 GitLab 서버 쪽에서는 아무것도 할 수 없고 Job은 그냥 stuck 상태로 대기하게 됩니다.
.gitlab-ci.yml에서 Job마다 tags를 지정하면 해당 태그를 가진 Runner에서만 실행되는데,
build_job:
stage: build
tags:
- neo4j
script:
- ./gradlew build
이 태그를 가진 Runner가 하나도 온라인이 아니면 Job은 실행되지 못하고 그대로 멈춰버립니다. 이번 장애도 정확히 이 케이스였습니다.
Runner를 여러 개 운영하는 이유도 이번에 다시 정리가 됐는데,
- 서비스별로 다른 런타임 환경(JDK, Node 버전 등)이 필요해서
- 특정 미들웨어가 설치된 환경에서만 실행돼야 하는 Job이 있어서
- 병렬 처리로 파이프라인 대기 시간을 줄이기 위해서
- 동일 태그의 Runner가 하나뿐이면 그 자체가 단일 장애점(SPOF)이 되기 때문에
라는 이유들이 있었고, 이번 사고가 바로 마지막 항목인 "Runner가 하나뿐이라 생긴 SPOF" 케이스였습니다.
장애 원인
로그를 확인해보니 이렇게 찍혀 있었습니다.
Runner is offline, last contact was about 19 hours ago
특정 태그를 담당하던 Runner가 설치된 EC2 인스턴스가 stopped 상태였던 것이 근본 원인이었습니다. 해당 Runner는 단일 EC2에만 설치되어 있었기 때문에, 그 인스턴스가 내려가면 해당 태그의 Job 전체가 멈추는 구조였습니다.
트러블슈팅 절차
1. Runner의 IP로 실제 EC2 찾기
GitLab Runner 상세 페이지에서 확인한 IP로 EC2를 조회했습니다.
aws ec2 describe-instances \
--filters "Name=ip-address,Values=<runner-ip>" \
--query "Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key=='Name'].Value|[0]]" \
--output table --region ap-northeast-2
그런데 조회된 인스턴스는 NAT 인스턴스였습니다. Runner가 NAT 뒤에 있는 별도 EC2였기 때문에, Public IP만 보고는 실제 Runner 위치를 특정할 수 없었습니다.
2. NAT 인스턴스 내부에서 Runner 프로세스 확인
혹시나 해서 SSM으로 접속해 프로세스와 바이너리를 확인했습니다.
ps aux | grep gitlab-runner
which gitlab-runner || find / -name "gitlab-runner" -type f 2>/dev/null
프로세스도 바이너리도 없었습니다. 이 인스턴스는 Runner와 무관하다는 것을 확인했습니다.
3. 마지막으로 성공한 Job 로그에서 실제 실행 호스트 찾기
Running on ip-xxx-xxx-xxx-xxx.ap-northeast-2.compute.internal...
Job 로그에 남아있는 실행 호스트명을 통해 실제 Runner가 설치된 내부 IP를 특정할 수 있었습니다.
4. 해당 Private IP로 실제 Runner EC2 조회
aws ec2 describe-instances \
--filters "Name=private-ip-address,Values=<private-ip>" \
--query "Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key=='Name'].Value|[0]]" \
--output table --region ap-northeast-2
이 인스턴스가 stopped 상태인 것을 확인했습니다.
5. EC2 시작
aws ec2 start-instances \
--instance-ids "<instance-id>" \
--region ap-northeast-2
6. Runner 프로세스 재확인
EC2가 시작되면 gitlab-runner 프로세스가 자동으로 기동되는지 확인했습니다.
ps aux | grep gitlab-runner
프로세스가 정상 실행되는 것을 확인했고, GitLab UI에서도 해당 Runner가 온라인으로 전환된 것을 확인할 수 있었습니다.
Runner 프로세스 점검 명령어 (환경별)
Amazon Linux 1처럼 systemd를 지원하지 않는 환경에서는
# 프로세스 확인
ps aux | grep gitlab-runner
# 서비스 재시작
sudo service gitlab-runner restart
# 바이너리 직접 실행 (서비스 등록 없이)
sudo /usr/bin/gitlab-runner run &
Amazon Linux 2 / 2023처럼 systemd 환경에서는
# 서비스 상태 확인
sudo systemctl status gitlab-runner
# 재시작
sudo systemctl restart gitlab-runner
# 부팅 시 자동 시작 설정
sudo systemctl enable gitlab-runner
이렇게 확인하면 됩니다.
재발 방지를 위해 정리한 것들
즉시 조치
- 해당 EC2가 중지된 원인이 의도적인지 확인
- EC2 상태 변경 시 알림이 오도록 CloudWatch Alarm 구성
- gitlab-runner 프로세스가 죽었을 때 자동 재시작되도록 init 스크립트 점검
구조적 개선
개선 항목 내용
| Runner 이중화 | 동일 태그의 Runner를 2개 이상 운영해 단일 장애점 제거 |
| Runner 버전 업그레이드 | 오래된 버전은 보안 패치를 위해 업그레이드 검토 |
| 모니터링 연동 | EC2 상태 변경 시 Slack 등으로 알림 구성 |
| Runner 인벤토리 문서화 | 각 Runner가 어떤 EC2에 있는지 정리해두기 |
마무리
이번 장애를 겪으면서 가장 크게 느낀 건, Runner가 하나뿐인 태그는 결국 언젠가 터질 수밖에 없는 구조라는 점이었습니다. 평소에는 잘 돌아가니까 신경을 안 쓰게 되는데, EC2 하나가 내려가는 것만으로 특정 파이프라인 전체가 멈출 수 있다는 걸 직접 겪고 나니 이중화의 필요성이 확실히 와닿았습니다.
또, Runner의 Public IP만 보고 실제 위치를 판단하면 안 된다는 것도 배웠습니다. NAT 뒤에 있는 구성이면 IP 하나만으로는 실제 EC2를 특정할 수 없고, Job 로그에 남는 실행 호스트명 같은 다른 단서를 함께 봐야 정확하게 찾을 수 있었습니다. 앞으로는 Runner별로 어떤 EC2에 있는지 미리 문서화해두는 게 이런 상황에서 훨씬 빠르게 대응하는 방법이 될 것 같습니다.
'n년차 개발자' 카테고리의 다른 글
| 개인 블로그, 정적 사이트 무료 호스팅 방법 정리 (0) | 2026.07.19 |
|---|---|
| AI 시대 개발자 공부법, IDE 없이 개발하는 사람들을 보며 든 생각 (0) | 2026.07.17 |
| Nexus 디스크 부족으로 인한 Maven Deploy 실패 (0) | 2026.07.14 |
| 크롤링 트래픽을 어디까지 허용하고 어떻게 통제해야 할까? (0) | 2025.12.15 |
| 개인화 추천 API 성능 개선 (0) | 2025.11.27 |
