본문으로 건너뛰기
← Systems Notebook / Ops
MLOps Notes12 / 15

MLOps 플랫폼을 역할로 이해하기: MLflow, Ray, KubeRay, KServe, Knative

MLflow·Ray·KubeRay·KServe·Knative가 각각 어떤 문제를 풀고, 학습부터 모델 서빙까지 어떻게 연결되는지 설명합니다.

이 글의 배경

HPE 엔지니어의 작업 자료를 바탕으로 정리한 글입니다. 개인 실험 기록이나 HPE 공식 문서와는 구분합니다. 본문의 환경과 버전은 글 작성 당시를 기준으로 합니다.

MLOps를 처음 접하면 제품 이름부터 외우기 쉽습니다. MLflow도 모델을 다루고, Ray도 모델을 다루며, KServe도 모델을 다룹니다. 그래서 “셋 중 하나만 고르면 되는가?”라는 질문이 생깁니다.

각 도구가 담당하는 일을 나누어 보면 이 질문을 정리할 수 있습니다. MLflow는 실험과 모델의 기록, Ray는 계산의 실행, KubeRay는 Kubernetes에서 Ray를 운영하는 방법, KServe는 모델 API의 배포, Knative는 필요할 때 0개까지 줄어드는 요청 기반 실행 환경을 담당합니다. 학습에서 배포까지 흐름을 따라가면 각 도구가 필요한 위치도 분명해집니다.

이 글에서는 먼저 학습이 실행되고 그 결과가 기록된 뒤 API로 배포되는 과정을 살펴봅니다. 각 도구가 맡는 경계를 확인해, 다음 글과 최종 실습에서 설정을 연결할 기준을 세웁니다.

이 글에서 다루는 것

  • MLflow, Ray/KubeRay, KServe, Knative의 역할과 경계
  • 학습 코드가 이미지가 되고, RayJob으로 실행되고, MLflow에 기록되는 과정
  • KServe Standard 모드와 Knative 모드의 차이
  • 이 실습에서 Airflow와 Kubeflow를 핵심 경로에서 제외한 이유
  • 플랫폼이 정상인지 확인하는 최소 점검 명령과 장애 위치 찾기

전체 구조를 한 장으로 보기

단계별 흐름 / 01학습 실행과 모델 서빙의 연결
개발자학습 코드
GitLab CI검증·이미지 빌드
Harbor학습 이미지
01 / 06
코드를 학습 이미지로

GitLab CI가 학습 코드를 검증하고 만든 이미지를 Harbor에 저장합니다.

1

개발자 → GitLab CI코드 검증

2

GitLab CI → Harbor학습 이미지 저장

단계를 선택하면 자동 재생이 멈춥니다. 선의 번호와 아래 설명을 함께 읽어 주세요.

전체 단계 한눈에 읽기
  1. 코드를 학습 이미지로

    GitLab CI가 학습 코드를 검증하고 만든 이미지를 Harbor에 저장합니다.

    • 개발자 → GitLab CI: 코드 검증
    • GitLab CI → Harbor: 학습 이미지 저장
  2. 학습 선언을 Git에 기록

    CI가 Chart Repo에 원하는 상태를 기록하면 Argo CD가 그 선언을 읽습니다.

    • GitLab CI → GitLab Chart Repo: 원하는 상태 기록
    • GitLab Chart Repo → Argo CD: 학습 선언 읽기
  3. RayJob으로 계산 실행

    Argo CD가 RayJob 선언을 동기화하고, KubeRay / Ray가 학습 계산을 실행합니다.

    • Argo CD → KubeRay RayJob: RayJob 동기화
    • KubeRay RayJob → Ray: 학습 계산
  4. 실험 기록과 모델 파일

    MLflow는 파라미터·메트릭·모델 정보를 기록하고 모델 아티팩트는 S3/MinIO에 보관합니다.

    • Ray → MLflow: 실험·모델 기록
    • MLflow → S3 / MinIO: 모델 아티팩트
  5. 서빙 선언과 모델 연결

    KServe에는 Git의 서빙 선언과 저장소의 모델 아티팩트가 함께 필요합니다. 학습 성공만으로 모든 모델을 곧바로 운영에 올린다는 뜻은 아닙니다.

    • GitLab Chart Repo → Argo CD: 서빙 선언 읽기
    • Argo CD → KServe: InferenceService 동기화
    • S3 / MinIO → KServe: 모델 아티팩트 로드
  6. Knative는 선택 사항

    Knative 모드에서는 scale-to-zero를 사용할 수 있습니다. 모든 KServe 배포에 필수인 다음 단계는 아닙니다.

    • KServe → Knative Serving: 선택한 서빙 모드
