development

GitHub Actions Self-hosted Runner Idle인데 실행 안 될 때 해결 방법 (Exit Code 1, 2 원인 정리)

jacky

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 dockerwhich 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 → 권한 → 환경 변수 순서로 확인하는 게 가장 빠릅니다.