🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

실전 MLOps 파이프라인 구축 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 9. · 13 Views

실전 MLOps 파이프라인 구축 완벽 가이드

머신러닝 모델을 개발부터 배포, 운영까지 자동화하는 MLOps 파이프라인을 처음부터 끝까지 구축하는 방법을 다룹니다. 초급 개발자도 따라할 수 있도록 실무 예제와 함께 설명합니다.


목차

  1. 프로젝트_아키텍처_설계
  2. 학습_파이프라인_구축
  3. 모델_레지스트리_연동
  4. 자동_배포_파이프라인
  5. 모니터링_대시보드
  6. 문서화와_운영_가이드
  7. 필요시 HPA 설정 변경

1. 프로젝트 아키텍처 설계

김개발 씨는 데이터 사이언스 팀에 합류한 지 2개월 된 주니어 ML 엔지니어입니다. 어느 날 팀장님이 다가와 말했습니다.

"우리 팀에서 만든 모델들, 이제 체계적으로 관리해야 해요. MLOps 파이프라인 좀 구축해볼래요?" 김개발 씨는 막막했습니다.

대체 어디서부터 시작해야 할까요?

MLOps 아키텍처 설계는 머신러닝 시스템의 청사진을 그리는 작업입니다. 마치 건물을 짓기 전에 설계도를 그리는 것처럼, 데이터 수집부터 모델 배포까지 전체 흐름을 미리 계획합니다.

이것을 제대로 설계하면 나중에 시스템을 확장하거나 문제를 해결하기가 훨씬 수월해집니다.

다음 코드를 살펴봅시다.

# MLOps 프로젝트 구조 설계
mlops_project/
├── src/
│   ├── data/           # 데이터 처리 모듈
│   │   ├── ingestion.py
│   │   └── validation.py
│   ├── training/       # 학습 파이프라인
│   │   ├── train.py
│   │   └── evaluate.py
│   ├── serving/        # 모델 서빙
│   │   ├── api.py
│   │   └── predict.py
│   └── monitoring/     # 모니터링
│       └── metrics.py
├── pipelines/          # 파이프라인 정의
│   └── training_pipeline.py
├── configs/            # 설정 파일
│   └── config.yaml
├── tests/              # 테스트 코드
└── docker/             # 컨테이너 설정

김개발 씨는 입사 2개월 차 주니어 ML 엔지니어입니다. 지금까지는 주피터 노트북에서 모델을 만들고, 학습 결과를 엑셀 파일로 정리하는 것이 전부였습니다.

그런데 팀장님이 갑자기 MLOps 파이프라인을 구축하라니, 어디서부터 손을 대야 할지 막막했습니다. 선배 개발자 박시니어 씨가 커피를 건네며 다가왔습니다.

"처음이라 막막하지? 일단 큰 그림부터 그려보자.

MLOps는 결국 ML과 DevOps의 만남이야." 그렇다면 MLOps 아키텍처란 정확히 무엇일까요? 쉽게 비유하자면, MLOps 아키텍처는 마치 공장의 생산 라인과 같습니다.

원재료인 데이터가 들어오면, 여러 공정을 거쳐 완제품인 예측 서비스가 나가는 것입니다. 각 공정이 잘 연결되어 있어야 품질 좋은 제품이 일정하게 생산됩니다.

MLOps도 마찬가지로 데이터 수집, 전처리, 학습, 평가, 배포라는 공정이 유기적으로 연결되어야 합니다. 체계적인 아키텍처가 없던 시절에는 어땠을까요?

데이터 사이언티스트들은 각자의 노트북에서 모델을 만들었습니다. 누가 어떤 데이터로 어떤 모델을 만들었는지 아무도 몰랐습니다.

더 큰 문제는 모델을 배포할 때 생겼습니다. "제 노트북에서는 잘 돌아가는데요?"라는 말이 매일 들렸습니다.

프로젝트가 커질수록 혼란은 눈덩이처럼 불어났습니다. 바로 이런 문제를 해결하기 위해 체계적인 MLOps 아키텍처가 등장했습니다.

잘 설계된 아키텍처를 사용하면 팀원 누구나 같은 환경에서 작업할 수 있습니다. 또한 문제가 생겼을 때 어디서 발생했는지 빠르게 파악할 수 있습니다.

무엇보다 새로운 모델을 추가하거나 기존 모델을 개선할 때 일관된 프로세스를 따를 수 있다는 큰 이점이 있습니다. 위의 프로젝트 구조를 살펴보겠습니다.

먼저 src 폴더에는 핵심 코드가 들어갑니다. 데이터 처리, 학습, 서빙, 모니터링이라는 네 가지 핵심 기능을 각각의 모듈로 분리했습니다.

