AI 머신러닝

MLOps 실전: 컨테이너 기반 머신러닝 모델 배포 및 서빙 전략

강코의 코딩 일기 2026. 4. 24. 16:15
반응형

실제 MLOps 환경에서 컨테이너를 활용해 머신러닝 모델을 효율적으로 배포하고 서빙하는 전략과 실무 팁을 공유합니다. 안정적인 운영 노하우를 확인해 보세요.

안녕하세요, 현업에서 MLOps 엔지니어로 일하며 수많은 시행착오를 겪어온 개발자입니다. 오늘은 많은 분들이 궁금해하실 컨테이너 기반 머신러닝 모델 배포 및 서빙 전략에 대한 저의 생생한 경험담을 공유해보고자 합니다. 혹시 이런 고민을 해보신 적 있나요?

  • 개발 환경에서는 잘 동작하던 모델이 프로덕션 환경에서는 엉뚱하게 동작하거나 에러를 뿜어내는 경우
  • 새로운 모델 버전을 배포할 때마다 시스템 전체가 불안정해지는 경험
  • 모델을 스케일 아웃해야 하는데 인프라 관리 부담이 너무 커서 엄두가 나지 않는 상황

저 역시 위와 같은 문제들에 직면하며 밤잠을 설쳤던 기억이 생생합니다. 특히 모델 학습 환경과 추론 환경의 불일치로 인한 문제는 매번 저희 팀의 발목을 잡았죠. 하지만 컨테이너 기술을 MLOps 파이프라인에 적극적으로 도입하면서 이러한 문제들을 상당 부분 해결할 수 있었습니다. 제가 직접 써보니, 컨테이너는 단순히 개발 환경을 격리하는 도구를 넘어, MLOps의 핵심적인 재현성, 이식성, 확장성을 보장하는 데 필수적인 요소임을 깨달았습니다. 이제 그 실전 경험과 노하우를 자세히 풀어보겠습니다.

📑 목차

실전 MLOps: 컨테이너 기반 머신러닝 모델 배포 및 서빙 전략 - candies, sweetmeats, jar, glass jar, container, glass container, confections, confectionery, treats, nibble, sweets, dessert, food, colorful, multicolored, delicious, sugar, candies, candies, jar, sweets, sweets, sweets, sweets, sweets, dessert, food, food, food, sugar, sugar, sugar

Image by Daria-Yakovleva on Pixabay

컨테이너 기반 MLOps, 왜 필수적인가?

저희 팀에서 컨테이너를 도입하기 전에는 모델 배포 과정이 그야말로 '수동 노동'의 연속이었습니다. 모델 학습 환경과 서빙 환경의 OS, 라이브러리 버전, 의존성 등이 달라서 발생하는 문제들은 일상다반사였죠. 특히 Python 기반의 모델들은 의존성 충돌이 잦아, 새로운 모델을 배포할 때마다 기존 서비스에 영향을 줄까 노심초사했습니다.

하지만 컨테이너를 도입하면서 이 모든 것이 달라졌습니다. Docker와 같은 컨테이너 기술은 애플리케이션과 그 실행에 필요한 모든 것을 하나의 독립적인 패키지로 묶어줍니다. 모델, 코드, 런타임, 시스템 도구, 시스템 라이브러리 등 모든 것이 컨테이너 이미지 안에 포함되죠. 제가 느낀 가장 큰 장점은 다음과 같습니다.

재현성과 이식성 확보

컨테이너는 모델이 학습된 환경과 동일한 환경을 어디서든 재현할 수 있게 해줍니다. "내 컴퓨터에서는 되는데?"라는 개발자들의 오랜 숙원을 해결해 준 것이죠. 하나의 컨테이너 이미지만 있으면, 개발 환경, 테스트 환경, 그리고 최종 프로덕션 환경 어디에서든 동일한 방식으로 모델을 실행할 수 있습니다. 이는 모델의 일관된 동작을 보장하며, 예측 불가능한 런타임 오류를 크게 줄여줍니다. 실제로 저희 팀에서는 컨테이너 도입 후 모델 배포 실패율이 70% 이상 감소하는 경험을 했습니다.

