Dev/DevOps, Infra

[Github, CI/CD] Github Actions self hosted runner with own GPUs

HJChung 2021. 10. 10. 17:25

Github Actions self hosted runner with own GPUs

Github Actions는 Gtihub을 기반으로 하는 CI/CD, 여러 개발 Workflow 자동화 툴이다.

 

그런데 사용하다 보면 Github Actions workflow를 Github-hosted runner에서만 실행시키는데 한계가 있는 경우가 있다. 

A GitHub-hosted runner is a virtual machine hosted by GitHub with the GitHub Actions runner application installed. 

 

 

구체적으로 어떤 경우가 있을 수 있는지 'ML application을 위한 Workflow를 Github Actions로 실행하고자 할 때'를 예시로 살펴보자면

  • ML application inference code의 Unit test, Integration Test 시에 GPU 사용을 해야할 때 Github-hosted runner에서는 이러한 컴퓨팅 기능을 제공해주지 않는다.
  • 보안 이슈가 있는 데이터를 사용해서 해당 Workflow가 실행 되어야 하는 경우 해당 데이터를 Github에 업로드할 수 없음으로 인해 Github-hosted runner에서는 실행할 수 없다. 

 

 

이런 한계를 해결할 수 있느 방법 중 하나가 바로 self-hosted runners를 사용하는 것이다

A Self-hosted runners is runners that you manage, but are accessible to your CI/CD system for executing jobs.

 

 

그래서 자신의 서버가 있거나 아니면 AWS의 EC2 등의 서버를 Github Actions의 self-hosted runner로 추가하고,

위의 ML application 개발 과정처럼 GPU 사용이 필요한 경우, GPU가 탑재된 self-hosted runner에서 GPU가 사용될 수 있게 셋업 및 스크립트 작성하는 방법을 정리해보고자 한다. 

 

Add self-hosted runners at various levels

You can add self-hosted runners at various levels in the management hierarchy:

- Repository-level runners are dedicated to a single repository.
- Organization-level runners can process jobs for multiple repositories in an organization.
- Enterprise-level runners can be assigned to multiple organizations in an enterprise account.

 

만약 자신의 github organization에서 관리하는 모든 repo에서 전반적으로 사용하고 싶다면 Organization-level runners로 추가하는 것이 편리하다. 하지만 이 과정은 organization owner가 발급한 token이 필요하므로 owner만 진행 가능하다. 

 

아래의 과정은 모두 Repository-level로 runners를 추가하는 방법에 대해서 다룰 것이다.

 

Github actions Self hosted Runner 추가하기

방법 1) "Docker-in-Docker" container로 Github actions Self hosted Runner 추가하기

https://github.com/myoung34/docker-github-actions-runner 에서 제공하는 도커 이미지를 사용하여 추가할 수 있다. 

이는 자신이 사용할 서버에 self-hosted runners를 docker container로 띄우고,

Github actions workflow를 수행할 것이 있으면 이를 실제 수행하는 container가 runner container 안에 떠서 github action script를 수행하는 방식으로 동작하는 self-hosted runner를 추가하는 방법이다. 

① prerequisite

If you want to run workflows that use Docker container actions or service containers, you must use a Linux machine and Docker must be installed.

② Create RUNNER_TOKEN something called a personal access token

runner를 추가할 repo에 접근 권한을 확인하기 위해 github personal access token을 발급받아야 한다. 

그리고 어떤 권한을 부여할건지 정하는 selecting box에서는

  • repo (all)
  • admin:org (all) (mandatory for organization-wide runner)
  • admin:enterprise (all) (mandatory for enterprise-wide runner)
  • admin:public_key - read:public_key
  • admin:repo_hook - read:repo_hook
  • admin:org_hook
  • notifications
  • workflow

이러한 것들을 추가해준다. 

그리고 나서 Generate token을 누르면 token이 발급되고, 그걸 복사하여 사용하면 된다!

※ Organization-level runners을 추가하고 싶은 경우 Owner가 위의 방식.으로 발급한 token을 가지고 진행해야지만 추가가 가능하다.

③ Let's going to run a docker github actions runner docker container that listens for workflows from Github.

https://github.com/myoung34/docker-github-actions-runner#manual에 나와있는 명령어를 참고해서 

docker run 명령어나 shell wrapper 또는 docker-compose 파일로 작성하여 실행시켜주면 된다. 