다음으로 pipelines 폴더에는 이 모듈들을 연결하는 파이프라인 정의가 들어갑니다. configs 폴더는 하이퍼파라미터나 경로 설정처럼 자주 바뀌는 값들을 코드와 분리해서 관리합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 이커머스 회사에서 상품 추천 모델을 개발한다고 가정해봅시다.

매일 새로운 구매 데이터가 들어오고, 주기적으로 모델을 재학습해야 합니다. 이때 체계적인 아키텍처가 있으면 데이터 엔지니어, 데이터 사이언티스트, ML 엔지니어가 각자의 영역에서 독립적으로 작업할 수 있습니다.

많은 기업에서 이런 패턴을 적극적으로 사용하고 있습니다. 하지만 주의할 점도 있습니다.

초보자들이 흔히 하는 실수 중 하나는 처음부터 너무 복잡한 구조를 설계하는 것입니다. 마이크로서비스, 쿠버네티스, 실시간 스트리밍까지 한꺼번에 도입하려다 프로젝트가 산으로 갑니다.

따라서 현재 팀의 규모와 필요에 맞게 단순하게 시작하고, 점진적으로 확장하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨와 화이트보드 앞에서 전체 구조를 그려본 김개발 씨는 고개를 끄덕였습니다. "아, 이렇게 큰 그림을 먼저 그리고 시작해야 하는 거군요!" 체계적인 아키텍처 설계는 MLOps의 첫 단추입니다.

이 첫 단추를 잘 끼워야 나머지 작업들이 순조롭게 진행됩니다.

실전 팁

💡 - 처음에는 단순하게 시작하고 필요에 따라 점진적으로 복잡도를 높이세요

  • 모든 설정 값은 코드와 분리하여 config 파일로 관리하세요
  • 각 모듈은 독립적으로 테스트할 수 있도록 설계하세요

2. 학습 파이프라인 구축

아키텍처 설계를 마친 김개발 씨는 이제 본격적으로 학습 파이프라인을 만들기 시작했습니다. 그런데 기존에 작성해둔 학습 코드를 보니 한숨이 나왔습니다.

데이터 로딩, 전처리, 학습, 평가가 하나의 스크립트에 뒤섞여 있었기 때문입니다. "이걸 어떻게 파이프라인으로 만들지?"

학습 파이프라인은 데이터를 입력받아 학습된 모델을 출력하는 자동화된 워크플로우입니다. 마치 자동차 조립 라인처럼 각 단계가 순서대로 실행되며, 한 단계의 출력이 다음 단계의 입력이 됩니다.

이렇게 파이프라인을 구축하면 재현 가능하고 일관된 모델 학습이 가능해집니다.

다음 코드를 살펴봅시다.

# training_pipeline.py
from prefect import flow, task
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import mlflow
import pandas as pd

@task(name="load_data")
def load_data(path: str) -> pd.DataFrame:
    """데이터를 로드하는 태스크"""
    return pd.read_csv(path)

@task(name="preprocess")
def preprocess(df: pd.DataFrame) -> tuple:
    """전처리 및 데이터 분할"""
    X = df.drop('target', axis=1)
    y = df['target']
    return train_test_split(X, y, test_size=0.2)

@task(name="train_model")
def train_model(X_train, y_train, params: dict):
    """모델 학습 태스크"""
    model = RandomForestClassifier(**params)
    model.fit(X_train, y_train)
    return model

@task(name="evaluate")
def evaluate(model, X_test, y_test) -> float:
    """모델 평가 태스크"""
    return model.score(X_test, y_test)

@flow(name="training_pipeline")
def training_pipeline(data_path: str, params: dict):
    """전체 학습 파이프라인을 오케스트레이션"""
    df = load_data(data_path)
    X_train, X_test, y_train, y_test = preprocess(df)
    model = train_model(X_train, y_train, params)
    accuracy = evaluate(model, X_test, y_test)
    return model, accuracy

김개발 씨는 기존 학습 코드를 들여다보며 머리를 긁적였습니다. train.py 파일 하나에 500줄이 넘는 코드가 빼곡히 들어차 있었습니다.

데이터 로딩 코드 사이에 전처리 코드가 섞여 있고, 학습 코드 중간에 평가 코드가 껴 있었습니다. 박시니어 씨가 화면을 보더니 말했습니다.

"이런 코드를 스파게티 코드라고 하지. 파이프라인으로 정리하면 훨씬 깔끔해져." 그렇다면 학습 파이프라인이란 정확히 무엇일까요?