확장성과 효율성 증대

머신러닝 모델은 서비스 부하에 따라 유연하게 확장되어야 합니다. 컨테이너는 가볍고 빠르게 시작할 수 있어, 수요 변화에 따라 모델 추론 서비스를 손쉽게 스케일 아웃(Scale-out)하거나 스케일 인(Scale-in) 할 수 있도록 돕습니다. 또한, 컨테이너 오케스트레이션 도구인 Kubernetes와 같은 기술과 결합하면 수십, 수백 개의 모델 인스턴스를 효율적으로 관리하고 배포할 수 있습니다. 이는 자원 활용도를 극대화하고 운영 비용을 절감하는 데 큰 도움이 됩니다.

MLOps 파이프라인과 컨테이너의 만남

저희 팀에서는 컨테이너를 단순히 배포 도구로만 활용하지 않고, MLOps 파이프라인 전반에 걸쳐 통합했습니다. 데이터 준비부터 모델 학습, 검증, 배포, 모니터링에 이르는 모든 단계에서 컨테이너의 이점을 극대화한 것이죠.

데이터 전처리 및 특성 공학 단계

이 단계에서도 컨테이너는 환경 일관성을 제공합니다. 특정 버전의 데이터 처리 라이브러리(예: Pandas, NumPy)에 의존하는 스크립트를 컨테이너화하여, 데이터 과학자가 어떤 환경에서 작업하든 동일한 전처리 결과를 얻을 수 있도록 했습니다. 이는 데이터 파이프라인의 재현성을 높여 모델 학습 결과의 신뢰도를 향상시킵니다.

모델 학습 및 버전 관리 단계

모델 학습 과정 자체도 컨테이너 안에서 실행하도록 구성했습니다. 예를 들어, 특정 버전의 TensorFlow나 PyTorch와 CUDA 드라이버 조합이 필요한 학습 스크립트를 Docker 이미지로 만들어 관리합니다. 이렇게 하면 여러 데이터 과학자가 각기 다른 모델을 학습할 때도 의존성 충돌 없이 독립적인 환경에서 작업할 수 있습니다. 또한, 학습이 완료된 모델 아티팩트와 함께 해당 모델을 생성하는 데 사용된 코드 및 환경(컨테이너 이미지)의 버전을 함께 관리하여, 언제든 특정 시점의 모델을 재현하고 검증할 수 있게 됩니다.

모델 배포 및 서빙 단계

이 부분이 컨테이너가 가장 빛을 발하는 지점입니다. 학습 및 검증이 완료된 모델은 추론 API 서버와 함께 Docker 이미지로 패키징됩니다. 이 이미지는 CI/CD 파이프라인을 통해 자동으로 테스트를 거쳐 컨테이너 레지스트리(예: Docker Hub, AWS ECR)에 저장됩니다. 이후 프로덕션 환경에서는 이 이미지를 가져와 Kubernetes와 같은 오케스트레이터를 통해 배포하고 서빙하게 됩니다. 이 과정은 완전히 자동화되어, 모델 배포에 소요되는 시간을 획기적으로 단축하고 인적 오류를 최소화합니다.

Docker를 활용한 모델 컨테이너화

실제로 저희 팀에서 머신러닝 모델을 컨테이너화할 때 사용하는 기본적인 Dockerfile 구조를 소개합니다. 핵심은 최소한의 이미지 크기와 최적화된 빌드 과정입니다.

Multi-stage Build를 통한 이미지 최적화

모델 학습에 필요한 라이브러리와 추론에 필요한 라이브러리는 다를 수 있습니다. 학습 시에는 불필요한 빌드 도구나 개발 라이브러리가 많이 필요하지만, 실제 서빙 이미지에는 최소한의 런타임만 있으면 됩니다. 저희는 Multi-stage build를 적극적으로 활용하여 최종 이미지 크기를 극적으로 줄였습니다. 실제로 이 방법을 적용하기 전에는 2GB가 넘던 이미지가 500MB 이하로 줄어들어, 배포 속도와 자원 효율성이 크게 개선되었습니다.


