요즘 MLOps라는 키워드가 점점 더 큰 인기를 얻고 있습니다. AI 시장이 폭발적으로 커지면서 AI 모델을 잘 학습시키고 관리할 필요가 생겼기 때문일 텐데요.

아마 DevOps에 대해 아신다면 MLOps도 금방 이해하실 수 있을 겁니다. ML(머신러닝)과 Ops(운영)의 합성어로, 머신러닝 모델을 개발하고 운영하는 전체 사이클을 효율적으로 갖추기 위한 방법론이거든요. DevOps의 효율적인 소프트웨어 개발 방식을 머신러닝 모델 개발에 대입한 것이 MLOps랍니다.

하지만 MLOps가 정확히 어떤 것인지 이해하려면 개념 설명만으로는 부족합니다. 실제 MLOps에 쓰이는 툴을 배포해보는 것이 확실히 효과적이죠.

그래서 이번 아티클에서는 MLOps를 위한 오픈소스 플랫폼 MLflow를 로컬 Kubernetes 환경에 배포하고 테스트까지 해보는 실습 가이드를 준비했습니다.

혹시 MLOps 툴을 사용해보기엔 머신러닝에 대한 지식이 부족해서 고민되시나요? 걱정하지 마세요! DevOps 엔지니어 관점에서 AI가 전문분야가 아니신 분들도 이해할 수 있도록 풀어서 설명하겠습니다.


MLflow를 언제 사용할까?

MLOps는 DevOps와 달리 관리해야 할 대상이 더 많습니다.

  • 머신러닝 모델 코드 (모델 학습용 Python 스크립트)
  • 데이터 (모델 학습용 데이터셋)
  • 하이퍼파라미터 (Batch Size와 같이 학습 과정에서 쓰이는 파라미터)
  • 모델 구동 환경 (라이브러리 버전 등)

위 요소들은 모두 머신러닝 모델의 성능에 직접적인 영향을 미칩니다. 그래서 모델을 안정적으로 재생산하려면 각각의 변경 이력을 통합해서 관리할 수 있어야 하는데요.

MLflow의 주요 역할이 바로 여기에 있습니다. 머신러닝 모델을 학습한 데이터부터 모델의 알고리즘과 파라미터까지 모두 기록하고 UI로 보여주고요. 원하는 모델을 API 서버로 배포까지 해줍니다.

엔지니어가 모델을 무수히 많이 학습하고 실험하더라도, 언제든 원하는 정확도를 가진 모델의 버전을 쉽게 찾아서 외부에 노출시킬 수 있게 되는 거죠.

한 마디로 요약하자면, MLflow는 머신러닝 모델 정보를 기록 및 시각화하고 모델을 배포할 수 있는 플랫폼입니다.


MLflow 설치 전 우리에게 필요한 아키텍처 훑어보기

MLflow가 머신러닝 모델과 관련된 다양한 정보를 기록하고 버전을 추적할 수 있게 도와준다고 했었죠. 이런 정보들을 안정적으로 저장할 수 있도록 우리는 두 가지 저장소를 MLflow와 함께 배포할 예정인데요. 이번 실습의 전체적인 아키텍처를 그리면 아래와 같습니다.

먼저, 모델의 메타데이터 저장소가 필요합니다. MLflow에서는 이를 백엔드 저장소(Backend Store)라고 하는데요.

모델의 메타데이터란…

  • 모델 실행(Run) ID
  • 모델의 성능 지표 (모델의 정확도 등)
  • 모델 학습에 사용되는 하이퍼파라미터 등을 말합니다.

이런 데이터들은 크기가 작다는 특징이 있고, 빠르게 참조되어야 MLflow UI에서 바로 확인할 수 있기 때문에 관계형 데이터베이스를 사용하는데요. 우리는 MLflow가 지원하는 PostgreSQL를 배포해서 사용할 예정입니다.

다음으로 필요한 것은 모델 관련 아티팩트 저장소(Artifact Store)입니다.

모델 관련 아티팩트란…

  • 모델 바이너리 (.pkl)
  • 모델 관련 지표 추세를 시각화한 이미지 (.png)
  • 학습용 데이터셋 등을 말합니다.

