이 글에서 다루는 것

MLflow를 PostgreSQL + S3 백엔드로 구성하여 Kubernetes에 Helm 배포하고, 커스텀 이미지와 Ingress를 연동합니다.

선수지식


이 단계에서 해결하려는 문제

한 프로젝트에 여러 버전의 모델이 등장하면, 각 실험의 파라미터/메트릭/아티팩트를 체계적으로 추적할 서버가 필요합니다. 로컬 MLflow UI(SQLite + file 저장)로는 팀 공유와 아티팩트 영속성이 부족합니다. 이 단계에서는 MLflow를 PostgreSQL(메타데이터) + S3(아티팩트) 백엔드로 구성하고 Helm으로 Kubernetes에 배포합니다.


📐 아키텍처 구성도

03


🐳 커스텀 MLflow Docker 이미지 제작

공식 MLflow 이미지는 PostgreSQL 드라이버(psycopg2)가 포함되어 있지 않아 오류가 발생합니다.

Dockerfile

FROM ghcr.io/mlflow/mlflow:v2.13.0
RUN pip install psycopg2-binary boto3

빌드 & 푸시

docker build -t ghcr.io/your-id/mlflow:with-psycopg2-and-s3 .
docker push ghcr.io/your-id/mlflow:with-psycopg2-and-s3

🛠 Helm Chart 구성 요소

Chart.yaml

apiVersion: v2
name: mlflow
description: A Helm chart for deploying MLflow on Kubernetes
version: 0.1.0
appVersion: "2.13.0"

values.yaml

image:
  repository: hoizz/mlflow
  tag: with-psycopg2-and-s3
  pullPolicy: IfNotPresent

env:
  defaultArtifactRoot: s3://mlflow-artifacts-keonho

service:
  type: ClusterIP
  port: 5000

ingress:
  enabled: true
  className: nginx  # 반드시 명시해야 NGINX가 라우팅함
  host: mlflow.local
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/proxy-body-size: 10m
    nginx.ingress.kubernetes.io/ssl-redirect: "false"

📄 Deployment 구성 (핵심 부분)

    command: ["mlflow", "server"]
    args:
      - "--backend-store-uri=postgresql://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)"
      - "--default-artifact-root={{ .Values.env.defaultArtifactRoot }}"
      - "--host=0.0.0.0"
      - "--port=5000"
      - "--serve-artifacts"  # 이게 있어야 artifacts UI/REST 지원 가능
    env:
      - name: DB_USER
        valueFrom:
          secretKeyRef:
            name: mlflow-db-secret
            key: username
      - name: DB_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mlflow-db-secret
            key: password
      - name: DB_HOST
        valueFrom:
          configMapKeyRef:
            name: mlflow-db-config
            key: host
    envFrom:
      - secretRef:
          name: aws-credentials-secret

전체 코드: GitHub (mlflow-deployment.yaml)


🌐 Ingress 구성

로컬 Kubernetes에서는 LoadBalancer가 작동하지 않으므로(퍼블릭 IP 없음), DaemonSet + hostNetwork 방식으로 Ingress를 구성합니다.

# Pod가 노드와 동일한 네트워크 사용하도록 ingress-nginx 커스텀
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.hostNetwork=true \
  --set controller.dnsPolicy=ClusterFirstWithHostNet \
  --set controller.kind=DaemonSet
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mlflow-ingress
  namespace: mlflow
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: mlflow.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mlflow-service
                port:
                  number: 5000

🚀 배포

kubectl create namespace mlflow

helm install mlflow mlflow-helm/ -n mlflow

🧪 검증

항목확인
MLflow UIhttp://mlflow.local 접근 확인
실험 등록mlflow.log_param, mlflow.log_metric, log_model()
S3 연동.mlruns 폴더가 S3에 생성되는지 확인
PostgreSQL 연동실험, run metadata가 DB에 들어가는지 확인

🚨 트러블슈팅

TS_01: psycopg2 모듈 없음 → 500 오류

ModuleNotFoundError: No module named 'psycopg2'

해결: 커스텀 Docker 이미지에서 pip install psycopg2-binary 포함


TS_02: Calico IPIP 문제로 VM(PostgreSQL) 통신 실패

# Calico가 IPIP 터널링(Protocol 4)을 사용해 Pod → 외부로 패킷 전송
tcpdump -i ens33 proto 4
# PostgreSQL이 설치된 VM이 IPIP 패킷을 Drop함

해결1: SNAT 강제 적용 (로컬 테스트 환경)

iptables -t nat -A POSTROUTING -s {pod-network-CIDR} -d {DB_IP} -j MASQUERADE

해결2: Calico IPIP 터널 비활성화 (VM-Pod 직접 통신 필요시)

kubectl edit ippool default-ipv4-ippool
spec:
  ipipMode: Never
  natOutgoing: true

# 이후 모든 Pod 재시작 + 노드 간 L3 라우팅 필요

설계 판단 (Why This Way?)

공식 MLflow 이미지에 psycopg2/boto3가 없어 커스텀 이미지가 필수이며, –serve-artifacts로 프록시 모드를 활성화해야 클라이언트가 직접 S3 인증 없이 아티팩트에 접근할 수 있습니다. 로컬 환경에서는 Ingress DaemonSet + hostNetwork로 클라우드 LB 없이 접근하고, 모델 URI는 Stage 기반으로 설정하여 핫스왑 구조와 연결했습니다.


다음에 읽을 글

MLOps 플랫폼 구축 4단계: Airflow GitSync + 외부 PostgreSQL + Secret 연동 — Airflow Helm 배포와 GitSync DAG 동기화