도식 원문
flowchart LR
  DEV["개발자\n학습 코드"] --> CI["GitLab CI\n검증·이미지 빌드"]
  CI --> H["Harbor\n학습 이미지"]
  CI --> G["GitLab Chart Repo\n원하는 상태"]
  G --> A["Argo CD\n동기화"]
  A --> RJ["KubeRay RayJob\n학습 실행"]
  RJ --> R["Ray\n분산 계산"]
  R --> M["MLflow\n파라미터·메트릭·모델"]
  M --> O["S3/MinIO\n모델 아티팩트"]
  G --> A2["Argo CD\n서빙 선언 동기화"]
  A2 --> KS["KServe InferenceService"]
  O --> KS
  KS --> KN["Knative Serving\n선택 사항: scale-to-zero"]

여기에는 성격이 다른 두 흐름이 있습니다.

  1. 소프트웨어 전달 흐름: 코드 → 이미지 → GitOps 저장소 → Kubernetes
  2. 모델 수명주기 흐름: 데이터 → 학습 → 평가 → 기록 → 승인 → 서빙

GitLab과 Argo CD가 첫 번째 흐름의 골격이고, Ray·MLflow·KServe가 두 번째 흐름을 실행합니다. 두 흐름은 이미지 식별자, Git 커밋, MLflow run ID, 모델 아티팩트 URI로 연결됩니다.

이 글의 lab.example.com, student-01, 192.0.2.0/24는 공개 문서용 치환값입니다. 독자는 실제 환경 값으로 바꾸되, 게시할 원고에는 실제 도메인·IP·계정·토큰을 남기지 않습니다.

MLflow: “어떤 실험에서 이 모델이 나왔는가”를 기록한다

팀이 학습 결과를 다시 만들고 운영에 사용하려면, 모델 파일과 그 파일을 만든 실험 조건을 함께 남겨야 합니다. 다음 항목이 그 연결을 확인하는 기준입니다.

  • 학습에 사용한 코드와 이미지
  • 데이터 분할과 하이퍼파라미터
  • 정확도 같은 평가 지표
  • 실제 모델 파일의 저장 위치
  • 검증을 통과해 현재 배포 대상으로 승인된 버전

MLflow Tracking은 실험과 run을 만들고 파라미터, 메트릭, 태그, 모델 아티팩트를 한데 묶습니다. Model Registry는 등록 모델과 모델 버전, 별칭(alias), 태그를 관리합니다.

python
import os

import mlflow
import mlflow.sklearn
from mlflow.models import infer_signature
from sklearn.datasets import load_digits
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

x, y = load_digits(return_X_y=True)
x_train, x_test, y_train, y_test = train_test_split(
    x, y, test_size=0.2, random_state=42, stratify=y
)
model = LogisticRegression(max_iter=500, random_state=42).fit(x_train, y_train)
accuracy = model.score(x_test, y_test)

mlflow.set_tracking_uri(os.environ["MLFLOW_TRACKING_URI"])
mlflow.set_experiment("digits-classification")

with mlflow.start_run() as run:
    mlflow.log_params({"solver": "lbfgs", "random_state": 42})
    mlflow.log_metric("accuracy", accuracy)
    mlflow.set_tags(
        {
            "git.commit": os.environ["GIT_COMMIT_SHA"],
            "container.image": os.environ["TRAINING_IMAGE"],
            "dataset.version": os.environ["DATASET_VERSION"],
        }
    )
    mlflow.sklearn.log_model(
        model,
        artifact_path="model",
        signature=infer_signature(x_train, model.predict(x_train)),
        input_example=x_test[:2],
        registered_model_name="digits-classifier",
    )
    print(run.info.run_id)

MLflow 서버의 저장소도 두 종류로 나눠 이해해야 합니다.

저장 대상 예시 권장 저장소
run, 파라미터, 메트릭, 모델 버전 같은 메타데이터 accuracy=0.97, run ID PostgreSQL 같은 DB
모델, 그래프, 보고서 같은 큰 파일 MLmodel, model.pkl S3 호환 오브젝트 스토리지

개인 실습에서는 SQLite로 시작할 수 있지만, 여러 사용자가 공유하고 Model Registry를 운영하는 환경은 DB 백엔드와 내구성 있는 아티팩트 저장소를 분리하는 편이 안전합니다. 계산 자원을 스케줄링하는 역할은 별도로 필요합니다. 학습을 실행하는 쪽은 Ray이고, MLflow는 실행 결과를 기록하는 쪽입니다.