모델 관련 아티팩트는 대용량이기 때문에 MLflow에서는 확장성과 가용성이 뛰어난 AWS S3를 아티팩트 저장소로 권장하는데요.

우리는 클라우드 네이티브 하기 때문에, S3를 완벽히 호환하는 오픈소스 저장소 MinIO를 사용하도록 하겠습니다.

마지막으로 MLflow를 배포합니다. 정확히는 mlflow server 명령어를 실행한 컨테이너를 Kubernetes Deployment로 배포하는 건데, 해당 컨테이너 내부에서는 MLflow UI와 API를 제공하는 경량 FastAPI 서버가 실행되는 것입니다.

지금까지 이야기한 것을 정리하자면, MLflow를 사용해서 머신러닝 실험 결과를 저장하고 관리하려면 백엔드 저장소(PostgreSQL)와 아티팩트 저장소(MinIO)가 필요합니다.

그럼 MLflow와 각 저장소를 minikube 클러스터에 배포하도록 하겠습니다.


이제 minikube 환경에 MLflow를 설치해보자

혹시 minikube를 아직 안 써보셨다면 이번 기회에 도전해보세요!

로컬 PC에서 가상 Kubernetes 클러스터를 자유롭게 올리거나 내릴 수 있는 편한 툴이랍니다. Kubernetes 환경에서 실습이나 테스트를 할 때 유용하게 쓸 수 있을 거예요. minikube 설치와 실행 방법을 쉽게 정리한 가이드를 따라하시다보면 금방 사용할 수 있게 될겁니다. 👉Minikube 소개 및 설치 가이드

실습 환경 준비

먼저 minikube 클러스터를 하나 새로 구축하겠습니다. 클러스터에 할당할 리소스는 아래와 같습니다.

  • CPU: 2 Core
  • RAM: 4 GB

참고

본 가이드는 MLflow 사용을 위한 환경을 구축하고 어떻게 동작하는지 보여드리는 것이 주요 목표이기 때문에 실습 편의를 위해 최소 사양으로 진행합니다. 실제 머신러닝 또는 딥러닝 훈련 스크립트를 사용한다면 더 많은 리소스가 필요한 점 유의하세요.

저는 my-mlflow라는 이름으로 minikube 클러스터를 생성하겠습니다. CPU와 RAM 할당량도 함께 설정합니다.

minikube start -p my-mlflow --cpus 2 --memory 4096

로컬 Kubernetes 클러스터 구축이 완료되었다면 저장소 2개와 MLflow를 클러스터에 배포해야겠죠? 빠르고 손쉽게 배포하기 위해 Helm을 사용하겠습니다.

Helm이란?

애플리케이션 배포에 필요한 모든 리소스를 Chart라는 단위로 묶어서 배포/관리할 수 있는 Kubernetes 패키지 매니저 툴입니다. Helm 설치부터 주요 기능까지 직접 사용해보는 가이드를 따라하시다보면 금방 사용할 수 있게 될 거예요. 👉Helm 설치 및 사용가이드

PostgreSQL 배포

먼저 MLflow의 백엔드 저장소 역할을 해줄 PostgreSQL를 배포해보겠습니다.

PostgreSQL의 공식 Helm Chart를 가져옵니다.

helm repo add bitnami https://charts.bitnami.com/bitnami |
helm repo update

PostgreSQL 배포 설정 저장용 postgres-values.yaml 파일을 원하는 경로에 생성합니다.

architecture: standalone # 실습을 위해 단일 인스턴스만 실행하도록 합니다.
auth:
  database: mlflow_db # MLflow가 사용할 DB를 정의합니다.
  username: mlflow_user # 주의: 실습을 위한 유저명입니다
  password: mlflow_password # 주의: 실습을 위한 비밀번호입니다
primary:
  persistence:
    enabled: true # postgresql Pod가 내려가도 저장된 데이터는 남도록 설정합니다.
    size: 2Gi # postgresql의 저장 공간
  resources:
    requests:
      memory: 512Mi
    limits:
      memory: 512Mi

배포한 리소스를 관리하기 편하게 클러스터에 PostgreSQL 리소스 전용 Namespace를 만들어주고요.

kubectl create ns postgres

