MLOps 에서 중요한 요소 중 하나는 CI/CD 입니다. 지속적으로 모델이 개발되고 이 모델들이 모여 있는 AI Engine 을 운영하는 ML as a Service 의 회사들에게 코드의 변경 사항이 기존에 인지하고 있던 모델의 성능에 나쁜 영향을 주지 않았다는 것을 확인하는 것이 필요합니다.
이러한 방법을 위해서 제가 이전 포스팅에서 Jenkins를 사용하는 기초적인 방법에 대해서 포스팅을 한 적이 있습니다. 제가 Jenkins에 익숙하지 않아서겠지만, 생각보다 사용하는데 있어서 불편함이 많았고 Github에서 이쁘게 결과가 출력되지 않아서 불만족 스러운 부분들이 많았습니다.
그래서 최근에 회사의 CI/CD 시스템에서 GIthub Action을 도입하였고, 이 과정에서 기초적인 개념과 사용법을 공부했던 내용들에 대해서 정리하는 포스팅을 남기고자 합니다.
Github Action의 개념 (6 가지)
1. Workflow :
- Event 에 의해서 trigger 되어 진행되는 자동화된 프로세스.
- Github Action을 사용한 작업의 최상위 개념으로 아래의 5가지 개념이 모여서 Workflow 하나가 되는 것.
- Workflow 파일은 YAML으로 작성
- Github Repository의 .github/workflows 폴더 아래에 저장 (Git에서 yaml 파일을 보고 workflow를 수행)
2. Event :
- Workflow를 트리거(실행)하는 특정 활동이나 규칙.
- (ex) 정해진 branch 에 대한 PR이 발생 시 workflow 실행
3. Job :
- 여러 Step이 모여서 하나의 Job이 구성
- 단일 가상 환경에서 실행
- 다른 Job에 의존 관계를 가질 수도 있고, 독립적으로 병렬로 실행될 수도 있음.
4. Step :
- Job 을 구성하는 단위이며, Step은 yaml 파일에 저장된 순서대로 실행
- action을 실행할 수 있음
5. Action :
- Job을 만들기 위해 Step들을 연결
- workflow의 가장 작은 빌드 단위
6. Runner :
- Gitbub Action Runner 어플리케이션이 설치된 머신
- Workflow가 실행될 인스턴스
GIthub Action 생성
우선 Github Action으로 CI 를 수행하고자 하는 Github Repository로 이동을 합니다. 그러면 익숙한 아래와 같은 메뉴가 있는 것을 볼 수 있습니다. 저는 Github Action에 대해서 공부하기 이전에는 Actions 라는 항목이 4번째에 있다는 것을 모르고 있었는데 공부하고 나니 보이네요.
이제 위의 메뉴에서 Actions를 누르면 아래와 같은 화면이 보이게 됩니다. 여기에서 Suggested 목록에는 미리 Job 으로 Docker를 사용하는 방법이라던가, Django를 사용하는 방법에 대해서 적힌 template 같은 yaml 파일을 얻을 수 있게 해주는 목록이 있습니다.
만약, 아래의 Suggested 목록에서 사용하고자 하는 예시가 없는 경우에는 "set up a workflow yourself"를 눌러서 진행해주시면 됩니다. 저는 Docker image 버튼을 눌러서 기본 template 를 가지고 작업을 진행하였습니다.
이 버튼을 눌러서 작업을 진행 한 이유는 제가 CI를 수행하고자 했던 project에는 Dockerfile이 정의되어 있어 ML 실험을 테스트 하기 위한 container image를 만들 수 있도록 되어 있었기 때문입니다.
실제로 이 포스팅을 찾아오신 분들은 ML을 하시는 분들이라고 생각이 되기 때문에 Dockerfile로 image를 구성할 수 있도록 하여 Docker image 템플릿을 사용하는 것을 추천드립니다.
Docker image 버튼을 누르면 다음과 같은 화면이 보입니다.
이 화면에서 Github Action을 위한 yaml 파일이 생성되는 위치를 알 수 있습니다.
<Repo name>/.github/workflows/<yaml>
저는 처음 이 화면을 보았을 때 Jenkins와 비교해서 훨씬 직관적이고 쉬운 느낌이라서 왜 진작 이걸 사용하지 않았을까 라는 생각이 들었습니다.
on 이라는 key 값은 위에서 설명드린 event trigger 파트를 정의합니다. 위의 템플릿에서는 "master"라는 브랜치에 "push"가 발생할 때, 그리고 "pull_request"가 발생할 때 yaml으로 정의한 workflow가 발생하도록 한다는 것을 의미합니다.
추가적인 설정으로 tag나 측정 파일에 변화가 생겼을 때, 혹은 일정 시간마다 event trigger가 일어나도록 할 수도 있으니 공식문서를 참조하시면 좋을 것 같습니다. https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
아래에서 설명하겠지만, 저는 event trigger로 수동으로 원하는 경우에 event를 발생시키는 workflow_dispatch를 사용하였습니다.
Job 파트는 runs-on 이라는 Job이 동작할 가상환경의 instance를 정의할 수 있습니다. 기본 설정은 ubuntu-latest로 ubuntu 가상환경을 구성해서 아래 steps들을 실행하게 됩니다.
Steps 파트에서는 먼저 uses가 등장합니다. 이 uses는 기본적으로 해당 steps 파트를 수행하기 위해 필요한 Github Actions에서 미리 정의해둔 기능을 수행해줍니다. 위의 template에 있는 "actions/checkout@v2"는 "push 나 pr 같은 workflow의 event를 일으킨 코드"를 가져오는 것입니다." (https://github.com/actions/checkout). uses 역시 cache 기능을 활용해서 CI execution time을 줄이는 등의 굉장히 많은 종류가 있기 때문에 살펴보시면 좋을 것 같습니다.. (https://github.com/actions/)
실제 코드 작성
저는 우선 다음과 같이 workflow_dispatch를 통해서 원하는 시점에만 Github Action이 수행되도록 하였습니다. 이때, 저는 실험의 종류를 2가지 생각하고 있었습니다.
(변수 이름을 포스팅을 위해 막 지었습니다...)
1) Kuberentes 를 활용한 single gpu를 사용하는 실험
2) Kuberentes 를 활용한 multi-gpu 를 사용하는 실험
아래와 같이 workflow_dispath를 위의 템플릿의 push, pull-request위치에 대신 위치하도록 합니다. 그리고 inputs 의 value로써 설정하고자 하는 변수들의 입력 정보를 제공합니다.
제가 작성한 yaml 파일을 사용하는 경우 'k8s_config'와 'distributed', 'exp_grp', 'exp_id' 의 값을 제공 GIthub Action을 수동으로 시작할 수 있습니다.
name: Engine-Project-Test
on:
workflow_dispatch:
inputs:
k8s:
description: 'k8s_config'
default: None
required: false
dist:
description: 'distributed'
default: 'False'
required: false
exp_grp:
description: 'exp_grp'
default: 'CI'
exp_id:
description: 'exp_id'
default: 'temp'
이제 Job을 정의할 순서입니다. 저는 github repo의 Docker file을 활용해서 이미지를 생성할 수도 있지만, Dockerfile에는 실제 실행 코드를 담아두지 않고 container의 환경만을 구성하였습니다. 그러므로, 매번 build할 필요 없이 dockerhub에서 원하는 이미지를 받아오고자 하였습니다.
그럼 실제로 제가 원하는 동작은 어떤 구성이 되는지 살펴보면 아래와 같습니다. runs-on에서 설정한 ubuntu 가상머신위에 사전에 Dockerhub에 업로드 해둔 Image를 기반으로 생성하는 Container를 띄우고, Job들이 해당 Container 내에서 실행 되도록 하는 것입니다.
jobs:
default_project_job: # Github-Action에서 보여지길 원하는 테스트 이름
runs-on: [self-hosted, gpu]
- name: checkout github
uses: actions/checkout@v2
- name: docker hub login # Docker Hub로 부터 Image pull을 위해 필요한 정보
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Dummy Project
run: |
docker run --gpus 1 -v /tmp/github-runner:/workspace -e PYTHONPATH=/workspace <dockerhub>/<repo>:runner python3 /workspace/<path>/<to>/main.py
위의 내용을 Jobs 에 입력해 줍니다.
먼저 checkout github 의 step부터 살펴보면, 해당 기능은 현재 Github Action의 Event를 발생시킨 branch의 코드를 가져오는 부분입니다. 이를 통해서 현재 Github Action의 $GITHUB_WORKSPACE 는 Event를 발생시킨 repository의 코드가 복사해져 있습니다. 추가 기능들을 설정할 수 있으며 공식 홈페이지 (https://github.com/actions/checkout) 에서 찾아보실 수 있습니다.
이제 docker hub login 부분에 대해서 살펴보겠습니다. 이 부분은 Docker Hub에 접속하여 Image Pull을 수행하는 경우 문제 없이 private hub 로 부터 Image를 가져올 수 있도록 Dockerhub의 계정과 관련된 사항을 전달하는 부분입니다.
이때, Dockerhub의 정보에 접근하기 위해서는 Dockerhub에서 제공하는 TOKEN 정보가 필요하며 해당 정보를 얻는 방법과 등록 방법은 아래 링크의 블로그에서 보실 수 있습니다. (https://teichae.tistory.com/entry/Docker-Hub%EC%97%90%EC%84%9C%EC%9D%98-Token-%EB%B0%9C%EA%B8%89-%EB%B0%A9%EB%B2%95)
마지막 Dummy Project Step입니다. 이 부분은 제가 지어둔 이름처럼 실제로 docker container를 project를 위해서 하나 띄우고 해당 프로젝트의 test를 수행하는 main.py까지 실행 시키는 부분입니다.
이 부분에서 주의해야 할 점이 있습니다. 기본적으로 docker in docker 형식으로 Github Action이 작동을 하고 있기 때문에 -v 옵션을 통해서 test를 위한 container에 volume sharing 시에 /tmp/github-runner 폴더는 Github Action이 실행되고 있는 self-hosted runner container가 가지는 것이 아니라 host에서 해당 경로에 폴더가 존재해야 합니다.
아래와 같이 정의가 필요하다는 것을 의미하며, 이 부분 때문에 self-hosted runner container를 정의할 때 <host folder path>를 host 와 self-hosted runner container가 volume을 share하여 $GITHUB_WORKSPACE의 코드를 test container에 전달할 수 있도록 하는 것이 필요합니다.
docker run -v <host folder path>:<path in container> image:tag ...
Self-hosted runner
이제 위에서 언급한 Self-hosted runner의 셋팅에 대해서 알아보도록 하겠습니다. 기본적으로 GIthub Action을 수행하면 runs-on 파트에서 ubuntu, mac OS, windows와 같은 설정들을 줄 수 있습니다. 하지만 이러한 경우에는 Github Action에서 지원해주는 기본적인 cpu만이 지원되는 가상 머신입니다.
저희는 MLOps를 위해서 Github Action을 사용해야하기 때문에 GPU가 있어야합니다.
그러므로, 사용하고자 하는 GPU machine에서 Github Action이 발생했을 때 테스트를 수행할 수 있도록 해주어야 합니다. 이 방법이 바로 Self-hosted runner입니다.
이때 GPU를 사용할 수 있도록 해주는 container image들은 많이 있지만 저는 아래의 이미지를 활용하였습니다.
- https://github.com/myoung34/docker-github-actions-runner
위의 이미지에 기반한 self-hosted runners를 docker container로 띄우고, Github Action workflow를 수행할 것이 있으면 이를 실제 수행하는 container가 runner container 안에 떠서 Github Action script를 수행하는 방식으로 동작하는 self-hosted runner를 추가하는 방법에 대해서 살펴보도록 하겠습니다.
사실 위의 이미지를 사용한다고 하더라도 해당 git repo에서 설정을 하고 다운로드를 할 것들은 없습니다. 단지 아래와 같이 사용할 이미지와 tag에 위 git에서 제공하는 이미지에 대한 정보를 전달해주면 됩니다.
docker run -d --restart always --name github-runner\
-e REPO_URL="https://github.com/<Github Action 적용하려는 repo 주소>" \
-e RUNNER_NAME="git-runner" \
-e RUNNER_TOKEN="<PAT key from Github>" \
-v /var/run/docker.sock:/var/run/docker.sock \ # docker in docker 설정
-v /tmp/github-runner:/home/runner/_work/<git repo이름>/<git repo이름> \ # 위에서 설명한 host와 self-hosted-runner container 공유폴더
myoung34/github-runner:latest # 이미지 정보
위의 내용을 하나씩 살펴보도록 하겠습니다.
우선, REPO_URL에는 Github Action이 테스트를 수행해주었으면 하는 Repository에 대해서 전달해 주면 됩니다. 그리고 RUNNER_NAME에는 어떤 이름의 runner로 Github Action 목록에서 보이면 좋을지를 선택할 수 있으므로 자유롭게 선택할 수 있습니다.
-v 옵션부터는 중요합니다. 위에서 설명드린대로 Docker in Docker 형태로 self-hosted container가 동작하면서 test container를 띄울 수 있어야 하므로 host의 docker socket을 self-hosted runner의 container에 공유해주어야 합니다. 그리고 이 부분은 아래의 부분이 위 코드에서 담당하고 있습니다.
-v /var/run/docker.sock:/var/run/docker.sock \ # docker in docker 설정
다음으로 DinD 만큼 중요한 설정 부분입니다. 위에서 제가 보여드린 yaml 파일대로 구성을 수행하면, /home/runner/_work 의 path에 GIthub Action을 수행할 repo 이름으로 2번 directory가 생기고 해당 폴더 내부에 Github Action의 Checkout Step 에서 가져온 코드가 담기게 됩니다. 이 부분을 host 의 /tmp/github-runner 폴더에 공유해주어서 추후에 test container에서 host의 /tmp/github-runner 폴더에 접근하여 코드를 가져올 수 있도록 할 생각입니다.
-v /tmp/github-runner:/home/runner/_work/<git repo이름>/<git repo이름>
그 후 아래와 같이 잘 작동한다면 준비가 끝난 것을 GIthub Repo에서 확인을 할 수 있습니다.
여기까지 완료가 되었다면 사실 모든 준비가 끝난 것입니다. 이제 전체적인 yml 코드를 한번 더 확인하면 다음과 같습니다.
name: Engine-Project-Test
on:
workflow_dispatch:
inputs:
k8s:
description: 'k8s_config'
default: None
required: false
dist:
description: 'distributed'
default: 'False'
required: false
exp_grp:
description: 'exp_grp'
default: 'CI'
exp_id:
description: 'exp_id'
default: 'temp'
jobs:
default_project_job:
runs-on: [self-hosted, gpu]
steps:
- name: setup node # 간혹 container 내부에서 sh not found 오류가 발생하면 node를 다음과 같이 설치해주세요
uses: actions/setup-node@v2
with:
node-version: '16'
- name: checkout github
uses: actions/checkout@v2
- name: docker hub login
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Dummy Project
run: |
docker run --gpus 1 -v /tmp/github-runner:/workspace -e PYTHONPATH=/workspace <dockerhub>/<repo>:<tag> python3 /workspace/<path>/<to>/main.py
실행
우선 Github Action으로 테스트를 수행할 만한 코드를 구성한 뒤 push해 줍니다. 그 후 Actions 의 탭으로 가면 이전과 다르게 Workflow라는 창이 생성된 것을 보실 수 있습니다.
이제 아래 빨간색 동그라미의 Run Workflow를 누르면 위에서 정의한 inputs와 관련된 빈칸들이 보이고 원하는 값을 설정한 뒤 실행시킬 수 있게 되어 있는 것을 발견하실 수 있습니다. 이 칸에 원하는 값들을 입력한 뒤 아래의 Run Workflow를 입력해주세요.
References
[1] https://zzsza.github.io/development/2020/06/06/github-action/
[2] https://www.hahwul.com/2020/10/18/how-to-trigger-github-action-manually/
[4] https://dev.to/mihinduranasinghe/using-docker-containers-in-jobs-github-actions-3eof
[5] https://mildwhale.github.io/2021-04-24-build-machine-with-m1-macmini/
[6] https://github.com/actions-runner-controller/actions-runner-controller
[7] https://dev.to/mihinduranasinghe/using-docker-containers-in-jobs-github-actions-3eof
[8] https://computingforgeeks.com/how-to-install-github-cli-on-linux-and-windows/
[10] https://dvc.org/blog/cml-self-hosted-runners-on-demand-with-gpus
'MLOps' 카테고리의 다른 글
[MLOps, Acceleration, Jax] Google Jax 와 Flax Library (1) | 2021.12.15 |
---|---|
[MLOps, Acceleration, Jax] Google Jax 란? (0) | 2021.12.13 |
[MLOps] Kubernetes (k8s) Persistent Volume Share w/ nfs (0) | 2021.11.23 |
[Tensorboard] Multi Process 에서 하나의 Tensorboard 에 접근할 때 문제점 (0) | 2021.10.29 |
[유용한 블로그 포스팅 모음] (0) | 2021.10.29 |