예전 글에서 흔히 보이는 Production stage 대신 최신 설계에서는 champion, challenger 같은 모델 별칭과 검증 상태 태그를 쓰는 것이 좋습니다. MLflow의 고정 stage는 2.9부터 deprecated 상태입니다.

Ray와 KubeRay: “이 학습을 어디에서 몇 개의 프로세스로 돌릴 것인가”

Ray는 Python 작업을 여러 프로세스와 노드로 확장하는 실행 프레임워크입니다. Ray Core 위에 데이터 처리, 학습, 튜닝, 서빙 라이브러리가 올라갑니다. 다음 코드는 함수 하나를 원격 task로 실행하는 가장 작은 예입니다.

python
import ray

ray.init(address="auto")

@ray.remote(num_cpus=1)
def train_one(seed: int) -> dict:
    return {"seed": seed, "accuracy": 0.95 + seed / 1000}

results = ray.get([train_one.remote(seed) for seed in range(3)])
print(results)

Kubernetes에서 Ray의 클러스터와 작업을 관리할 때는 KubeRay Operator를 사용합니다. KubeRay가 제공하는 주요 사용자 정의 리소스는 다음과 같습니다.

리소스 목적 이 시리즈의 사용처
RayCluster 수명이 긴 Ray 클러스터 공유형 대화식 환경
RayJob 클러스터 생성, 작업 제출, 종료를 하나로 관리 배치 학습
RayService Ray Serve 애플리케이션과 클러스터를 운영 복합 Python 추론 그래프가 필요할 때

최종 실습에서는 RayJob을 사용합니다. Git의 YAML이 학습 실행 자체를 선언할 수 있고, 성공 후 클러스터를 정리할 수 있기 때문입니다.

yaml
apiVersion: ray.io/v1
kind: RayJob
metadata:
  name: digits-train-a1b2c3d4
  namespace: ml-training
spec:
  entrypoint: "python -m src.train"
  shutdownAfterJobFinishes: true
  ttlSecondsAfterFinished: 300
  rayClusterSpec:
    rayVersion: "2.47.1" # 설치 환경에서 검증한 버전으로 고정한다.
    headGroupSpec:
      rayStartParams: {}
      template:
        spec:
          containers:
            - name: ray-head
              image: harbor.lab.example.com/mlops/digits-trainer:a1b2c3d4
              resources:
                requests:
                  cpu: "1"
                  memory: 2Gi
    workerGroupSpecs:
      - groupName: workers
        replicas: 1
        minReplicas: 1
        maxReplicas: 2
        rayStartParams: {}
        template:
          spec:
            containers:
              - name: ray-worker
                image: harbor.lab.example.com/mlops/digits-trainer:a1b2c3d4
                resources:
                  requests:
                    cpu: "1"
                    memory: 2Gi

이 YAML의 rayVersion과 컨테이너에 설치된 Ray 버전은 호환되도록 맞춰야 합니다. 배포 전에는 현재 KubeRay 호환표를 확인하고, 팀이 검증한 이미지 버전과 함께 고정합니다.

KServe: “모델을 어떤 API로 배포하고 운영할 것인가”

MLflow에 등록한 모델을 HTTP 예측 API로 제공할 때는 서빙 구성이 필요합니다. KServe는 Kubernetes에 InferenceService라는 선언형 API를 추가하고, 모델 아티팩트 다운로드·ServingRuntime 선택·Service 생성·오토스케일링을 조율합니다.

yaml
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: digits-classifier
  namespace: ml-serving
spec:
  predictor:
    minReplicas: 1
    serviceAccountName: kserve-model-reader
    model:
      modelFormat:
        name: mlflow
      runtime: kserve-mlserver
      protocolVersion: v2
      storageUri: s3://ml-models/digits/a1b2c3d4/model

storageUri에 latest 같은 가변 위치를 쓰면, Git의 선언이 같아도 가리키는 모델이 바뀔 수 있습니다. Git에 기록된 URI가 정확히 어느 모델 아티팩트를 가리키는지 고정되어야 롤백과 감사가 가능합니다.

KServe의 ServingRuntime은 “아티팩트 형식과 실제 서버 구현”을 연결합니다. 예를 들어 kserve-mlserver는 MLflow 형식을 지원하지만, 해당 runtime이 클러스터에 설치되어 있고 사용하려는 모델 flavor·프로토콜과 맞는지 먼저 확인해야 합니다.

bash
kubectl get clusterservingruntime
kubectl get clusterservingruntime kserve-mlserver -o yaml

Knative: KServe의 필수 동의어가 아니라 선택 가능한 실행 모드

KServe에는 크게 두 배포 방식이 있습니다.