# per repo
docker run -d --restart always --name github-runner \
  -e REPO_URL="https://github.com/myoung34/repo" \ # 추가할 repo url
  -e RUNNER_NAME="foo-runner" \ # runner name 지정
  -e RUNNER_TOKEN="footoken" \ # 위에서 발급 받은 github PAT token
  -e RUNNER_WORKDIR="/tmp/github-runner-your-repo" \ # The working directory for the runner. Runners on the same host should not share this directory. Default is '/_work'. 자세한 내용은 https://github.com/myoung34/docker-github-actions-runner#environment-variables 참고
  -e RUNNER_GROUP="my-group" \ # Settings > Actions > Runners에 가면 Default가 기본으로 있습니다. Group설정을 따로 해주지 않고 넘어가면 디폴트로 Default group에 추가가 됩니다.
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/github-runner-your-repo:/tmp/github-runner-your-repo \
  myoung34/github-runner:latest

 

여기까지 정상적으로 되었다면 runner을 추가한 repo에 Settings > Actions > Runners에 위의 RUNNER_NAME으로 runner가 추가되어 있는 것을 확인할 수 있다. 

 

방법 2) Github.com에서 제공하는 방식에 따라 Github actions Self hosted Runner 추가하기

repository level runners를 추가할 경우에는 공식문서인 Adding a self-hosted runner to a repository 를 보고 그대로 진행하면 된다.

  1. On GitHub, navigate to the main page of the repository.
  2. Under your repository name, click  Settings.
  3. In the left sidebar, click Actions.
  4. In the left sidebar, under "Actions", click Runners.
  5. Click New self-hosted runner.
  6. 여기까지 하면 아래와 같은 명령어들이 나온다.  runner로 사용할 서버의 shell에서 아래의 명령어를 순서대로 입력한다. 
  7. Configure 단계에서 명령어를 입력하면 몇 가지 설정을 할 수 있는 입력 칸이 뜬다. runner의 name을 정하는 것은 일반적인 것이고 생소한 것은 Group과 Label일 것이다.
    1. Group에 뭘 적어야 하나?? Settings > Actions > Runners에 가면 Default가 기본으로 있다. Group설정을 따로 해주지 않고 넘어가면 디폴트로 Default group에 추가가 된다. 다른 Group에 추가하고 싶다면 그 Group의 이름을 적어주면 된다.
    2. Label에는 뭘 적어야 하나?? 여러 runners가 있을 때 github actions script에서 어떤 runner를 사용하라고 Label을 통해 지정해 줄 수 있는데 그때 Label이 사용된다. 그래서 해당 runner의 구분자처럼 쓸 수 있도록 몇 가지를 , 를 기준으로 몇 개 적어주자! 자세한 내용은 Using the configuration script to create and assign labels을 참고 할 수 있다.
    3. Group에는 아무것도 적어주지 않아서 Default를 사용하고, Label에는 self-hosted, linux, x64,103이라고 설정하고 여기까지 정상적으로 되었다면 runner을 추가한 repo에 Settings > Actions > Runners에 아래처럼 runner가 추가되어 있는 것을 확인할 수 있다. 

 

 

Github actions Self hosted Runner 사용하기

여기서는 앞서 <Github actions Self hosted Runner 추가하기>에서 두 번째 방법으로 GPU가 있는 자체 서버를 Self hosted Runner로 추가하였다고 가정하고,

이걸 사용하기 위한 workflow yml 스크립트 작성을 해 볼 것이다. 

service 설치 (Installing the service)

먼저 처음 사용하기 전에 self-hosted runner service를 설치하고 상태 관리를 해야한다. 

  1. Stop the self-hosted runner application if it is currently running.
  2. Install the service with the following command:
    sudo ./svc.sh install​

service 시작 (Starting the service)

sudo ./svc.sh start

 

Github Actions with own GPUs 사용하기 위해 필요한 것 설정

GPU가 있는 self-hosted runner 서버에서 GPU를 사용해야 하는 github actions workflow(ex. ML application inference code unittest.. 등)가 실행되어야 할 때 필요한 것이 있다.

 

github actions workflow는 docker가 뜨면서 실행된다. 그래서 docker와 docker container 내에서 NVIDIA GPU를 이용할 수 있도록 해주는 plug-in 또는 Wrapper인 nvidia-docker가 필요하다. 

 

이 두 자료를 보고 nvidia-docker까지 설치 완료 한 후 

sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

자신이 설치한 버전에 맞게 위의 명령어를 수정/입력했을 때 아래 사진과 같이 되었다면 이제 준비는 다 된 것이다. 

 