쉽게 비유하자면, 학습 파이프라인은 마치 요리 레시피와 같습니다. 재료 손질하기, 양념 만들기, 볶기, 담기라는 각 단계가 명확하게 정의되어 있고 순서대로 실행됩니다.

누가 만들어도 같은 맛이 나는 것처럼, 파이프라인을 정의해두면 언제 실행해도 같은 결과를 얻을 수 있습니다. 파이프라인 없이 학습할 때는 어떤 문제가 있었을까요?

가장 큰 문제는 재현성이었습니다. "지난주에 만든 모델이 더 좋았는데, 어떻게 만들었더라?" 아무도 기억하지 못했습니다.

또 다른 문제는 협업이었습니다. 다른 사람이 작성한 학습 코드를 이해하려면 몇 시간씩 코드를 읽어야 했습니다.

디버깅도 악몽이었습니다. 어디서 에러가 났는지 찾으려면 전체 코드를 처음부터 끝까지 훑어봐야 했습니다.

바로 이런 문제를 해결하기 위해 파이프라인 오케스트레이션 도구가 등장했습니다. Prefect, Airflow, Kubeflow 같은 도구를 사용하면 각 단계를 독립적인 태스크로 정의할 수 있습니다.

각 태스크는 입력과 출력이 명확하고, 실패하면 해당 태스크만 재실행할 수 있습니다. 무엇보다 전체 워크플로우를 시각적으로 확인할 수 있어서 현재 어느 단계가 실행 중인지 한눈에 파악됩니다.

위의 코드를 자세히 살펴보겠습니다. @task 데코레이터는 함수를 파이프라인의 한 단계로 만듭니다.

load_data, preprocess, train_model, evaluate 각각이 독립적인 태스크입니다. @flow 데코레이터는 이 태스크들을 연결하는 전체 워크플로우를 정의합니다.

training_pipeline 함수 안에서 태스크들이 순서대로 호출되면, Prefect가 자동으로 의존성을 파악하고 올바른 순서로 실행해줍니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 금융회사에서 사기 탐지 모델을 운영한다고 가정해봅시다. 매일 밤 새로운 거래 데이터로 모델을 재학습해야 합니다.

파이프라인을 구축해두면 스케줄러가 자동으로 학습을 시작하고, 문제가 생기면 담당자에게 알림을 보냅니다. 사람이 매일 밤을 새울 필요가 없습니다.

하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수 중 하나는 태스크를 너무 잘게 쪼개는 것입니다.

한 줄짜리 코드까지 태스크로 만들면 오히려 복잡해지고 성능도 떨어집니다. 따라서 논리적으로 의미 있는 단위로 태스크를 구성해야 합니다.

일반적으로 데이터 로딩, 전처리, 학습, 평가 정도로 나누는 것이 적당합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

스파게티 코드를 깔끔한 파이프라인으로 정리한 김개발 씨는 뿌듯한 표정을 지었습니다. "이제 어디서 뭘 하는지 한눈에 보이네요!" 학습 파이프라인은 MLOps의 심장과 같습니다.

이 심장이 규칙적으로 뛰어야 전체 시스템이 건강하게 돌아갑니다.

실전 팁

💡 - 태스크는 하나의 명확한 책임만 갖도록 설계하세요

  • 각 태스크의 입출력 타입을 명시하면 디버깅이 쉬워집니다
  • 실패한 태스크만 재실행할 수 있도록 멱등성을 고려하세요

3. 모델 레지스트리 연동

학습 파이프라인을 완성한 김개발 씨는 새로운 고민에 빠졌습니다. 파이프라인을 여러 번 돌리다 보니 모델 파일이 수십 개가 쌓였습니다.

model_v1.pkl, model_final.pkl, model_final_final.pkl... "이 중에 어떤 게 가장 성능이 좋은 모델이지?"

모델 레지스트리는 학습된 모델의 버전 관리 시스템입니다. 마치 깃허브가 코드의 버전을 관리하듯이, 모델 레지스트리는 모델 파일과 함께 학습에 사용된 파라미터, 성능 메트릭, 메타데이터를 함께 저장합니다.

이를 통해 어떤 모델이 언제, 어떻게 만들어졌는지 추적할 수 있습니다.

다음 코드를 살펴봅시다.

# model_registry.py
import mlflow
from mlflow.tracking import MlflowClient

# MLflow 서버 연결
mlflow.set_tracking_uri("http://mlflow-server:5000")
mlflow.set_experiment("fraud_detection")