아래 helm install 명령어로 PostgreSQL를 배포합니다.

helm install postgres-demo bitnami/postgresql \
--namespace postgres \
-f {postgres-values.yaml 파일 경로}

명령어 실행 후 배포가 완료되면 아래처럼 표시됩니다.

kubectl get all 명령어로 PostgreSQL 실행에 필요한 리소스들이 잘 배포되었는지 확인할 수 있습니다.

다음은 아티팩트 저장소 역할을 해줄 MinIO를 배포할 차례입니다.

MinIO 배포

MinIO 공식 Helm Chart를 가져옵니다.

helm repo add minio https://charts.min.io |
helm repo update

MinIO 배포 설정 저장용 minio-values.yaml 파일을 원하는 경로에 생성합니다.

mode: standalone # 실습을 위해 단일 인스턴스만 실행하도록 합니다.
replicas: 1
rootUser: "minioadmin" # 주의: 실습을 위한 유저명입니다
rootPassword: "minioadmin" # 주의: 실습을 위한 비밀번호입니다
persistence:
  enabled: true # minio Pod가 내려가도 저장된 데이터는 남도록 설정합니다.
  size: 5Gi # MinIO의 저장 공간
resources:
  requests:
    memory: 512Mi
  limits:
    memory: 512Mi

MinIO 리소스를 위한 Namespace도 만들어줍니다.

kubectl create ns minio

그리고 아래 helm install 명령어로 MinIO를 배포합니다.

helm install minio-demo minio/minio \
--namespace minio \
-f {minio-values.yaml 파일 경로}

MinIO 배포가 완료되면 아래처럼 표시됩니다.

kubectl get all 명령어로 잘 배포되었는지 확인해보겠습니다.

MinIO 리소스가 모두 배포되었으면 한 가지 작업이 필요합니다. 머신러닝 관련 아티팩트를 저장할 공간(Bucket)을 MinIO에서 직접 만들어줘야 하거든요. 우린 MinIO 웹 콘솔에서 작업해보겠습니다.

먼저 새 터미널 창에서 아래와 같이 minikube service 명령어를 실행해둡니다. 이렇게 하면 ClusterIP인 Service에 접근할 수 있게 됩니다.

minikube service -p {minikube 클러스터명} -n {MinIO 네임스페이스명} {MinIO 콘솔 Service명}

명령어가 실행되면 해당 Service에 접근할 수 있는 URL을 생성해줍니다. 이 URL은 매번 달라지기 때문에 자신의 실습 환경에서 생성되는 URL을 사용하세요.

웹 브라우저를 통해 해당 URL로 접속하면…

차분한 분위기의 MinIO 웹 콘솔이 실행됩니다. 이제 우리가 minio-values.yaml에 설정한 Username과 Password를 입력해서 로그인합시다. 그럼 아마 Bucket을 새로 생성하라고 할 거예요.

‘Create a Bucket’ 버튼을 누르면 나오는 Bucket 생성 창에서 MLflow를 위한 Bucket 이름(mlflow)을 지정한 다음 ‘Create Bucket’ 버튼을 눌러 생성합니다.

mlflow Bucket이 새로 생성된 걸 확인했다면, 다시 웹 콘솔에서 나옵니다. MinIO Service를 열어뒀던 minikube service 명령어 창도 닫아주세요.

PostgreSQL과 MinIO 작업을 모두 끝냈으니 이제 MLflow를 배포해봅시다!

MLflow Server 배포

먼저 MLflow Helm Chart를 가져옵니다.

helm repo add community-charts https://community-charts.github.io/helm-charts |
helm repo update

그리고 MLflow 배포 설정 저장용 mlflow-values.yaml 파일을 원하는 경로에 생성합니다.

# 백엔드 저장소 설정 (PostgreSQL)
backendStore:
  databaseMigration: true
  postgres:
    enabled: true
    # 배포한 Postgresql의 Service명 + 네임스페이스명을 포함한 클러스터 내 전체 도메인 주소
    host: postgres-demo-postgresql.postgres.svc.cluster.local 
    port: 5432
    database: mlflow_db        # postgres-values.yaml 참고
    user: mlflow_user          # postgres-values.yaml 참고
    password: mlflow_password  # postgres-values.yaml 참고
 
