development
GitHub Actions Self-hosted Runner Idle인데 실행 안 될 때 해결 방법 (Exit Code 1, 2 원인 정리)
GitHub Actions Self-hosted Runner Idle인데 실행 안 될 때 정리
GitHub Actions에서 self-hosted runner를 붙여두면 직접 관리하는 서버에서 빌드나 배포를 돌릴 수 있어서 꽤 편합니다. 그런데 실제로 많이 막히는 구간은 runner 등록 자체보다, Idle 상태까지는 잘 왔는데 job 안에서 명령어가 제대로 실행되지 않는 경우입니다.
특히 이럴 때는 네트워크 문제부터 의심하기보다, 먼저 잡 라우팅/매칭과 실제 runner 안의 실행 환경을 같이 보는 게 빠릅니다.
핵심 포인트: runner 이름이 아니라 label 기준으로 본다
self-hosted runner를 등록할 때 입력하는 이름은 관리용 식별자에 가깝고,
실제로 GitHub Actions가 어떤 runner에 job을 보낼지는
workflow의 runs-on에 적은 label 기준으로 결정됩니다.
그래서 self-hosted runner를 쓸 때는 서버 이름을 기준으로 생각하기보다 label 기준으로 잡이 매칭된다고 이해하는 게 맞습니다.
| 구분 | 예시 |
|---|---|
| 잘못 적은 예 | runs-on: my-server-01 |
| 권장하는 예 | runs-on: [self-hosted, linux, build] |
즉, runner가 Idle 상태라는 건 GitHub에 연결은 되어 있다는 뜻이지만, 그 다음으로는 원하는 label로 정확히 job이 배정됐는지, 그리고 배정된 뒤 그 서버 안에서 명령어를 실행할 수 있는 상태인지를 따로 봐야 합니다.
커스텀 label은 단순하게 나눠두는 게 좋다
처음에는 기본 label만으로도 충분해 보이지만, 실제로는 역할별로 label을 나눠두는 편이 훨씬 덜 헷갈립니다.
build: 빌드 전용deploy: 배포 전용docker: Docker 사용 가능gpu: GPU 작업용
예를 들면 이렇게 지정할 수 있습니다.
runs-on: [self-hosted, linux, build]
Docker 명령까지 필요한 작업이라면 이렇게 더 분명하게 나눌 수 있습니다.
runs-on: [self-hosted, linux, docker, build]
이렇게 해두면 runner가 여러 대가 됐을 때도 어떤 job이 어느 서버로 가야 하는지 훨씬 명확해집니다.
Idle까지는 오는데 명령어가 안 먹힐 때
이 단계에서는 보통 runner 자체가 죽은 게 아니라, job은 정상적으로 잡혔는데 runner 안의 실행 환경이 맞지 않아서 step이 실패하는 경우가 많습니다.
1. PATH 문제
가장 흔한 원인입니다.
터미널에서 직접 접속해서 실행할 때는 되는데,
GitHub Actions 안에서는 docker: command not found,
node: command not found 같은 식으로 실패하는 경우가 여기에 해당합니다.
이유는 간단합니다. 내가 평소 로그인해서 쓰는 쉘 환경과 runner가 실제로 실행되는 환경이 완전히 같지 않을 수 있기 때문입니다.
이럴 때는 먼저 아래처럼 runner 기준 환경을 찍어보는 게 빠릅니다.
- name: Debug runner environment
run: |
whoami
pwd
echo "$PATH"
which docker || true
which node || true
which npm || true
여기서 which docker 나 which node 가 비어 있으면,
명령어가 설치 안 된 경우거나 PATH에 안 잡히는 경우입니다.
2. working-directory / 경로 문제
두 번째로 흔한 원인은 경로 문제입니다. 로컬에서는 특정 폴더 안에서 명령을 실행했는데, runner는 다른 위치에서 실행하고 있어서 파일이나 디렉토리를 못 찾는 경우가 많습니다.
- run: npm install
working-directory: ./app
그런데 실제 checkout된 경로에 ./app 폴더가 없으면
바로 실패하게 됩니다.
이런 경우는 exit code 1 이나 exit code 2로 자주 보입니다.
이럴 때는 아래 두 개만 먼저 봐도 원인이 빨리 보입니다.
pwd로 현재 위치 확인ls -al로 실제 파일 구조 확인
그리고 경로를 참조하는 step이
actions/checkout 이후에 실행되는지도 같이 보는 게 좋습니다.
3. 실행 권한 문제
스크립트 파일이 있어도 실행 권한이 없으면 실패합니다.
특히 ./gradlew, ./mvnw, ./deploy.sh 같은 파일에서 자주 만납니다.
chmod +x gradlew
chmod +x deploy.sh
서버를 새로 셋업했거나 권한이 꼬여 있으면 runner는 살아 있어도 실제 명령 실행 단계에서 바로 막힐 수 있습니다.
4. 환경 변수 누락
터미널에서는 잘 되는데 Actions에서만 실패한다면 환경 변수가 runner 실행 환경에 없을 가능성도 큽니다.
예를 들면 JAVA_HOME, NODE_ENV,
내부 API 주소, 인증 토큰 같은 값들입니다.
이 경우 명령어는 실행되더라도 내부에서 실패하면서
exit code 1로 끝나는 경우가 많습니다.
env:
NODE_ENV: production
JAVA_HOME: /usr/lib/jvm/java-17-openjdk
중요한 값은 서버에 이미 설정돼 있겠지 하고 넘기기보다, workflow에서 필요한 값만 명확하게 넣어주는 편이 훨씬 덜 헷갈립니다.
Exit Code 1, 2는 보통 이렇게 본다
Exit Code 1
가장 일반적인 실패입니다. 파일 없음, 권한 문제, 환경 변수 누락, 또는 실행한 프로그램 내부 에러처럼 범위가 넓습니다.
- 파일이나 폴더가 실제로 존재하는지
- 실행 권한이 있는지
- 필요한 환경 변수가 들어갔는지
- 실행한 프로그램이 내부적으로 실패한 것은 아닌지
Exit Code 2
보통 잘못된 명령 사용, 인자 오류, 디렉토리 경로 오류, 또는 쉘 문법 문제 쪽을 먼저 의심하면 됩니다.
- 명령어 옵션이나 인자가 잘못되지 않았는지
cd하려는 경로가 실제 존재하는지working-directory가 맞는지- 쉘 스크립트 문법 오류는 없는지
결국 중요한 건 숫자 자체보다 어느 step에서 어떤 명령이 실패했는지 로그를 보는 것입니다.
제가 먼저 보는 최소 체크리스트
runs-on이 runner 이름이 아니라 label 기준으로 적혀 있는지 확인pwd,ls -al로 현재 경로와 파일 구조 확인echo $PATH,which docker,which node로 실행 환경 확인- 스크립트 실행 권한이 있는지 확인
- 필요한 환경 변수가 workflow에 명시돼 있는지 확인
마무리
self-hosted runner를 쓸 때 가장 먼저 기억할 건 runner 이름이 아니라 label 기준으로 잡이 매칭된다는 점입니다.
그리고 runner가 Idle이라고 해서 끝난 게 아니라, 그 다음에는 실제 서버 안에서 명령어를 실행할 수 있는 환경인지를 봐야 합니다.
제가 겪어본 대부분의 문제는 결국 아래 네 가지 중 하나였습니다.
- PATH 문제
- 경로 문제
- 실행 권한 문제
- 환경 변수 누락
그래서 self-hosted runner가 Idle까지는 잘 왔는데도 step 실행에서 계속 실패한다면, 복잡하게 넓게 보기보다 label 매칭 → 현재 경로 → PATH → 권한 → 환경 변수 순서로 확인하는 게 가장 빠릅니다.
클립보드에 복사되었습니다.