def train_and_log_model(X_train, y_train, params: dict):
    """모델을 학습하고 MLflow에 기록"""
    with mlflow.start_run(run_name="rf_training"):
        # 하이퍼파라미터 로깅
        mlflow.log_params(params)

        # 모델 학습
        model = RandomForestClassifier(**params)
        model.fit(X_train, y_train)

        # 메트릭 로깅
        accuracy = model.score(X_test, y_test)
        mlflow.log_metric("accuracy", accuracy)

        # 모델 저장 및 등록
        mlflow.sklearn.log_model(
            model,
            "model",
            registered_model_name="fraud_detector"
        )
        return model

def promote_model_to_production(model_name: str, version: int):
    """검증된 모델을 프로덕션으로 승격"""
    client = MlflowClient()
    client.transition_model_version_stage(
        name=model_name,
        version=version,
        stage="Production"
    )

김개발 씨의 프로젝트 폴더를 열어보면 한숨이 절로 나옵니다. models 폴더 안에는 이름도 제각각인 모델 파일들이 잔뜩 쌓여 있었습니다.

model_0601.pkl, model_test.pkl, model_best.pkl, model_best_2.pkl... 도대체 어떤 모델이 어떤 데이터로 학습되었는지, 성능은 얼마인지 알 수가 없었습니다.

박시니어 씨가 폴더를 보더니 웃음을 터뜨렸습니다. "우리 팀도 예전에 이랬어.

그러다가 MLflow 도입하고 세상이 바뀌었지." 그렇다면 모델 레지스트리란 정확히 무엇일까요? 쉽게 비유하자면, 모델 레지스트리는 마치 도서관의 서지 시스템과 같습니다.

책을 제목으로만 찾는 게 아니라, 저자, 출판년도, 분류번호, 대출 이력까지 모두 기록되어 있는 것입니다. 모델 레지스트리도 마찬가지로 모델 파일뿐 아니라 학습 날짜, 하이퍼파라미터, 성능 메트릭, 심지어 학습에 사용된 데이터셋 정보까지 모두 기록합니다.

모델 레지스트리가 없을 때는 어떤 문제가 있었을까요? "지난달에 만든 모델이 더 좋았는데, 어떻게 만들었더라?" 이런 질문에 아무도 답하지 못했습니다.

더 심각한 문제는 배포 시점에 터졌습니다. 운영팀에서 "현재 서비스 중인 모델이 뭐예요?"라고 물으면 개발팀에서 "음...

아마 이거일 거예요?"라고 대답하는 상황이 벌어졌습니다. 모델에 문제가 생겨서 롤백해야 할 때는 더 큰 혼란이 일어났습니다.

바로 이런 문제를 해결하기 위해 MLflow같은 모델 레지스트리가 등장했습니다. MLflow를 사용하면 모든 실험을 자동으로 기록할 수 있습니다.

어떤 파라미터로 학습했는지, 어떤 성능이 나왔는지, 모델 파일은 어디에 저장되었는지 모두 추적됩니다. 무엇보다 모델의 생애주기를 관리할 수 있습니다.

"Staging"에서 테스트를 거쳐 "Production"으로 승격하는 과정을 체계적으로 관리할 수 있습니다. 위의 코드를 자세히 살펴보겠습니다.

mlflow.start_run 컨텍스트 안에서 일어나는 모든 것이 하나의 실험으로 기록됩니다. log_params는 하이퍼파라미터를, log_metric은 성능 지표를 기록합니다.

log_model은 모델 파일 자체를 저장하는데, registered_model_name을 지정하면 모델 레지스트리에 등록됩니다. promote_model_to_production 함수는 테스트를 통과한 모델을 프로덕션 환경으로 승격시킵니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 추천 시스템을 운영하는 회사라고 가정해봅시다.

매주 새로운 모델을 학습하고, 그중 가장 성능이 좋은 모델을 서비스에 배포합니다. MLflow UI에서 지난 몇 주간의 모든 실험을 비교할 수 있고, 특정 메트릭 기준으로 정렬해서 최고 성능 모델을 찾을 수 있습니다.

문제가 생기면 이전 버전으로 즉시 롤백할 수도 있습니다. 하지만 주의할 점도 있습니다.

초보자들이 흔히 하는 실수 중 하나는 모든 것을 로깅하려고 하는 것입니다. 학습 중간 결과, 모든 에폭의 메트릭, 디버깅용 이미지까지 저장하면 스토리지가 금방 차버립니다.

따라서 정말 필요한 정보만 선별해서 기록하는 것이 좋습니다. 보통 최종 메트릭, 핵심 하이퍼파라미터, 모델 파일 정도면 충분합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. MLflow를 도입한 후 김개발 씨는 웹 UI에서 지난 모든 실험을 한눈에 볼 수 있게 되었습니다.

