1. 개요
지난 글에 이어서 이번에는 kubeflow를 사용해서 데이터 전처리, 모델 학습, 평가까지의 파이프라인을 작성, 구동해보았다. 파이썬에는 kubeflow pipeline 작성을 위해 kfp 라는 라이브러리가 있는데 이를 활용해서 진행했다.
2. 사용환경
OS: 윈도우 11
3. 파이프라인
Kubeflow에서 파이프라인을 작성하는 방법에는 크게 2가지 정도의 방향이 있다. 첫번째는 도커 이미지 단위, 두 번째는 함수 단위로 컴포넌트를 구성해서 파이프라인을 이어주는 방법이다. 첫 번째 방법은 kfp 라이브러리의 dsl 에서 ContainerOp 라는 함수를 사용해서 형성할 수 있다.
3.1 docker recret
kubeflow 파이프라인을 작성하는 방법은 여러가지가 있지만 보통 이미지 방식을 선택하는 것 같았다. 그래서 나도 이미지 기반의 파이프라인을 선택했으며 이때 kubernetes 에서 이미지를 당겨오려면 이미지 저장소에 인증할 정보를 미리 저장해두어야 한다. 여기서는 docker recret이라는 이름의 인증 정보를 미리 형성하여 개인 도커 허브 로그인 정보를 저장해두었다. 쿠버네티스를 시작한 후에 파이프라인이 동작할 네임 스페이스에서 시크릿을 형성해주면 된다.
kubectl create secret docker-registry --docker-server=https://index.docker.io/v1/ --docker-username={아이디} --docker-password={비밀번호} --docker-email={이메일}
위의 커맨드를 입력하면 docker secret이 형성된다. 비밀번호는 도커에 로그인할때 입력하는 실제 비밀번호가 아니라 개인 설정/security 에서 토큰을 발급받은 뒤에 해당 토큰을 입력해야 한다. 깃허브처럼 토큰을 형성하고 개인적으로 저장해두지 않으며 나중에 토큰을 다시 형성하는 번거로움이 있으니까 미리 저장해두자. 시크릿을 형성한 후에는 혹시 모르니까 제대로 시크릿이 형성되었는지 확인해야 한다.
3.2 Image based component
data_op = dsl.ContainerOp(
name="example component",
image="example_image/latest"
).add_volume(
k8s_client.V1Volume(
name='data-pvc'
)
)
위의 예시처럼 kubeflow ui 상에서 표시할 name과 pull할 베이스 이미지를 설정해주면 해당 이미지를 가져와서 자동으로 실행해주는 방식으로 동작한다. 그리고 예시처럼 add_volume이라는 메소드를 사용하면 원하는 저장소를 마운트할 수 있으며 해당 방식으로 파이프라인 컴포넌트간의 저장소를 공유할 수 있다.
다만 위처럼 이미지 방식의 파이프라인은 kubeflow에서 권장하는 방식은 아니며, kubeflow는 재사용가능한 형태의 파이프라인을 작성하는 것을 권장한다. 사용 방식에 익숙해지는 것에 시간이 조금 걸리긴 했지만 확실히 재사용가능한 함수 형태의 파이프라인을 작성하는 것이 수정, 관리에 훨씬 편하다는 느낌을 받았다.
3.3 function based component
두 번째 방식인 함수 형태의 컴포넌트 구성은 kfp.components의 create_component_from_func 이라는 함수를 사용해서 사용할 수 있다.
def example_component(ex_arg='test'):
from library_to_use import example
result = do_something_u_want(ex_arg)
return result
@dsl.pipeline(
name='Pipeline'
)
def pipeline():
example_op = comp.create_component_from_func(example_component)
example_op = data_op(ex_arg='test')
example_op.apply(onprem.mount_pvc(pvc_name="phmpipeline", volume_name='test-phm', volume_mount_path=volume_path))
해당 함수의 사용 방식은 조금 유의할 사항이 있다. 원하는 동작을 하는 함수 내에서 모든 것을 수행해야하며 이것은 필요한 라이브러리를 import 하는 것까지 포함한다. 이때 나는 약간의 문제가 생겼었는데 내가 만든 모델을 사용할 수가 없었다는 점이었다. 내가 작성한 모델을 포함해서 모든 내가 작성한 함수를 사용하려면 파이썬 기본 경로에 내가 만든 코드들을 넣어줘야 했는데 그렇게 되면 이후 관리가 너무 힘들고 번거로울 것 같다는 생각이 들었다. 그래서 생각한 방법은 작성한 모든 코드들을 담은 이미지 파일을 하나 형성하고 해당 이미지를 불러오면서 PYTHONPATH에 추가하고 그 위에서 component가 돌도록 설정하는 것이었다. 이것도 마찬가지로 번거롭지만 딱히 떠오르는 방식이 없었다. 함수 방식의 component가 이미지를 불러오려면 아래처럼 인자만 추가해주면 되었다.
example_op = comp.create_component_from_func(example_component, base_image=BASE_IMAGE_URL)
이때 BASE_IMAGE_URL은 이미지를 당겨올 때처럼 docker pull ~~ 에서 입력하는 형태의 주소를 넣어주면 된다. 나는 도커 허브에 이미지를 업로드하여 당겨오는 방식으로 구성했으며 이미지를 빌드할때 dockerfile에서 PYTHONPATH에 코드의 경로를 추가하는 과정을 넣어주었다.
FROM pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime
ENV PYTHONUNBUFFERED 1
RUN mkdir /src
COPY src/ /src/
ENV PYTHONPATH="${PYTHONPATH}:/src"
ENV PYTHONPATH="${PYTHONPATH}:/src/model"
ENV PATH="${PATH}:/src"
ENV PATH="${PATH}:/src/model"
WORKDIR /src
RUN mkdir /src/pv
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
PYTHONPATH, PATH를 모두 사용한 이유는 그냥 이래야 작동해서 추가했다... 내가 작성한 코드는 모두 src 폴더에 들어있으며 dockerfile은 이미지를 사용할때 src 폴더를 복사한 후 환경변수에 추가, 그리고 해당 폴더로 이동까지 과정이다. 작업 경로를 src 폴더로 지정했기 때문에 위에서 create_component_from_func을 작성할 때는 해당 경로에 있다고 생각하고 작업을 작성해주면 된다. 이때 굉장히 헷갈린 점이 있었는데 dockerfile에서 지정한 작업 경로와 파이프라인을 작성할 때의 경로, 그리고 마지막으로 컴포넌트에서 작동하는 함수의 경로를 주의해야 한다는 것이다.
아까 위에서 파이프라인을 작성할 때 아래처럼 apply 함수를 사용해서 pvc를 마운트해주었다. 적어두진 않았지만 volume_path 는 /src/pv 인데 여기서 작업 경로가 이미 /src 이니까 /pv 만 적으면 될 것이라 생각했었다. 당연히 마운트는 상위 경로의 /pv로 지정되었고 내가 원하는 방향으로 파이프라인이 동작하지 않았다. 컴포넌트를 작성할 때는 이미지에서 설정한 부분은 생각하지 말고 작성해야 한다. 컴포넌트에서 사용할 함수는 도커 이미지에서 설정한 데로 진행하면 된다.
example_op = comp.create_component_from_func(example_component)
example_op = data_op(ex_arg='test')
example_op.apply(onprem.mount_pvc(pvc_name="phmpipeline", volume_name='test-phm', volume_mount_path=volume_path))
이런 방식으로 파이프라인 컴포넌트를 하나 하나 구성해서 순서를 지정하면 문제없이 파이프라인이 구동하게 된다. 순서를 지정하는 방법은 아래처럼 after 라는 함수를 사용해서 이어주면 된다.
# data processing
comp1 = comp.create_component_from_func(func1, base_image=BASE_IMAGE_URL)
comp1 = comp1(args)
comp1.apply(onprem.mount_pvc(pvc_name="phmpipeline", volume_name='test-phm', volume_mount_path=volume_path))
# training
comp2 = comp.create_component_from_func(func2, base_image=BASE_IMAGE_URL)
comp2 = comp2(args)
comp2.apply(onprem.mount_pvc(pvc_name="phmpipeline", volume_name='test-phm', volume_mount_path=volume_path))
comp2.after(comp1)
3.4 매트릭 추가
나는 데이터 전처리-모델 학습-모델 테스트의 총 3단계로 파이프라인을 구성했다. 그리고 마지막 테스트 단계에서 모델 결과를 확인하기 위해 metrics를 추가해주었다. metric를 추가하는 방법은 아래와 같이 컴포넌트 함수에 인자를 추가해주는 방식으로 가능하다.
from kfp.components import OutputPath
...
def predict(mlpipeline_metrics_path: OutputPath("Metrics"), model_path)
import json
accuracy = test_model(model_path)
metrics = {
"metrics": [
{
"name": "Accuracy",
"numberValue": accuracy
}
]
}
print("saving mlpipline metrics")
with open(mlpipeline_metrics_path, 'w') as f:
json.dump(metrics, f)
@dsl.pipeline(
name='Pipeline'
)
def pipeline():
...
pred_op = comp.create_component_from_func(predict, base_image=BASE_IMAGE_URL)
pred_op = pred_op(model_path='pv/result')
...
특이한 점은 predict 함수에는 첫 번째 인자로 OutputPath 가 들어가지만 컴포넌트를 형성할 때는 이것을 신경쓰지 않고 이후의 인자만 입맛대로 넣어주면 된다는 것이다. 이렇게 해주면 파이프라인이 끝난 뒤에 output 에 알아서 결과가 출력된다.
3.5 파이프라인
파이프라인을 모두 작성했다면 아래의 코드를 실행해서 yaml 파일로 파이프라인을 작성할 수 있다. compile 함수 내에는 아까 @dsl.pipeline 을 달아주었던 함수를 넣어주면 된다.
kfp.compiler.Compiler().compile(pipeline,'{}.yaml'.format(experiment_name))
그리고 위처럼 파이썬 내에서 선언을 할 수도 있고 아래의 커맨드라인의 형태로도 컴파일 가능하다.
dsl-compile --py $base_dir/pipeline.py --output $base_dir/experiment_name.yaml
4. 파이프라인 실행
위의 과정을 통해 형성된 yaml 파일을 kubeflow ui 에 접속해서 업로드해주면 파이프라인의 과정을 그래프로 확인할 수 있다. 그리고 큰 문제가 없다면 expriment에서 실행을 했을때 성공하는 화면을 볼 수 있다.
추가로 마지막 predict 컴포넌트에 추가했던 metric도 정상적으로 출력되는 것을 확인할 수 있다.
'1. Engineering > Kubeflow' 카테고리의 다른 글
[Kubeflow] 윈도우11에서 Minikube로 kubeflow 설치하기 (0) | 2023.03.20 |
---|---|
[Kubeflow] ENAS RNN pipeline (0) | 2022.10.20 |
[Kubeflow] Kubeflow 설치 (0) | 2022.10.20 |
[Kubeflow] GNN 파이프라인 (0) | 2022.10.20 |