-
Github Actions Self-hosted-runner 사용 방법클라우드 2025. 7. 12. 11:02
도입한 이유
먼저 취업한 친구에게 보안에 민감한 기업 같은 경우에는 Github-hosted-runner는 Github 클라우드 환경에서 실행되므로, 민감한 코드나 비공개 환경 정보가 외부로 유출될 가능성이 있어 Self-hosted-runner를 사용한다고 이야기를 들었습니다.
그래서 회사에서 사용할 기술을 미리 배워본다는 차원에서 Self-hosted-runner를 도입하여 CI/CD 파이프라인을 구축하려 했습니다.
도입을 하기 전에 이것이 어떤 원리로 동작 되는지 파악하였습니다.
우선 Self-hosted-runner는 사용자가 직접 관리하는 VM 또는 물리 서버에 설치하여 사용하는 실행 환경입니다.
- Github Actions 파이프라인에 Job(작업)을 생성하고 대기시켜 놓습니다.
- runner가 Github에 polling 방식으로 job을 가져가서 실행합니다.
이렇게 동작되는 방식의 장점이 하나 더 있었는데, Github-hosted runner를 사용할 때는 온프레미스의 private subnet에 있는 서버에 배포하기 위해서는 Bastion을 통하거나 터널링 방식으로 외부에서 접근되는 포인트를 만들어서 배포했어야 했는데, Self-hosted-runner를 사용할 경우 polling 방식으로 Github 서버에 접근해서 Job을 가져오기 때문에 외부에서 접근되는 포인트를 만들 필요가 없어 보안적으로 안전하다는 장점이 있었습니다.
도입 방법
우선 레포지토리의 Admin 권한이 있어야 합니다.
그 후 Settings - Actions - Runners에 들어가면
Github Runner 목록 화면 다음과 같은 화면이 나오는데 New Self-hosted-Runner를 클릭합니다.
그리고 명령어들이 나올텐데 이 명령어를 Self-hosted-Runner를 동작하게 할 VM에서 실행시켜 러너를 실행합니다.
# 1. 러너 설치 mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64-2.326.0.tar.gz -L <https://github.com/actions/runner/releases/download/v2.326.0/actions-runner-linux-x64-2.326.0.tar.gz> tar xzf ./actions-runner-linux-x64-2.326.0.tar.gz # 2. 등록 ./config.sh \\ --url <https://github.com/{organization}/{repo}> \\ --token <등록용 토큰> \\ --name shared-self-hosted-runner --labels self-hosted,linux,X64,shared \\ --unattended # 3. 서비스로 등록하고 실행 ./svc.sh install ./svc.sh start
러너 생성시 OS와 아키텍처 선택 화면 각 명령어 설명
- 러너 설치
- 러너를 설치할 때 러너가 돌아갈 운영체제에 맞게 선택하고 설치해줍니다. 저는 linux_x64로 설치하였습니다.
- 레포지토리 등록
- 그리고 등록해주는데 --url 옵션은 Organization과 repo 이름 입력하면 됩니다. 자동으로 나올거긴 합니다.
- --token 옵션은 러너를 Github 레포지토리에 등록하기 위한 단계이고, runner가 Github Action과 통신을 하기 위한 값입니다. 이것도 자동으로 나올 겁니다.
- --name 옵션은 러너의 이름을 지정합니다. 아까 Runner 목록 사진에 이름 적혀있던 거 참고하면 됩니다.
- --label 옵션은 나중에 Github Actions workflow의 runs-on에서 어떤 러너를 선택할지 결정하는데 그 때 사용하기 위해 설정합니다.
- 그리고 --unattended 는 대화형 입력을 생략하는 옵션인데 이건 직접 설치하기 때문에 굳이 안 붙여도 됩니다.
- 러너를 서비스로 등록하고 실행
- 이는 러너를 Linux 시스템의 서비스(systemd 서비스)로 등록하고 실행하기 위한 명령어인데요. 서버 재시작 시 자동으로 러너가 다시 실행되고 백그라운드로 동작하기 위해서 사용합니다.
이렇게 하면 러너를 등록하고 설치하였고 polling 방식으로 Github에 job이 있는지 확인합니다.
예를 들면
runner → Github : “작업 있어요?” Github → Runner : “ㄴㄴ” (잠시 대기) runner → Github : “작업 있어요?” Github → runner : “있어 이거 실행 ㄱㄱ”
이런 방식으로 동작합니다.
폴링하는 주기는 비공개 됐으나 보통 2~5초라고 판단하고 backoff 전략을 사용한다고 합니다.
backoff 전략은 처음에는 빠르게 시도하다가 실패가 계속되면 점점 재시도 간격을 늘리는 방식입니다.
이제 러너가 잘 돌아가는지 확인하기 위해 파이프라인을 돌려봅니다.
저는 CD 파이프라인을 .github/workflow/be-dev-cd.yml 에 설정해두었습니다.
현재 아래와 같은 파이프라인인데 jobs 아래의 runs-on 에서 러너 만들때 지정했던 태그를 넣어주면 해당 태그를 가진 러너에 job을 할당합니다.
해당 태그를 가진 러너가 여러개면 그 중 하나를 선택해서 job을 실행시키는데요
처음에 이거 모르고 태그가 겹치는 러너가 3개였는데 자꾸 파이프라인에서 SSH Connection Error가 나와서 네트워크 설정과 키 설정을 다 해주었는데 간헐적으로 오류가 자꾸 발생했고, 그 이유를 몰랐었는데 관련이 없는 러너에서 job이 실행되어 문제가 생긴 것이였습니다.
name: Backend Dev Docker CD on: push: branches: [ dev ] paths-ignore: - '**/README.md' - '**/.gitignore' jobs: build-and-deploy: runs-on: [ self-hosted, linux, X64, shared ] steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' # CI 과정에서 테스트를 했기 때문에 테스트 제외 - name: Build JAR run: | ./gradlew clean build -x test cp build/libs/newsum-0.0.1-SNAPSHOT.jar app.jar - uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-2 - name: Docker ECR Login run: | aws ecr get-login-password --region ap-northeast-2 \\ | docker login --username AWS --password-stdin ${{ secrets.ECR_REGISTRY }} # Docker Buildx 설치 및 설정 - name: Setup Docker Buildx run: | BUILDX_VERSION="v0.24.0" # <-- Buildx 버전 명시 BUILDX_DIR="/usr/local/lib/docker/cli-plugins" BUILDX_BIN="$BUILDX_DIR/docker-buildx" sudo mkdir -p "$BUILDX_DIR" # 러너 아키텍처에 맞는 Buildx 바이너리 다운로드 (X64 러너이므로 linux-amd64) BUILDX_URL="<https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-amd64>" echo "Downloading buildx from $BUILDX_URL to $BUILDX_BIN" sudo curl -L "$BUILDX_URL" -o "$BUILDX_BIN" sudo chmod a+x "$BUILDX_BIN" echo "Docker Buildx version after setup:" docker buildx version # 설치 확인을 위한 명령 # arm 아키텍처로 이미지 빌드 - name: Build & Push ARM image with tag `dev` run: | IMAGE="${{ secrets.ECR_REGISTRY }}/${{ secrets.ECR_REPO }}:dev" docker buildx inspect multi-arch >/dev/null 2>&1 || docker buildx create --name multi-arch --use --driver docker-container --bootstrap docker buildx use multi-arch docker buildx build \\ --platform linux/arm64 \\ --tag "$IMAGE" \\ --push \\ . # 태그로 인스턴스 IP 가져오기 - name: Get EC2 Private IPs by Tag id: fetch run: | IPS=$(aws ec2 describe-instances \\ --filters "Name=tag:Role,Values=newsum-backend" \\ "Name=tag:Environment,Values=dev" \\ "Name=instance-state-name,Values=running" \\ --query 'Reservations[*].Instances[*].PrivateIpAddress' \\ --output text) # IP 리스트 디버깅 출력 echo "IPS: $IPS" # 줄바꿈 제거하고 공백 구분의 한 줄로 만들어 출력 IPS_SINGLE_LINE=$(echo $IPS | tr '\\n' ' ') echo "ips=$IPS_SINGLE_LINE" >> "$GITHUB_OUTPUT" - name: Deploy to WAS EC2 via SSH run: | # GitHub Actions 시크릿을 쉘 변수로 먼저 할당 ECR_REGISTRY=${{ secrets.ECR_REGISTRY }} ECR_REPO=${{ secrets.ECR_REPO }} SSH_KEY_PATH=${{ secrets.SSH_KEY_PATH }} INFISICAL_URL=${{ secrets.AWS_CLOUDFRONT_URL }} INFISICAL_PROJECT_ID=${{ secrets.INFISICAL_PROJECT_ID }} INFISICAL_TOKEN=${{ secrets.INFISICAL_TOKEN_DEV }} INFISICAL_ENV=${{ secrets.INFISICAL_ENV_DEV }} INFISICAL_API_URL=${{ secrets.INFISICAL_API_URL }} for IP in ${{ steps.fetch.outputs.ips }}; do echo "🚀 배포 중: $IP" echo "SSH키 경로 : $SSH_KEY_PATH" # Self-hosted-runner 내부 키 경로 ssh -o StrictHostKeyChecking=no -i $SSH_KEY_PATH ubuntu@$IP <<-EOF # ECR 로그인 aws ecr get-login-password --region ap-northeast-2 \\ | docker login --username AWS --password-stdin $ECR_REGISTRY # .env 생성 curl -Ls https://$INFISICAL_URL/tools/infisical-linux-arm64 -o infisical chmod +x infisical && sudo mv infisical /usr/local/bin/infisical infisical export \\ --env=$INFISICAL_ENV \\ --projectId=$INFISICAL_PROJECT_ID \\ --token=$INFISICAL_TOKEN \\ --format=dotenv \\ --domain=$INFISICAL_API_URL > /home/ubuntu/newsum-backend.env # value에 따옴표 제거 (Infisical에서 문자열이 따옴표로 감싸져 나올 때 유용) sed -i "s/=['\\"]\\([^'\\"]*\\)['\\"]$/=\\1/" /home/ubuntu/newsum-backend.env # 기존 컨테이너 중지 및 제거 docker stop newsum_backend || true docker rm newsum_backend || true # 새 이미지 pull 후 실행 docker pull $ECR_REGISTRY/$ECR_REPO:dev docker run -d \\ --name newsum_backend \\ --restart=always \\ --env-file /home/ubuntu/newsum-backend.env \\ -p 8080:8080 \\ $ECR_REGISTRY/$ECR_REPO:dev # 🔐 환경변수 파일 삭제 rm -f /home/ubuntu/newsum-backend.env EOF done
하지만 이 파이프라인은 Self-hosted-Runner를 사용할 때 미리 aws 및 docker 명령어를 미리 설치할 수 있다는 장점을 사용하지 않고, 중복으로 설치하는 파이프라인이고 효율적이지 못한 부분도 많이 보입니다.
다음 포스팅은 이 파이프라인을 리팩토링 하는 과정에 대해 글을 쓰려고 합니다.
'클라우드' 카테고리의 다른 글
Redis 클러스터 구성 (1) 2025.07.28 EC2로 SSH 접속이 되는 원리 (0) 2025.06.30 CloudFront + S3 + ACM + Route53으로 프론트(Vite + React) 정적 파일 배포 하기 (1) 2025.06.14 Infisical CLI 설치하는 파이프라인 최적화 (1) 2025.06.07 신한 DS ICT 인프라 실기시험 실습 (2) 2025.06.07