"와, 이제 어떤 모델이 최고인지 바로 알 수 있네요!" 모델 레지스트리는 MLOps의 기억 장치입니다. 이 기억이 있어야 과거의 실수를 반복하지 않고, 미래의 성공을 재현할 수 있습니다.

실전 팁

💡 - 모든 실험에 의미 있는 이름을 붙여서 나중에 찾기 쉽게 하세요

  • 모델 승격은 자동화된 테스트를 통과한 후에만 수행하세요
  • 스토리지 비용을 고려해서 오래된 실험은 주기적으로 정리하세요

4. 자동 배포 파이프라인

김개발 씨는 드디어 만족스러운 모델을 만들었습니다. 정확도 95%!

이제 이걸 서비스에 올려야 하는데... "배포는 어떻게 하지?" 예전에는 모델 파일을 USB에 담아서 운영팀에 전달했다는 우스갯소리가 떠올랐습니다.

분명 더 나은 방법이 있을 것입니다.

자동 배포 파이프라인은 검증된 모델을 프로덕션 환경에 자동으로 배포하는 시스템입니다. 마치 택배 시스템처럼 출발지에서 목적지까지 사람의 개입 없이 물건이 이동하는 것과 같습니다.

CI/CD 개념을 ML에 적용한 것으로, 모델이 테스트를 통과하면 자동으로 서비스 환경에 배포됩니다.

다음 코드를 살펴봅시다.

# deploy_pipeline.py
from fastapi import FastAPI
import mlflow
from kubernetes import client, config

# FastAPI 서빙 앱
app = FastAPI()

# 프로덕션 모델 로드
model = mlflow.sklearn.load_model(
    "models:/fraud_detector/Production"
)

@app.post("/predict")
async def predict(features: dict):
    """예측 API 엔드포인트"""
    prediction = model.predict([list(features.values())])
    return {"prediction": int(prediction[0])}

# Kubernetes 배포 설정
def deploy_to_kubernetes(image_tag: str):
    """쿠버네티스에 새 버전 배포"""
    config.load_kube_config()
    apps_v1 = client.AppsV1Api()

    # 롤링 업데이트로 무중단 배포
    deployment = apps_v1.read_namespaced_deployment(
        name="model-serving",
        namespace="ml-production"
    )
    deployment.spec.template.spec.containers[0].image = image_tag

    apps_v1.patch_namespaced_deployment(
        name="model-serving",
        namespace="ml-production",
        body=deployment
    )

김개발 씨는 모델을 완성하고 나서 뿌듯해하고 있었습니다. 그런데 팀장님이 다가와 말했습니다.

"좋아요, 그럼 내일 오전까지 배포 부탁해요." 김개발 씨의 표정이 굳어졌습니다. 배포라니, 그건 운영팀에서 하는 거 아니었나요?

박시니어 씨가 낮은 목소리로 말했습니다. "우리 팀은 DevOps도 같이 하거든.

걱정 마, 자동 배포 파이프라인 있으면 버튼 하나로 끝나." 그렇다면 자동 배포 파이프라인이란 정확히 무엇일까요? 쉽게 비유하자면, 자동 배포 파이프라인은 마치 공항의 수하물 처리 시스템과 같습니다.

짐을 맡기면 자동으로 스캔되고, 분류되고, 올바른 비행기에 실립니다. 사람이 일일이 들고 다닐 필요가 없습니다.

배포 파이프라인도 마찬가지로 모델이 테스트를 통과하면 자동으로 컨테이너화되고, 스테이징 환경에서 검증받고, 프로덕션에 배포됩니다. 수동 배포 시절에는 어떤 문제가 있었을까요?

배포할 때마다 체크리스트를 따라 수십 개의 명령어를 입력해야 했습니다. 한 번 실수하면 서비스가 중단되었습니다.

더 큰 문제는 배포에 대한 두려움이었습니다. "금요일 오후에는 절대 배포하지 말 것"이라는 불문율이 있을 정도였습니다.

배포가 무서우니 변경사항을 모아두었다가 한꺼번에 배포했고, 그러다 보니 문제가 생겨도 원인을 찾기 어려웠습니다. 바로 이런 문제를 해결하기 위해 CI/CD 기반의 자동 배포가 등장했습니다.

**CI(지속적 통합)**는 코드가 변경될 때마다 자동으로 테스트를 실행합니다. **CD(지속적 배포)**는 테스트를 통과한 코드를 자동으로 프로덕션에 배포합니다.

ML에서는 여기에 모델 검증 단계가 추가됩니다. 새 모델의 성능이 기존 모델보다 좋아야만 배포가 진행됩니다.