# 아티팩트 저장소 설정 (MinIO)
artifactRoot:
  s3:
    enabled: true
    bucket: mlflow # MinIO에서 새로 생성한 MLflow용 Bucket 이름
    awsAccessKeyId: minioadmin # minio-values.yaml 참고
    awsSecretAccessKey: minioadmin # minio-values.yaml 참고
 
# MinIO 연결 관련 설정
extraEnvVars:
  # 배포한 MinIO의 Service명과 포트번호 + 네임스페이스명을 포함한 클러스터 내 전체 도메인 주소
  MLFLOW_S3_ENDPOINT_URL: "http://minio-demo.minio.svc.cluster.local:9000" 
  MLFLOW_S3_IGNORE_TLS: "true" # 실습 환경이므로 TLS 무시
  MLFLOW_SERVER_ALLOWED_HOSTS: "*" # 클러스터 내 모든 요청 받을 수 있도록 허용
# 모니터링 설정
serviceMonitor:
  enabled: true

MLflow 리소스를 위한 Namespace도 만들어줍니다.

kubectl create ns mlflow

아래 helm install 명령어로 MLflow를 배포합니다.

helm install mlflow-demo community-charts/mlflow \
--namespace mlflow \
-f {mlflow-values.yaml 파일 경로}

MLflow 배포가 완료되면 아래처럼 표시됩니다.

kubectl get all 명령어로 확인해보면 모두 정상 배포된 것을 확인할 수 있습니다.

이제 MLflow의 웹 UI에 접속해보겠습니다. 저희는 minikube를 사용하고 있기 때문에, 클러스터 내 배포된 Service에 접근하려면 새 터미널 창에서 아래와 같이 minikube service 명령어를 실행해둬야 합니다.

MLflow의 Service Type

Helm Chart로 생성되는 MLflow Service의 기본 Type은 ClusterIP입니다. ClusterIP는 클러스터 내부에서만 통신하고 외부로는 노출되지 않는 것이 일반적인데요. 본 가이드는 실습 환경에서 MLflow를 사용하는 것이기 때문에, ClusterIP인 MLflow Service를 외부에서 접근할 수 있도록 강제로 문을 열어둔 것입니다. (minikube service 명령어로) 만약 실제 업무 환경에서 MLflow를 사용한다면 Service의 Type을 LoadBalancer 또는 NodePort로 변경하는 것이 좋습니다.

minikube service -p {minikube 클러스터명} -n {mlflow 네임스페이스명} {mlflow Service명}

그리고 웹 브라우저를 통해 해당 URL로 접속하면…

MLflow 웹 UI가 정상 표시됩니다!🎉


MLFlow가 잘 설치되었는지 확인하기

MLflow는 다른 저장소들과 연결되어야 한다고 했었죠? 우리가 위에서 그린 아키텍처처럼 잘 동작 중인지 직접 확인해보겠습니다.

MLflow의 실험 기록, 모델 저장 등의 기능은 Python 스크립트에서 MLflow 라이브러리의 메소드로 사용 가능합니다. 그래서 MLflow 동작 검증용 Python 스크립트를 작성해서 실행해보겠습니다.

MLflow 검증용 Python 스크립트 배포 및 실행

Kubernetes 환경에서 일회성 작업을 실행할 수 있는 가장 쉬운 방법은 Job 리소스를 활용하는 것입니다. MLflow 동작 검증용 Python 스크립트를 Job으로 배포해서 실행하는 거죠.

우리는 Python 스크립트에서 아래와 같은 동작을 검증해보겠습니다.

  • my-test라는 이름의 MLflow Experiment(모델 실험 단위) 시작
    • mlflow.set_experiment("my-test")
  • 해당 Experiment에서 Run(새로운 모델 학습 단위) 시작
    • 우리가 작성하는 스크립트에서는 동작 검증만 할 거라서 모델 학습 과정은 생략했습니다.
    • mlflow.start_run()
  • 해당 Run 안에서 status: active라는 메타데이터 저장
    • mlflow.log_param("status", "active")
  • 해당 Run 안에서 test.txt라는 아티팩트 저장
    • test.txt에는 MinIO에 저장되는 테스트용 데이터라고 써둠
    • mlflow.log_artifact("test.txt")