workflow yml 스크립트 작성 (Using self-hosted runners in a workflow)

self-hosted runner을 사용해서 이 스크립트를 실행하고 싶다면 runs-on 값에 self-hosted 라고 적어주면 기본적으로 self-hosted runner가 실행된다. 

 

runs-on은 앞서 설정해준 label을 기준으로 어떤 runner을 실행시킬 것인지 결정하게 되는데 기본적으로 모든 self hosted runner는 self-hosted label을 가지고 있기 때문이다. 

jobs:
    test:
        runs-on: self-hosted

 

만약에 self-hosted로 등록된 runner가 여러 개 있고, 이를 구분해서 사용하고 싶다면 앞서 설정한 labels를 활용하면 된다.

예를 들어 GPU가 있는 서버를 runner로 추가 할 때 label로 'gpu'를 적어주었고, 이 runner가 필요하다면

jobs: 
  test: 
    runs-on: [self-hosted, gpu]

이런 식으로 해당 runner를 식별가능하게 해 준다. 

 

실행 결과 slack alert 하기

어떤 workflow가 실행완료되는데 몇 분이 걸린다면 그게 진행되는 동안 다른 것을 하는 경우가 많을 것이다. 

이런 경우 실행 결과를 slack으로 받아 볼 수 있다면 굉장히 편리해지고 신경 써야 할 것이 하나 줄어든다. 

 

자세한 내용은 Building a CI/CD Pipeline with GitHub Actions and Docker (Part 1) 글에 <Feedback using Slack Notifications>에 잘 나와있다. 

이 글에서는 simple-slack-notify@master 을 사용하고 있는데, rtCamp/action-slack-notify도 비슷한 기능을 제공하고 star도 훨씬 많다. 

 

나 같은 경우는 성공/실패/취소했을 때 받는 메시지를 다 지정해주고 싶어서  simple-slack-notify@master 을 사용해서 script를 작성했다. 또 어떤 Branch에서 발생한 actions인지도 알고 싶어서 branch 이름을 먼저 extract 하는 step도 추가해주었다. 

 

최종적으로

- name: Extract branch name
  shell: bash
  run: echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV

- name: Slack Notification
  if: always()
  uses: edge/simple-slack-notify@master
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
   with:
          username: 'Github Actions Runner'
          status: ${{ job.status }}
          success_text: 'Unittest completed successfully'
          failure_text: 'Unittest failed'
          cancelled_text: 'Unittest was cancelled'
          fields: |
            [{ "title": "Repository", "value": "${env.GITHUB_REPOSITORY}", "short": true },
            { "title": "Branch", "value": "${env.BRANCH}", "short": true },
            { "title": "Action URL", "value": "${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}"}]

이런 스크립트를 추가해 주었다. 

 

실행

name: ML application unittest

on:
  push:
    branches: [test]

jobs: 
  test: 
    runs-on: [self-hosted, gpu]
    steps:
      - uses: actions/checkout@v2
      - name: ML inference code Unittest
        run: docker-compose -f docker-compose-unittest.yml up --exit-code-from [SERVICE] # see https://libertegrace.tistory.com/entry/211021-docker-compose-exit-code-from
      
      - name: Extract branch name
      	shell: bash
      	run: echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV

      - name: Slack Notification
        if: always()
        uses: edge/simple-slack-notify@master
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
         with:
                username: 'Github Actions Runner'
                status: ${{ job.status }}
                success_text: 'Unittest completed successfully'
                failure_text: 'Unittest failed'
                cancelled_text: 'Unittest was cancelled'
                fields: |
                  [{ "title": "Repository", "value": "${env.GITHUB_REPOSITORY}", "short": true },
                  { "title": "Branch", "value": "${env.BRANCH}", "short": true },
                  { "title": "Action URL", "value": "${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}"}]

최종적으로 이렇게 작성된 .github/workflows/unittest.yml이 있을 때 

test branch로 push가 되면 self-hosted runner로 추가한  GPU self-hosted runner의 상태가 Active로 바뀌면서 잘 진행되는 것을 확인할 수 있다!

 

그리고 성공적으로 끝났다면 아래와 같은 슬랙 알람이  도착한다. 

 

 

 

 

 

 

 

Reference

MLOps Tutorial #4: GitHub Actions with your own GPUs

CML self-hosted runners on demand with GPUs

About GitHub-hosted runners

About self-hosted runners

GitHub Actions로 간단히 CI 서버 대신하기

Building a CI/CD Pipeline with GitHub Actions and Docker (Part 1)