위의 코드를 자세히 살펴보겠습니다. 먼저 FastAPI로 모델 서빙 앱을 만듭니다.

MLflow에서 "Production" 스테이지의 모델을 로드하고, /predict 엔드포인트로 예측 요청을 받습니다. deploy_to_kubernetes 함수는 새 버전의 컨테이너 이미지를 쿠버네티스 클러스터에 배포합니다.

롤링 업데이트 방식을 사용하면 구버전 파드가 하나씩 새버전으로 교체되어 서비스 중단 없이 배포가 가능합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 실시간 번역 서비스를 운영한다고 가정해봅시다. 새로운 언어 모델이 학습되면 GitHub에 PR이 올라갑니다.

CI가 자동으로 테스트를 실행하고, 성능 벤치마크를 측정합니다. 기존 모델보다 BLEU 점수가 높으면 자동으로 스테이징 환경에 배포됩니다.

QA팀이 최종 검증을 마치면 프로덕션에 한 번의 클릭으로 배포됩니다. 하지만 주의할 점도 있습니다.

초보자들이 흔히 하는 실수 중 하나는 롤백 계획 없이 배포하는 것입니다. 아무리 테스트를 잘해도 프로덕션에서만 나타나는 문제가 있습니다.

따라서 항상 이전 버전으로 즉시 롤백할 수 있는 체계를 갖추어야 합니다. 쿠버네티스의 경우 kubectl rollout undo 명령 하나로 롤백이 가능합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 자동 배포 파이프라인을 구축한 후, 김개발 씨는 더 이상 배포가 두렵지 않게 되었습니다.

"이제 금요일 오후에도 배포할 수 있겠네요!" 자동 배포 파이프라인은 MLOps의 고속도로입니다. 이 고속도로가 잘 닦여 있어야 모델이 빠르고 안전하게 사용자에게 도달합니다.

실전 팁

💡 - 카나리 배포를 도입해서 일부 트래픽으로 먼저 테스트하세요

  • 배포 후 핵심 메트릭을 자동으로 모니터링하고 이상 시 자동 롤백을 고려하세요
  • 배포 이력을 기록해서 문제 발생 시 빠르게 원인을 추적하세요

5. 모니터링 대시보드

배포까지 성공적으로 마친 김개발 씨는 이제 좀 쉴 수 있을 줄 알았습니다. 그런데 일주일 후, 고객센터에서 연락이 왔습니다.

"요즘 추천이 이상해요. 전혀 관련 없는 상품만 추천해요." 모델 성능이 떨어진 건가요?

언제부터 그랬는지 알 수가 없었습니다.

모니터링 대시보드는 배포된 모델의 상태를 실시간으로 감시하는 시스템입니다. 마치 자동차의 계기판처럼 속도, 연료, 엔진 상태를 한눈에 보여주는 것과 같습니다.

모델의 예측 성능, 응답 시간, 입력 데이터 분포 변화를 추적해서 문제가 생기기 전에 미리 알려줍니다.

다음 코드를 살펴봅시다.

# monitoring.py
from prometheus_client import Counter, Histogram, Gauge
import time
from evidently import ColumnMapping
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset

# Prometheus 메트릭 정의
PREDICTION_COUNT = Counter(
    'model_predictions_total',
    'Total number of predictions',
    ['model_version', 'prediction_class']
)
PREDICTION_LATENCY = Histogram(
    'model_prediction_latency_seconds',
    'Prediction latency in seconds'
)
MODEL_ACCURACY = Gauge(
    'model_accuracy',
    'Current model accuracy'
)

def predict_with_monitoring(model, features):
    """모니터링이 포함된 예측 함수"""
    start_time = time.time()

    # 예측 수행
    prediction = model.predict([features])[0]

    # 메트릭 기록
    latency = time.time() - start_time
    PREDICTION_LATENCY.observe(latency)
    PREDICTION_COUNT.labels(
        model_version="v1.2",
        prediction_class=str(prediction)
    ).inc()

    return prediction

def check_data_drift(reference_data, current_data):
    """데이터 드리프트 감지"""
    report = Report(metrics=[DataDriftPreset()])
    report.run(
        reference_data=reference_data,
        current_data=current_data
    )
    return report.as_dict()

김개발 씨는 난감한 상황에 빠졌습니다. 모델이 이상하다는 연락을 받았는데, 언제부터 문제가 생겼는지 알 수가 없었습니다.

서버 로그를 뒤져봐도 에러는 없었습니다. 모델은 분명히 예측을 내놓고 있었으니까요.

다만 그 예측이 엉뚱했을 뿐입니다. 박시니어 씨가 한숨을 쉬며 말했습니다.