그리고 이 Python 스크립트는 ConfigMap 리소스에서 정의할 예정입니다. Job에서 ConfigMap에 저장된 데이터(Python 스크립트)를 가져온 다음 실행하면 끝이라 관리가 깔끔하거든요.

Python 스크립트를 저장하는 ConfigMap과 스크립트를 실행하는 Job을 정의한 매니페스트 파일은 아래와 같습니다. (파일명은 verify-mlflow.yaml로 생성)

apiVersion: v1
kind: ConfigMap
metadata:
  name: verify-script
data:
  verify.py: |
    import mlflow
    import os
 
    mlflow_uri = os.environ.get("MLFLOW_URI")
 
    mlflow.set_tracking_uri(mlflow_uri)
    mlflow.set_experiment("my-test")
 
    with mlflow.start_run():
      mlflow.log_param("status", "active")
      print("[OK] 메타데이터 저장 성공 (PostgreSQL)")
 
      with open("test.txt", "w") as f:
        f.write("MinIO에 저장되는 테스트용 데이터")
 
      mlflow.log_artifact("test.txt")
      print("[OK] 아티팩트 저장 성공 (MinIO)")
 
---
apiVersion: batch/v1
kind: Job
metadata:
  name: mlflow-verify-job
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: checker
        image: python:3.9-slim
        command: ["/bin/sh", "-c"]
        args:
        # mlflow와 boto3(S3 유형 저장소 연동 관련) 설치 후 스크립트 실행
        - pip install mlflow boto3 && python /code/verify.py
        volumeMounts:
        - name: script-vol
          mountPath: /code
        env:
        # 환경변수에 MLflow URI와 MinIO 아티팩트 업로드를 위한 URI 및 계정정보 등록 
        - name: MLFLOW_URI
          value: "http://mlflow-demo.mlflow.svc.cluster.local"
        - name: MLFLOW_S3_ENDPOINT_URL
          value: "http://minio-demo.minio.svc.cluster.local:9000"
        - name: AWS_ACCESS_KEY_ID
          value: "minioadmin"
        - name: AWS_SECRET_ACCESS_KEY
          value: "minioadmin"
      volumes:
      - name: script-vol
        configMap:
          name: verify-script

💭컨테이너 환경변수에 MinIO 계정 정보를 텍스트로 저장해도 괜찮을까?

MLflow로 아티팩트를 저장하려면 MinIO의 ID와 패스워드가 필요한데요. 지금은 실습 환경이지만, 실제 업무 환경에서 위와 같이 MinIO 계정 정보를 텍스트로 저장해두면 보안에 취약하고 위험합니다. 모델 학습용 스크립트를 실행하는 Kubernetes 리소스에 MinIO 계정 정보를 환경변수로 안전하게 사용하려면 어떻게 해야 할까요?

그리고 kubectl apply -f {매니페스트 파일명} 명령어를 실행해서 해당 ConfigMap과 Job을 배포하면 Python 스크립트가 동작하는 Pod가 생성될 텐데요. 해당 Pod의 로그를 아래 명령어로 확인해보면 실시간으로 Pod 내에서 MLflow 라이브러리가 설치되고 스크립트가 실행되는 것을 확인할 수 있습니다. (우리가 사용하는 Pod의 이미지에는 MLflow가 설치되어있지 않아 새로 설치하는 데에 시간이 꽤 걸릴 수 있습니다.)

kubectl logs {Job으로 생성된 Pod 이름} -f

라이브러리 설치 후 스크립트가 실행되면 이렇게 PostgreSQL에 메타데이터를, MinIO에 아티팩트를 저장했다고 뜹니다. MLflow와 각 저장소가 정상 동작하고 있다는 의미입니다. kubectl logs -f 명령어로 Pod의 로그를 확인하는 중에 위 로그가 떴다면 Job 동작이 끝난 것이기 때문에 CTRL + C 단축키로 나오면 되겠습니다.

💭스크립트를 실행할 때마다 MLflow를 설치해야 할까?

