안녕하세요~~
오늘 작성해볼 내용은 머신러닝 모델을 위한 CI / CD 파이프라인입니다.
- CI는 Continous Integration
- CD는 Continous Deploy 혹은 Continuous Delivery
이는 사람마다 해석이 조금씩 다른 거 같습니다. 뭐 굳이 나누나 싶긴 한데 저는 CI는 배포 전까지의 모든 단계를 이야기하고 CD는 배포하는 단계로 정의하고 글을 작성하겠습니다..
그리고 CI / CD가 뭐야?라고 누군가 저에게 물어보면 저는 "자동화하는 거요..."라고 대답할 거 같습니다.
머신러닝 관점에서 CI / CD를 한다는 것은 모델러가 코드를 Github에 업로드했을 때 모든 과정이 자동화되어 사용자에게 전달되게 끔 하는 것이라고 생각합니다. 이는 비즈니스의 성격과 모델의 성격에 따라 옳을 수도 그를 수도 있습니다. 모델의 결과를 보고 수동 혹은 자동 테스트를 거친 뒤 배포 파이프라인을 실행할 수도 있고 조건에 따른(metrics) 혹은 배포 전략(canary 등)에 따른 A/B 테스트를 포함한 배포 자동화를 할 수도 있습니다. 그렇지만 그냥.. 다 자동화해보겠습니다.
사실 일반 소프트웨어 관점에서는 CI / CD만 필요한 경우가 많은데 머신러닝 모델 관점에서는 CT라는 과정이 추가적으로 필요하기도 합니다. Continous Training이라는 개념인데, 머신러닝 모델은 학습할 때 사용한 데이터를 가장 잘 표현하는 함수라고 이야기할 수 있습니다. 학습되어야 하는 데이터가 변경된다면(트렌드의 변화, 요구사항의 변화) 재학습이 필요합니다. 지속적으로 학습을 해주는 개념도 있지만 그건 조금 더 공부한 뒤 나~중에 포스팅하겠습니다.
먼저 그림을 한번 그려봤습니다. 모델러의 Github Repository에 1. Code Push부터 쿠버네티스에 BentoML로 패킹한 모델을 ArgoCD를 통해 배포하는 15. Deploy Model Serving까지의 과정이 전부 자동화되어있는 구상도입니다
사용하고자 하는 Tool의 종류는 다음과 같아요(구상도의 순서에 따라 나열)
- Github
- Github Actions
- Docker
- Github Packages
- ArgoCD
- Kubernetes - Job
- BentoML
- Kubernetes - Deployment
- Kubernetes - Service
1. Code Push
머신러닝 모델 CI / CD를 한다고 했으니 당연히 머신러닝 모델이 있어야 합니다. 제가 만들 머신러닝 모델은 Iris 꽃 품종 분류하는 모델이에요. Scikit-learn이라는 라이브러리를 통해 데이터를 불러오고 모델을 정의하고 학습하겠습니다. 그 코드를 작성해봅시다.
# train.py
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
# Load data
X, y = load_iris(return_X_y=True)
# Preprocessing
train_x, test_x, train_y, test_y = train_test_split(X, y, test_size=0.2)
# Model Train
clf = SVC()
clf.fit(train_x, train_y)
# Model Evaluate
pred_y = clf.predict(test_x)
test_acc = accuracy_score(pred_y, test_y)
print(f"test accuracy: {test_acc:.4f}")
머신러닝 모델 학습은 보통 위와 같이 4단계를 거칩니다.
- 데이터 준비
- 데이터 전처리
- 모델 학습
- 모델 평가
사실 이따가 코드에 이것저것 추가가 되게 될 건데 일단 간단하게 학습하는 코드를 작성했습니다. 그럼 이제 이 코드를 저장할 레포지토리가 필요한데요. 레포지토리의 이름을 저는 cicd-for-mlcicd-for-ml로 지었습니다. https://github.com/new가면 바로 만들 수 있어요!~ 완성!
이제 알아서 앞서 작성한 train.py를 올려주세요
지금까지 한 게 1. Code Push입니다!
2. Trigger
앞 서 Code를 Push 했습니다. 뭔가 이벤트가 발생한 거죠?. Code Push라는 이벤트에 따라 그림에서의 3. Build, 4. Push, 5.Version Update가 자동으로 이루어져야 합니다. 이걸 도와주는 툴이 바로바로 Github Actions인데요. Github Actions 뿐만 아니라 특정 이벤트에 따라 워크플로(cicd)를 수행해주는 다양한 툴이 존재해요. 여기에 2022년에 유행하는 다양한 CI / CD 툴을 소개해줍니다. 참고해주세요. 이는 곧 목적을 이룰 수 있는 다양한 툴이 있는데! 그중 우리는 Github Actions를 사용하겠다는 이야기입니다. 툴은 어떠한 목적을 이루기 위한 도구일 뿐이고 언제든 상황에 따라 대체될 수 있어요. 이 이야기는 앞으로도 계속할 겁니다.
앞서 워크플로라는 말을 했는데 "특정 이벤트가 발생했을 때 뭔갈 하겠다"라는 걸 정의해야 합니다. 여기서 "뭔가" 이게 바로 워크플로라고 이야기할 수 있습니다. 하나의 Workflow는 여러 개의 Step로 구성할 수 있습니다. 부족한 설명이지만 일단은 이렇게만 설명할게요.
또다시 정리해보면 우리가 만들 Workflow는 Code Push라는 이벤트에 따라 실행되고 Workflow가 하는 일은 3가지 Step으로 구성됩니다. 그건 바로 3. Build, 4. Push, 5. Version Update : 계속 반복했는데 이해가 됐길..
Github Actions은 Github에서 무료로 제공해주는 CI / CD 툴이에요. Yaml 형식으로 Workflow를 정의해야 합니다. 그리고 레포지토리의 .github/workflows 라는 위치에(고정) Yaml을 정의해야 합니다. 저는 파일 이름을 ci_train.yaml로 지었습니다.
cicd-for-ml # Repository
├── .github
│ └── workflows
│ └── ci_train.yaml
└── train.py
이제 파일의 구성을 간단히 작성하고 설명한 뒤 내용을 채워보겠습니다.
name: CI for Train
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v3
- name: Docker Build
run: echo docker build
- name: Docker Push
run: echo docker push
- name: Update Version
run: echo update version
앞서 Github Actions을 간단히 설명할 때 하나의 Workflow는 여러 개의 Step으로 구성할 수 있다고 했는데, 사실 하나의 Workflow는 여러 개의 Job으로 구성될 수 있고 또 하나의 Job은 여러개의 Step으로 구성될 수 있어요.
지금 위의 예시에선 CI for Train이라는 workflow가 build라는 하나의 job으로 구성되고 build라는 job은 4개의 step으로 구성된 모습을 볼 수 있습니다.
그리고 위 파일을 Github 레포지토리에 업로드한 뒤 레포지토리의 Actions 탭에 가보면 실행 중인 Workflow를 확인할 수 있어요.
왼쪽에 보면 All workflows 아래에 CI for Train이라는 이름을 가진 workflow를 볼 수 있고 오른쪽엔 실행된 workflow들을 볼 수 있죠?. 이를 클릭해주세요
위 사진에서 왼쪽을 보면 Jobs라고 나와있고 그 아래에 build라고 적혀있습니다. build라는 job 하나로 구성된 workflow를 보고 있는 거죠. 그리고 오른쪽을 보면 build라고 하나만 보이는데 여러 개의 Job으로 구성했다면 여러 개가 보이게 될 거고 Job 간에 종속성을 가지게 할 수도 있습니다. Job1이 실행된 뒤 특정 조건에 따라 Job 2가 실행되게도 할 수 있다는 이야기예요.
그러면 build도 클릭해주세요.
자 여기서 오른쪽을 보면 우리가 정의한 4개의 Step. checkout repository, docker build, docker push, update version 이외의 Step도 볼 수 있는데 정의한 것 이외의 Step은 자동으로 생성된 것입니다. 정의한 워크플로우를 자세히 봤다면 눈치챘겠지만 워크플로를 어떠한 환경에서 실행할지 선택할 수 있어요. 그 환경을 Runner라고 부르는데 하나의 Virtual Machine을 대여했다고 생각하면 돼요. Github에서 잠깐 동안 사용할 수 있는 컴퓨터를 대여해준 거죠. 그 컴퓨터를 사용하도록, 또 끝낼 때 종료하도록 해야 하기 때문에 앞 뒤에 Step들이 필요합니다. (신경 쓸 내용은 아니에요)
이제 3, 4, 5번을 위한 안에 내용을 잘 채워보겠습니다.
3. Docker Build
이번에 다룰 내용은 머신러닝 모델 학습을 위한 환경 및 코드를 도커로 컨테이너 이미지를 만들고 Github Packages라는 Image Registry에 업로드하는 내용입니다.
도커는 컨테이너 기술을 위한 가상화 플랫폼입니다. 도커를 이용해 적절한 이미지를 만들면 어느 환경이든 도커가 설치되어 있다면 간단히 환경을 복제할 수 있죠. 사실 이미지를 위한 가상화 플랫폼은 도커 이외에도 존재하지만 그중 도커를 사용해서 이미지를 만듭니다. 사실상 표준이 되었죠.
이번 장에서 필요한 명령어는 다음과 같습니다.
docker build -t <ghcr.io>/<username>/<image_name>:<version> -f <Dockerfile> <PATH>
docker build라는 명령어에 꼭 필요한 옵션들입니다.
- -t, --tag: list, Name and optionally a tag in the 'name:tag' format
- -f, --file: string, Name of the Dockerfile (Default is 'PATH/Dockerfile')
docker build --help 명령어를 입력하면 다양한 옵션 및 설명을 확인할 수 있고 위 내용도 여기서 가져왔다.
-t에 들어가는 내용은 생성되는 이미지의 "name:tag"이다. 예를 들어 iris_train:0.0.1와 같다. 여기서 iris_train이 이미지의 이름이고 0.0.1 이 tag이다.
-f에 들어가는 내용은 Dockerfile의 위치이다. default 값이 PATH/Dockerfile 이다보니 PATH에 Dockerfile이라는 이름으로 Dockerfile을 정의했다면 생략해도 된다.
PATH는 도커 빌드가 실행되는 곳의 위치이다. 위치에서 빌드가 진행되므로 파일을 추가하는 등의 작업을 할 때 상대 경로를 잘 생각해야 한다.
Dockerfile에 대한 이야기를 안 했는데 Dockerfile은 도커 빌드를 어떻게 할지? 작성하는 파일이다.
도커 이미지를 만든다는 것은 가상의 어떠한 환경, 즉 이미지를 만든다는 건데 기본적으로 어떠한 이미지 위에 내가 필요한 파일을 추가하거나 필요한 패키지를 설치하거나 해서 새로운 이미지를 만들어 내는 것이다.
FROM ubuntu:20.04
기본 이미지를 지정하는 것이 FROM 이다. ubuntu 20.04 버전의 이미지를 가져와 기본 이미지로 선택하고 그 위에 뭔갈 설치하고 추가할 수 있다. 그리고 ENTRYPOINT, CMD 명령어를 통해 이미지가 생성되고 난 뒤 수행할 명령어를 지정할 수도 있다.
# Dockerfile
FROM ubuntu:20.04
RUN apt update
RUN apt install python3.8 -y
ENTRYPOINT ["python3.8"]
CMD [ "--version" ]
ubuntu 20.04 에는 기본적으로 python이 설치되어 있지 않기 때문에 apt를 업데이트 한 뒤 apt를 통해 python3.8 버전을 설치했고 ENTRYPOINT와 CMD를 통해서 python의 버전을 확인했다.
이 도커 파일을 가지고 이미지를 만들고 실행하는 명령어는 다음과 같다.
docker build -t ubuntu:test .
docker run ubuntu:test
자 이제 기본적인 설명은 여기까지 하고.. 우리에게 필요한 도커 이미지를 만들어보자. 내가 만들 도커 이미지는 python 환경에 scikit-learn와 bentoml이 설치되어야 한다. 그리고 train.py라는 파일을 실행해야 한다.
FROM python:3.8
RUN pip install scikit-learn==1.1.0 bentoml==1.0.0
ADD train.py .
ENTRYPOINT ["python"]
CMD [ "train.py" ]
앞서 기본 예시에선 ubuntu 20.04에 파이썬을 직접 설치했지만 python3.8 이미지부터 시작하면 파이썬 설치하는 과정을 생략할 수 있다! 사실 이 python:3.8이라는 이미지는 도커 허브라는 공개 레지스트리에서 제공하는 공식 이미지인데 (앞에 유저 이름이 붙어있지 않은 ubuntu 이미지도 마찬가지) 이 또한 분명 어떠한 이미지로부터 시작되어 만들어진 이미지이다. Docker hub에서 python이라 검색한 뒤 3.8이라는 태그를 가지는 이미지를 찾아보면 아래와 같은 화면을 볼 수 있다. 링크
ENTRYPOINT와 CMD의 차이점도 검색해서 알고 계시면 좋고 도커는 이미지를 생성하기 위해 레이어를 쌓는 계층구조로 되어 있고 이를 통해 캐싱을 지원하므로 도커 빌드를 위한 시간을 단축할 수 있는 점을 잘 이용해서 도커 파일을 작성해야 합니다.
그러면 위의 도커 파일을 실행하기 위해선 다음과 같은 폴더로 구성되어 있어야 합니다.
.
├── Dockerfile
└── train.py
여기서 아래 명령어를 통해 빌드합니다.
docker build -t iris_train:0.0.1 .
맨뒤의 "."은 PATH를 의미합니다. 이는 옵션이 아닌 필수로 입력해야 하는 값인데 docker build를 수행하는 위치를 의미합니다.
ADD train.py . 에서 train.py는 PATH에 의한 현재 위치(".")에 위치하는 파일을 이야기하고 뒤에 있는 "." 은 이미지 내에서의 현재 위치를 의미합니다. 그러니까 현재 폴더에 있는 train.py라는 파일이 도커 이미지에 복사되는 것입니다.
그리고 이미지를 실행하면 ENTRYPOINT와 CMD로 입력한 값을 통해 학습이 진행된 것을 확인할 수 있습니다.
이번 장에서 하려고 했던 내용은 Github Actions안에서 도커를 빌드하는 것인데 4번 Docker Push를 하면서 같이 설명하겠습니다.