"모니터링 없이 모델을 운영하는 건 눈 감고 운전하는 거랑 같아. 지금이라도 대시보드를 만들어야 해." 그렇다면 모니터링 대시보드란 정확히 무엇일까요?

쉽게 비유하자면, 모니터링 대시보드는 마치 병원의 환자 모니터와 같습니다. 심박수, 혈압, 산소포화도가 실시간으로 표시되고, 이상이 생기면 경보음이 울립니다.

MLOps 모니터링도 마찬가지로 모델의 응답 시간, 예측 분포, 에러율을 실시간으로 보여주고, 이상 징후가 감지되면 알림을 보냅니다. 모니터링 없이 운영할 때는 어떤 문제가 있었을까요?

가장 큰 문제는 모델 드리프트를 감지하지 못하는 것이었습니다. 모델은 학습 당시의 데이터 분포에 맞춰져 있는데, 시간이 지나면서 실제 데이터의 패턴이 변합니다.

코로나 때 사람들의 쇼핑 패턴이 완전히 바뀐 것처럼요. 모니터링이 없으면 이런 변화를 알아채지 못하고, 고객 불만이 쌓여서야 문제를 인지하게 됩니다.

바로 이런 문제를 해결하기 위해 ML 전용 모니터링 도구들이 등장했습니다. PrometheusGrafana는 인프라 메트릭을 수집하고 시각화합니다.

응답 시간, 처리량, 에러율 같은 기본적인 서비스 지표를 추적합니다. EvidentlyWhyLabs 같은 도구는 ML 특화 모니터링을 제공합니다.

입력 데이터의 분포 변화, 예측값의 변화, 피처 드리프트를 감지해줍니다. 위의 코드를 자세히 살펴보겠습니다.

Counter, Histogram, Gauge는 Prometheus의 세 가지 메트릭 타입입니다. Counter는 누적 값을 세고, Histogram은 분포를 측정하고, Gauge는 현재 값을 나타냅니다.

predict_with_monitoring 함수는 예측을 수행하면서 동시에 지연 시간과 예측 횟수를 기록합니다. check_data_drift 함수는 기준 데이터와 현재 데이터를 비교해서 분포 변화를 감지합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 대출 심사 모델을 운영한다고 가정해봅시다.

평소에는 승인율이 60% 정도인데, 어느 날 갑자기 90%로 뛰었습니다. 모니터링 대시보드가 이상 징후를 감지하고 알림을 보냅니다.

조사해보니 신규 제휴사에서 보내는 데이터 형식이 달라서 모델이 오작동하고 있었습니다. 모니터링이 없었다면 수억 원의 손실이 발생했을 수도 있습니다.

하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수 중 하나는 알림을 너무 민감하게 설정하는 것입니다.

조금만 변해도 알림이 오면 "양치기 소년" 효과가 생깁니다. 결국 중요한 알림도 무시하게 됩니다.

따라서 정말 중요한 지표에만 알림을 설정하고, 임계값은 충분히 여유 있게 잡아야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

모니터링 대시보드를 구축한 후, 김개발 씨는 매일 아침 커피를 마시며 대시보드를 확인하는 습관이 생겼습니다. "이제 문제가 생기면 바로 알 수 있어서 마음이 편해요!" 모니터링 대시보드는 MLOps의 눈과 귀입니다.

이 눈과 귀가 있어야 모델이 제대로 작동하는지 확인하고, 문제가 생겼을 때 빠르게 대응할 수 있습니다.

실전 팁

💡 - 비즈니스 메트릭과 기술 메트릭을 함께 모니터링하세요

  • 알림 피로를 줄이기 위해 심각도에 따라 알림 채널을 분리하세요
  • 정기적으로 모니터링 지표를 검토하고 불필요한 것은 정리하세요

6. 문서화와 운영 가이드

프로젝트가 어느 정도 마무리되어 갈 무렵, 박시니어 씨가 김개발 씨를 불렀습니다. "다음 주부터 새로운 프로젝트 투입되는데, 이 시스템 인수인계 문서 좀 작성해줘." 김개발 씨는 막막했습니다.

코드는 다 작성했는데, 이걸 어떻게 설명하지?

문서화와 운영 가이드는 시스템을 유지보수하고 운영하는 데 필요한 모든 정보를 정리한 것입니다. 마치 가전제품의 사용설명서처럼 설치 방법, 사용 방법, 문제 해결 방법을 담고 있습니다.

좋은 문서가 있으면 담당자가 바뀌어도 시스템이 안정적으로 운영될 수 있습니다.

다음 코드를 살펴봅시다.