이번 실습에서 사용한 MLflow 검증용 스크립트는 MLflow 라이브러리가 설치되지 않은 컨테이너(python:3.9-slim)에서 실행됩니다. MLflow와 다른 저장소들이 잘 연동되었는지 확인하기 위한 용도라고는 해도, 반복 실행하게 될 경우에는 시간을 너무 잡아먹겠죠. MLflow의 기능을 사용하는 스크립트를 컨테이너로 실행할 때 매번 필요한 라이브러리를 설치하지 않으려면 어떻게 해야 할까요?

실제로 잘 저장되었는지 확인하기 위해 MLflow 웹 UI에 다시 접속해보겠습니다. 먼저 새 터미널 창에서 아까와 같은 minikube service 명령어를 실행하고요.

생성된 URL을 통해 MLflow 웹 UI에 접속하면…

my-test Experiment가 생성되었네요! 클릭해서 들어가봅시다…

그럼 Run이 새로 저장되어있는 걸 확인할 수 있습니다. Source 컬럼을 보면 우리의 스크립트 파일명인 verify.py도 기록되어있네요. 저장된 Run 이름을 클릭합니다.

그럼 Run의 세부 내용을 볼 수 있는 페이지가 표시됩니다. 아래 Parameters 패널에 우리가 스크립트에서 기록했던 메타데이터 status: active가 잘 저장되어있네요. 즉, 우리가 MLflow와 연동한 백엔드 저장소(PostgreSQL)가 잘 작동 중이라는 뜻입니다.

누가 MLflow에서 Run을 생성했을까?

MLflow의 Run 상세 페이지에서 오른쪽 About this run 패널을 보면 Created byroot로 표시되어있습니다. 우리가 MLflow를 배포할 때 별도의 인증 기능을 설정하지 않았기 때문에 Root 권한으로 실행된 것인데요. 실제 업무 환경에선 인증 기능이 설정되어 있는 것이 좋습니다. 누가 모델 학습을 실행했는지 기록이 되고 추적할 수 있거든요. 지금은 MLflow를 배포하고 잘 동작하는지 검증하는 데에 초점을 맞추지만, 기회가 된다면 이런 인증 기능까지 설정하는 내용도 다뤄보도록 하겠습니다.

Run 상세 페이지의 탭 중에 가장 오른쪽에 있는 Artifacts는 해당 Run으로 인해 저장된 아티팩트가 표시되는 곳입니다. 클릭해서 들어가면…

이렇게 화면 왼쪽에 저장된 아티팩트(test.txt)가 표시되고, 아티팩트 파일명을 클릭하면 그 내용도 잘 저장되어 있음을 확인할 수 있습니다. 우리가 배포한 아티팩트 저장소(MinIO)가 MLflow와 잘 연동되고 있다는 뜻입니다.

지금까지 minikube 클러스터에서 MLflow와 PostgreSQL, MinIO를 모두 배포하고, 잘 연동되어 동작하는지 검증해봤습니다.

만약 실습을 마쳤다면 사용 중이던 minikube 클러스터를 아래 명령어로 중지합니다. minikube 클러스터는 나중에 다시 실행할 수 있으니 걱정마세요!

minikube stop -p {minikube 클러스터명}

마무리

MLflow는 머신러닝을 학습할 때 기록해야 하는 데이터를 효율적으로 관리할 수 있고 웹 UI에서 쉽게 확인할 수 있는 툴인 것을 이번 실습을 통해 알 수 있었습니다.

MLflow와 백엔드 저장소(PostgreSQL), 아티팩트 저장소(MinIO)로 구성된 아키텍처도 각 컴포넌트를 직접 Helm으로 배포하면서 자연스럽게 익히셨을 텐데요.

다음은 MLOps의 꽃이라고 할 수 있는 모델 학습 기록과 추적을 맛볼 차례입니다. 우리가 지금까지 배포한 MLflow 플랫폼을 통해 머신러닝 스크립트를 실행하고, 하이퍼파라미터를 수정해가며 기록하고 추적하는 실습을 이어지는 아티클에서 다뤄보도록하겠습니다.

읽어주셔서 감사합니다. 오늘도 행복한 하루 되세요😸


함께 읽어보면 좋은 아티클