구분 Standard 모드 Knative 모드
생성되는 워크로드 Deployment, Service, HPA Knative Service와 revision
scale-to-zero 기본 HPA만으로는 불가 가능
콜드 스타트 없음 0에서 시작할 때 발생
운영 복잡도 상대적으로 낮음 Knative 네트워킹·autoscaler 운영 필요
잘 맞는 대상 상시 트래픽, 예측 가능한 지연 개발·검증, 간헐적·버스트 트래픽

따라서 “KServe를 쓰면 무조건 0개로 줄어든다”는 설명은 정확하지 않습니다. Knative 모드에서 minReplicas: 0을 명시했을 때 scale-to-zero를 활용할 수 있습니다. Standard 모드는 HPA 기반이며 0개까지 줄이는 용도가 아닙니다.

yaml
spec:
  predictor:
    minReplicas: 0
    maxReplicas: 3
    scaleMetric: concurrency
    scaleTarget: 5
    model:
      modelFormat:
        name: mlflow
      runtime: kserve-mlserver
      storageUri: s3://ml-models/digits/a1b2c3d4/model

간헐적 모델 수십 개를 한 클러스터에 올리는 개발 환경이라면 유휴 자원을 크게 아낄 수 있습니다. 반대로 첫 요청 지연이 중요한 서비스나 모델 로딩에 수분이 걸리는 GPU 워크로드는 minReplicas: 1 이상이 더 적합합니다. 비용과 지연 중 무엇을 우선할지 모델별로 결정해야 합니다.

이 시리즈의 외부 노출은 Gateway API를 기준으로 설명합니다. Knative 네트워킹 계층으로 Kourier를 선택할 수 있고, 외부 Gateway가 TLS를 종료한 뒤 Kourier Service로 전달하는 구조를 사용할 수 있습니다. 특정 컨트롤러 설치 명령은 버전 의존성이 크므로 클러스터 운영자가 선택한 KServe·Knative 호환 조합을 따릅니다.

Airflow와 Kubeflow를 핵심 경로에서 뺀 이유

Airflow는 시간표, 의존성, 재시도, 여러 시스템을 넘나드는 DAG를 운영하는 범용 워크플로 오케스트레이터로 쓰입니다. Kubeflow Pipelines는 컴포넌트 기반 ML 워크플로와 메타데이터가 필요한 조직에서 사용할 수 있습니다. 이 도구들을 실습에 포함할지는 현재 구현할 흐름에 해당 기능이 필요한지를 기준으로 판단했습니다.

다만 이 실습에서 실제 목표는 다음 네 단계입니다.

  1. src/train.py 변경으로 CI를 시작합니다.
  2. 학습 이미지를 만듭니다.
  3. GitOps로 RayJob을 생성합니다.
  4. 결과를 MLflow에 기록합니다.

이 흐름은 별도의 DAG 스케줄러 없이도 구성할 수 있습니다. 사용하는 플랫폼마다 설치와 운영이 필요하므로, 각 도구가 “왜 필요한지”를 현재 요구와 연결해 판단합니다. 다음 요구가 생기면 오케스트레이터를 추가할 수 있습니다.

  • 정해진 시간이나 데이터 도착 이벤트로 재학습해야 합니다.
  • 전처리, 학습, 평가, 승인 단계별 재시도 정책이 다릅니다.
  • Kubernetes 밖의 데이터베이스·API·배치 시스템을 함께 조율합니다.
  • 여러 팀이 워크플로 UI, SLA, backfill 기능을 공통으로 사용합니다.

이 실습은 위 요구가 없는 학습 경로부터 구성합니다. 오케스트레이터의 도입은 필요한 실행 조건이 늘어날 때 다시 검토합니다.

플랫폼 상태를 먼저 확인하는 명령

애플리케이션을 배포하기 전에 CRD와 컨트롤러가 준비됐는지 확인합니다. 다음 명령은 읽기 전용입니다.

bash
kubectl get crd rayjobs.ray.io rayclusters.ray.io rayservices.ray.io
kubectl get crd inferenceservices.serving.kserve.io

kubectl get pods -n kuberay-operator
kubectl get pods -n kserve
kubectl get pods -n knative-serving

kubectl get svc -n mlflow
kubectl get clusterservingruntime

위 조회 결과에서 CRD와 컨트롤러를 확인한 뒤에는, 학습과 서빙에 필요한 연결까지 점검합니다.

  • KubeRay Operator Pod가 Running이고 Ray CRD를 조회할 수 있습니다.
  • KServe 컨트롤러가 Running이고 InferenceService CRD가 있습니다.
  • Knative 모드를 쓴다면 serving 구성요소와 선택한 네트워킹 계층이 준비돼 있습니다.
  • MLflow Service의 클러스터 DNS가 학습 namespace에서 해석되고 연결됩니다.
  • 사용할 모델 형식에 맞는 ServingRuntime이 존재합니다.