# README.md 템플릿
"""
# MLOps 파이프라인 운영 가이드

## 시스템 아키텍처
- 학습 파이프라인: Prefect + MLflow
- 서빙: FastAPI + Kubernetes
- 모니터링: Prometheus + Grafana

## 빠른 시작
# 환경 설정
pip install -r requirements.txt

# 학습 파이프라인 실행
python -m pipelines.training_pipeline

# 로컬 서빙 테스트
uvicorn src.serving.api:app --reload

## 장애 대응 런북

### 모델 성능 저하 시

3. 필요시 HPA 설정 변경

김개발 씨는 빈 문서 파일을 앞에 두고 멍하니 앉아 있었습니다. 지난 몇 달간 열심히 만든 시스템인데, 막상 설명하려니 어디서부터 시작해야 할지 모르겠습니다.

코드를 보면 다 아는 내용인데, 글로 쓰려니 왜 이렇게 어려운 걸까요? 박시니어 씨가 옆에 앉으며 말했습니다.

"문서화가 어려운 건 당연해. 하지만 이게 없으면 네가 떠난 후에 이 시스템은 블랙박스가 되어버려." 그렇다면 문서화와 운영 가이드란 정확히 무엇일까요?

쉽게 비유하자면, 문서화는 마치 요리 레시피북과 같습니다. 아무리 맛있는 음식이라도 레시피가 없으면 요리사가 바뀌면 그 맛을 재현할 수 없습니다.

문서화가 있으면 시스템을 처음 보는 사람도 어떻게 동작하는지 이해하고, 문제가 생겼을 때 어떻게 해결해야 하는지 알 수 있습니다. 문서화가 없을 때는 어떤 문제가 있었을까요?

담당자가 휴가를 가거나 퇴사하면 시스템이 사실상 멈추었습니다. 아무도 어떻게 배포하는지, 문제가 생기면 어디를 봐야 하는지 몰랐습니다.

새로운 팀원이 오면 며칠씩 옆에 붙어서 설명해야 했습니다. 그것도 매번 같은 내용을 반복해서요.

바로 이런 문제를 해결하기 위해 체계적인 문서화 문화가 중요해졌습니다. README는 프로젝트의 첫인상입니다.

이 프로젝트가 무엇인지, 어떻게 시작하는지를 담습니다. **런북(Runbook)**은 장애 대응 매뉴얼입니다.

특정 상황에서 어떤 조치를 취해야 하는지 단계별로 안내합니다. **모델 카드(Model Card)**는 모델의 성능, 학습 데이터, 제한사항을 담은 신분증 같은 문서입니다.

위의 코드를 자세히 살펴보겠습니다. README 템플릿은 시스템 아키텍처 개요, 빠른 시작 가이드, 장애 대응 런북, 연락처를 포함합니다.

이 정도만 있어도 새로운 팀원이 시스템을 파악하고 기본적인 문제를 해결할 수 있습니다. generate_model_card 함수는 모델 카드를 자동으로 생성합니다.

학습할 때마다 자동으로 문서가 업데이트되니 문서가 코드와 따로 노는 일이 없습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 대기업의 ML 플랫폼팀이라고 가정해봅시다. 수십 개의 모델이 운영되고, 담당자도 수시로 바뀝니다.

체계적인 문서화가 없으면 혼란이 생깁니다. 반면 모든 모델에 표준화된 모델 카드가 있고, 장애 상황별 런북이 정비되어 있으면, 새벽 3시에 장애가 나도 온콜 담당자가 침착하게 대응할 수 있습니다.

하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수 중 하나는 문서를 한 번 작성하고 방치하는 것입니다.

코드는 계속 바뀌는데 문서는 그대로면, 오히려 혼란을 야기합니다. 틀린 문서는 없는 것보다 나쁩니다.

따라서 문서 업데이트를 코드 리뷰 프로세스에 포함시키는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

체계적인 문서를 작성한 김개발 씨는 뿌듯한 마음으로 새 프로젝트로 떠났습니다. 며칠 후 후임자에게서 메시지가 왔습니다.

"문서 덕분에 금방 파악했어요. 감사합니다!" 문서화는 MLOps의 유산입니다.

이 유산이 있어야 시스템이 사람보다 오래 살아남고, 팀의 지식이 축적됩니다.

실전 팁

💡 - 문서는 코드와 함께 버전 관리하세요

  • 런북은 실제 장애 경험을 바탕으로 계속 업데이트하세요
  • 자동 생성할 수 있는 문서는 최대한 자동화하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#MLOps#Pipeline#MLflow#Kubernetes#Monitoring#Project,MLOps,Pipeline

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.