# Stage 1: Build Stage (개발 및 의존성 설치)
FROM python:3.9-slim-buster as builder

WORKDIR /app

# 시스템 의존성 설치 (예: gcc, libgfortran 등)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        gcc \
        libgfortran5 \
        && \
    rm -rf /var/lib/apt/lists/*

# 파이썬 의존성 설치 (requirements.txt 사용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 모델 및 애플리케이션 코드 복사
COPY . .

# Stage 2: Final Stage (런타임 환경 구성)
FROM python:3.9-slim-buster

# 시스템 의존성 설치 (필요한 경우만)
# RUN apt-get update && \
#     apt-get install -y --no-install-recommends \
#         libgfortran5 \
#         && \
#     rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 빌드 스테이지에서 설치된 의존성 복사
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY --from=builder /app /app

# 모델 로드 경로 설정 (예: /app/models)
ENV MODEL_PATH=/app/models

# 모델 서빙 포트 설정
EXPOSE 8000

# 애플리케이션 실행 명령어
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

위 예시는 FastAPI와 Uvicorn을 이용한 간단한 모델 서빙 서버를 가정합니다. requirements.txt에는 모델 추론에 필요한 라이브러리 목록이 포함됩니다. Multi-stage build 덕분에 최종 이미지에는 불필요한 빌드 도구들이 포함되지 않아, 보안성도 함께 높아지는 효과를 얻었습니다.

컨테이너 이미지 보안 강화

프로덕션 환경에서 컨테이너 이미지는 보안에 매우 취약할 수 있습니다. 저희는 다음과 같은 실천 사항들을 적용하여 이미지의 보안을 강화했습니다.

  • 최소 권한 원칙: 컨테이너 내부에서 root 권한으로 실행되는 것을 피하고, 전용 사용자(non-root user)를 생성하여 사용합니다.
  • 최소 이미지 사용: python:3.9-slim-buster 와 같은 최소화된 베이스 이미지를 사용합니다. Alpine Linux 기반 이미지는 더욱 작지만, 특정 라이브러리와 호환성 문제가 있을 수 있어 유의해야 합니다.
  • 불필요한 포트 및 서비스 제거: 모델 서빙에 필요한 포트 외에는 모두 닫고, 불필요한 백그라운드 서비스를 실행하지 않습니다.
  • 취약점 스캔: CI/CD 파이프라인에 Trivy, Clair와 같은 컨테이너 이미지 취약점 스캐너를 통합하여, 이미지가 컨테이너 레지스트리에 푸시되기 전에 잠재적인 보안 문제를 감지하고 해결합니다.
실전 MLOps: 컨테이너 기반 머신러닝 모델 배포 및 서빙 전략 - yellow, blue, container, window, color, metal, geometry

Image by ValterM on Pixabay

Kubernetes로 확장 가능한 모델 서빙 환경 구축

Docker로 모델을 컨테이너화했다면, 이제 이 컨테이너들을 효율적으로 관리하고 확장해야 합니다. 이때 Kubernetes가 빛을 발합니다. 저희 팀은 Kubernetes를 도입하여 모델 배포의 자동화, 확장성, 고가용성을 확보했습니다.

Deployment와 Service를 이용한 모델 배포

Kubernetes에서는 Deployment를 사용하여 모델 컨테이너의 인스턴스(Pod) 수를 관리하고, Service를 통해 이 Pod들에 외부 트래픽을 분산합니다.


apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-ml-model-deployment
  labels:
    app: ml-model
    version: v1.0.0
spec:
  replicas: 3 # 초기 Pod 수
  selector:
    matchLabels:
      app: ml-model
      version: v1.0.0
  template:
    metadata:
      labels:
        app: ml-model
        version: v1.0.0
    spec:
      containers:
      - name: ml-model-container
        image: your-registry/my-ml-model:v1.0.0 # 위에서 빌드한 Docker 이미지
        ports:
        - containerPort: 8000
        resources: # 자원 제한 설정 (OOMKilled 방지)
          requests:
            memory: "512Mi"
            cpu: "500m"
          limit:
            memory: "1Gi"
            cpu: "1"
        livenessProbe: # 컨테이너 활성 상태 확인
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 5
        readinessProbe: # 트래픽 받을 준비 상태 확인
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 15
          periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: my-ml-model-service
spec:
  selector:
    app: ml-model
    version: v1.0.0
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8000
  type: LoadBalancer # 또는 ClusterIP, NodePort

위 YAML은 my-ml-model:v1.0.0 이미지를 사용하여 3개의 Pod를 배포하고, 이 Pod들로 트래픽을 분산하는 Kubernetes Deployment 및 Service 예시입니다. livenessProbereadinessProbe를 설정하여 컨테이너가 정상적으로 동작하는지, 그리고 트래픽을 받을 준비가 되었는지 지속적으로 확인하는 것이 중요합니다. 실제로 저희 팀에서는 이 Probe 설정을 통해 서비스 다운타임을 크게 줄일 수 있었습니다.

Horizontal Pod Autoscaler (HPA)를 이용한 자동 확장

Kubernetes의 HPA (Horizontal Pod Autoscaler)는 CPU 사용량, 메모리 사용량 또는 커스텀 메트릭(예: 초당 요청 수)을 기반으로 Pod의 수를 자동으로 조절하여 서비스의 가용성과 성능을 최적화합니다. 저희는 모델 서빙의 CPU 사용량이 70%를 넘으면 자동으로 Pod 수를 늘리도록 설정했습니다. 실제로 특정 시간대에 트래픽이 몰리는 서비스의 경우, HPA 덕분에 수동 개입 없이도 안정적인 성능을 유지할 수 있었습니다.


apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-ml-model-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-ml-model-deployment
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

이 HPA 설정은 my-ml-model-deployment의 Pod 수가 최소 3개에서 최대 10개까지 자동으로 조절되며, Pod의 평균 CPU 사용률이 70%를 초과하면 Pod 수를 늘립니다.

모델 배포 전략: Canary, Blue/Green

새로운 모델 버전을 배포할 때, 모든 사용자에게 한 번에 적용하는 것은 매우 위험합니다. 잠재적인 버그나 성능 저하가 발생했을 때, 서비스 전체에 치명적인 영향을 줄 수 있기 때문이죠. 저희 팀은 점진적인 배포 전략을 사용하여 이러한 위험을 최소화하고 있습니다.

Canary 배포

Canary 배포는 새로운 버전의 모델을 소수의 사용자(예: 5~10%)에게 먼저 노출하고, 문제가 없는지 모니터링한 후 점진적으로 트래픽을 늘려가는 방식입니다. 마치 탄광의 카나리아처럼, 문제가 발생하면 빠르게 감지하고 롤백할 수 있다는 장점이 있습니다.

  • 장점: 위험 부담이 적고, 실제 사용자 트래픽을 통해 새로운 모델의 성능을 검증할 수 있습니다. A/B 테스트와 유사하게 특정 사용자 그룹에만 새로운 모델을 노출하여 피드백을 얻기 용이합니다.
  • 단점: 배포 과정이 상대적으로 복잡하며, 트래픽을 점진적으로 전환하는 로직이 필요합니다 (예: Istio, Nginx Ingress Controller 등).

저희 팀에서는 Istio와 같은 서비스 메시를 활용하여 트래픽을 관리하고 있습니다. 새로운 모델 버전(v1.1)이 배포되면, 초기에 전체 트래픽의 5%만 v1.1로 라우팅하고, 24시간 동안 에러율, 지연 시간, 모델 예측 결과 등을 모니터링합니다. 문제가 없으면 20%, 50%, 100% 순으로 트래픽을 전환하는 방식으로 운영하고 있습니다.

Blue/Green 배포

Blue/Green 배포는 현재 운영 중인 환경(Blue)과 동일한 새로운 환경(Green)을 구축한 후, 모든 트래픽을 한 번에 새로운 환경으로 전환하는 방식입니다.

  • 장점: 배포 과정이 간단하고, 문제가 발생했을 때 즉시 이전 버전(Blue)으로 롤백하여 서비스 중단을 최소화할 수 있습니다.
  • 단점: 두 개의 완벽한 환경을 유지해야 하므로 자원 소모가 크고, 새로운 환경을 프로비저닝하는 데 시간이 소요될 수 있습니다.

저희 팀에서는 중요도가 높고 빠르게 롤백이 필요한 서비스에 Blue/Green 배포를 적용했습니다. 특히 모델의 성능 지표가 민감하게 서비스에 영향을 미치는 경우, 빠르게 이전 버전으로 돌아갈 수 있는 Blue/Green 방식이 효과적이었습니다.

특징 Canary 배포 Blue/Green 배포
배포 방식 소수 사용자에게 점진적 노출 후 트래픽 전환 새로운 환경 구축 후 전체 트래픽 일괄 전환
위험도 낮음 (점진적 적용으로 문제 파악 용이) 중간 (문제 발생 시 서비스 전체 영향 가능성)
롤백 용이성 상대적으로 복잡 (트래픽 전환 로직 관리) 매우 용이 (이전 환경으로 즉시 전환)
자원 효율성 높음 (두 환경을 동시에 완벽히 유지할 필요 없음) 낮음 (두 개의 완벽한 환경 유지)
주요 사용처 새로운 기능/모델 테스트, A/B 테스트, 점진적 롤아웃 빠른 롤백이 중요한 핵심 서비스, 대규모 업데이트
실전 MLOps: 컨테이너 기반 머신러닝 모델 배포 및 서빙 전략 - woman, crowd, soup, the raval, barcelona, dinner, raval, festival, people, food, crowd, crowd, crowd, crowd, crowd, soup, barcelona, dinner, dinner

Image by Antonio_Cansino on Pixabay

MLOps 모니터링 및 로깅

아무리 잘 배포된 모델이라도 지속적인 모니터링 없이는 안정적인 운영을 보장할 수 없습니다. 저희 팀에서는 모델의 성능과 시스템의 건강 상태를 실시간으로 모니터링하고, 발생 가능한 문제를 사전에 감지하고 대응하는 체계를 구축했습니다.

성능 모니터링

Kubernetes 환경에서는 Prometheus와 Grafana 조합이 가장 흔하게 사용됩니다. 저희는 다음과 같은 지표들을 모니터링하고 있습니다.

  • 시스템 메트릭: Pod의 CPU 사용량, 메모리 사용량, 네트워크 I/O. HPA가 적절하게 동작하는지 확인하는 데 중요합니다.
  • 모델 추론 메트릭:
    • 요청 처리량 (Throughput): 초당 요청 수 (RPS)
    • 응답 지연 시간 (Latency): P50, P90, P99 지연 시간
    • 에러율: HTTP 5xx 에러 비율
    • 모델 예측 분포: 모델 예측값의 분포 변화를 모니터링하여 데이터 드리프트(Data Drift)나 모델 성능 저하 징후를 감지합니다.

이러한 지표들을 Grafana 대시보드에서 시각화하고, 특정 임계값을 넘어서면 Slack이나 PagerDuty를 통해 경고 알림을 받도록 설정했습니다. 실제로 모델 예측 분포에 이상 징후가 감지되어, 빠르게 원인을 분석하고 데이터 파이프라인의 문제를 해결한 경험이 있습니다.

중앙 집중식 로깅

여러 컨테이너와 Pod에서 발생하는 로그를 효과적으로 관리하기 위해 중앙 집중식 로깅 시스템을 구축했습니다. 저희는 주로 ELK Stack (Elasticsearch, Logstash, Kibana) 또는 Loki/Grafana Loki를 사용합니다.


# Python FastAPI 애플리케이션 로그 설정 예시
import logging
from fastapi import FastAPI, Request
from prometheus_client import start_http_server, Counter, Histogram

# Prometheus metrics
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP Requests', ['method', 'endpoint'])
REQUEST_LATENCY = Histogram('http_request_latency_seconds', 'HTTP Request Latency', ['method', 'endpoint'])

app = FastAPI()

# 로깅 설정
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

@app.on_event("startup")
async def startup_event():
    start_http_server(8001) # Prometheus metrics endpoint

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    method = request.method
    endpoint = request.url.path

    REQUEST_COUNT.labels(method=method, endpoint=endpoint).inc()
    
    with REQUEST_LATENCY.labels(method=method, endpoint=endpoint).time():
        response = await call_next(request)
    
    logger.info(f"Request: {method} {endpoint} - Status: {response.status_code}")
    return response

@app.get("/health")
async def health_check():
    return {"status": "ok"}

@app.post("/predict")
async def predict(data: dict):
    # 모델 추론 로직 (생략)
    prediction = {"result": "example_prediction"}
    logger.info(f"Prediction made: {prediction}")
    return prediction

컨테이너에서 발생하는 모든 로그는 표준 출력(stdout/stderr)으로 보내고, Kubernetes의 로깅 에이전트(예: Fluentd, Fluent Bit)가 이를 수집하여 Elasticsearch와 같은 중앙 로그 저장소로 전송합니다. 개발자와 운영팀은 Kibana와 같은 도구를 통해 모든 서비스의 로그를 한곳에서 검색하고 분석할 수 있어, 문제 발생 시 신속한 원인 파악과 해결이 가능해졌습니다.

결론: 컨테이너 기반 MLOps, 현업에서 성공하기 위한 열쇠

제가 직접 컨테이너 기반 MLOps를 구축하고 운영하면서 느낀 점은, 이는 선택이 아닌 필수적인 전략이라는 것입니다. 모델의 재현성, 이식성, 확장성을 보장하고, 배포 자동화와 안정적인 운영을 가능하게 하는 핵심적인 기술입니다. 초기 도입에는 학습 곡선이 존재하지만, 장기적으로는 개발 효율성을 극대화하고, 예측 불가능한 문제 발생률을 줄여주며, 궁극적으로는 데이터 과학자와 MLOps 엔지니어 모두의 삶의 질을 향상시킵니다.

물론 이 모든 과정을 완벽하게 구축하는 것은 쉽지 않은 일입니다. 하지만 Docker와 Kubernetes를 중심으로 한 컨테이너 기술은 MLOps의 복잡성을 관리하고, 머신러닝 모델을 안정적으로 프로덕션에 배포하고 서빙하는 데 있어 가장 강력한 도구임이 분명합니다. 이 글이 여러분의 MLOps 여정에 작은 도움이 되었기를 바랍니다.

혹시 여러분의 팀에서는 어떤 컨테이너 기반 MLOps 전략을 사용하고 계신가요? 또는 이 글에서 다루지 않은 팁이나 노하우가 있다면 댓글로 공유해 주세요! 함께 성장하는 기회가 되기를 바랍니다.

📌 함께 읽으면 좋은 글

  • [AI 머신러닝] 생성형 AI 모델, 우리 회사 데이터로 똑똑하게: 도메인 특화 Fine-tuning 실전 가이드
  • [생산성 자동화] Git Hooks 활용 개발 워크플로우 자동화: 커밋 전 코드 품질 검사 및 표준 강제화 전략
  • [기술 리뷰] Bun Node.js Deno 비교 분석: 차세대 자바스크립트 런타임 선택 가이드

이 글이 도움이 되셨다면 공감(♥)댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.

반응형