학습 namespace에서 MLflow 연결을 확인하려면 임시 curl Pod를 사용할 수 있습니다.

bash
kubectl run mlflow-connectivity-check \
  --namespace ml-training \
  --rm -it --restart=Never \
  --image=curlimages/curl:8.12.1 \
  -- http://mlflow.mlflow.svc.cluster.local:5000/health

이미지 태그는 예시입니다. 실제 환경에서는 승인한 digest로 고정하고, MLflow 배포가 다른 포트나 경로를 쓰면 Service 정의에 맞춥니다.

어디가 고장 났는지 계층별로 찾기

RayJob이 만들어졌지만 학습이 시작되지 않는다

bash
kubectl describe rayjob -n ml-training digits-train-a1b2c3d4
kubectl get pods -n ml-training -o wide
kubectl get events -n ml-training --sort-by=.lastTimestamp
  • ImagePullBackOff: Harbor 주소, pull secret, 이미지 태그를 확인합니다.
  • Unschedulable: CPU·메모리·GPU 요청과 노드 가용량을 확인합니다.
  • submitter 실패: entrypoint, 모듈 경로, Ray 버전 호환성을 확인합니다.
  • worker만 실패: head와 worker에 같은 이미지, Secret, PVC가 주입됐는지 비교합니다.

학습은 끝났지만 MLflow에 run이 없다

bash
kubectl logs -n ml-training -l ray.io/node-type=head --tail=200
kubectl get svc -n mlflow
kubectl get networkpolicy -A

MLFLOW_TRACKING_URI 오타, namespace DNS, NetworkPolicy, 인증서, MLflow 인증 설정을 차례로 확인합니다. S3 업로드만 실패한다면 tracking 서버 연결과 artifact store 연결을 분리해 진단합니다.

InferenceService가 Ready가 되지 않는다

bash
kubectl get inferenceservice -n ml-serving digits-classifier -o yaml
kubectl get pods -n ml-serving
kubectl describe pod -n ml-serving -l serving.kserve.io/inferenceservice=digits-classifier
kubectl logs -n ml-serving -l serving.kserve.io/inferenceservice=digits-classifier -c storage-initializer

주요 원인은 S3 endpoint·인증서·Secret, 잘못된 storageUri, ServingRuntime 미설치, 모델과 runtime의 버전 불일치입니다. Knative 모드에서는 요청이 해당 계층을 거치므로 Knative revision과 Kourier 경로까지 확인합니다.

운영에서 꼭 지킬 경계

  • MLflow UI에 보이는 모델과 실제 배포 모델을 자동으로 같은 것으로 간주하지 않습니다. 평가와 승인 게이트를 둡니다.
  • 모델 별칭은 편리한 가변 포인터입니다. 배포 Git에는 가능하면 불변 모델 URI나 아티팩트 버전을 기록합니다.
  • latest 이미지 태그 대신 Git SHA 태그나 OCI digest를 씁니다.
  • S3 키와 Harbor 암호를 YAML에 넣지 않습니다. Kubernetes Secret 또는 외부 Secret 관리 도구로 주입합니다.
  • Ray head, worker, submitter에 필요한 인증 정보와 볼륨이 모두 들어갔는지 확인합니다.
  • 학습용 서비스 계정과 서빙용 서비스 계정을 분리하고 최소 권한을 적용합니다.
  • scale-to-zero는 비용 기능이면서 지연 정책입니다. 콜드 스타트를 SLO에 포함합니다.

정리

도구를 책임으로 다시 정리하면 다음과 같습니다.

  • MLflow: 실험, run, 메트릭, 모델 버전과 아티팩트의 계보
  • Ray: Python 기반 분산 계산과 학습 실행
  • KubeRay: RayCluster·RayJob·RayService를 Kubernetes 방식으로 운영
  • KServe: 모델 아티팩트를 선언형 추론 API로 배포
  • Knative: KServe에서 선택할 수 있는 요청 기반 실행·scale-to-zero 계층

도구의 연결 경로를 정한 뒤에는 각 단계가 같은 데이터·모델·Python 환경을 사용하는지 확인해야 합니다. 다음 글에서는 이 조건을 고정해 같은 결과를 다시 만드는 방법을 다룹니다.

이전 글: 멀티클러스터 GitOps와 환경 승격
다음 글: 재현 가능한 ML을 위한 스토리지와 버전 관리

공식 참고자료