(’26) 머신러닝 플랫폼 엔지니어링 책 Study에서 책 4-5장 내용을 기반으로 본인 환경에서 실습한 내용을 정리하였습니다.

 

책 4–5장에서 언급하는 MLflow, Feast, Kubeflow Pipelines를 실제로 연결하는 과정에 대해 모델 파일과 피처 데이터가 어디에 저장되고 다음 단계가 그것을 어떻게 읽는지 직접 실습을 진행해보았다.

환경 준비와 실행 순서는 John Kim 님의 「04. 실험, 피처, 파이프라인 연결하기」와 ml-platform-book-env 저장소를 참고했다. 본인은 WSL2의 Ubuntu 26.04.1 LTS를 사용하기에 PowerShell 대신 Bash 환경에 맞게 바꾸어 실행해보았다. 즉, 책 내용을 해당 가이드에 따라 재현하면서 WSL에서 준비한 부분과 실제 확인한 결과를 블로그로 기록해보고자 하였다.

1. 실습 개요

전체 흐름부터 정리하면 다음과 같다. 특히 5장 파이프라인은 4장에서 학습해 MLflow에 등록한 모델을 가져와 배치 추론을 수행한다.

4장: CSV 읽기 → 모델 3개 학습 → MLflow에 기록 → 모델 등록
4장: 피처 parquet 업로드 → Feast 정의 등록 → 과거/온라인 피처 조회
5장: 대상 사용자 읽기 → Feast 피처 조회 → 등록 모델로 예측 → parquet 저장

‘배치 추론’은 여러 입력을 한 번에 읽어 예측 결과를 파일로 남기는 방식이다. 사용자 요청 한 건마다 바로 응답하는 API 서버와는 실행 형태가 다르다.

세 도구를 다음처럼 나눠 보니 역할이 더 분명해졌다.

도구 이번 실습에서 답하는 질문 실제 남는 것
MLflow 어떤 설정으로 학습했고, 어떤 모델 파일을 쓸까? 실험 기록, 지표, 모델 버전
Feast 이 사용자의 피처를 어떤 데이터와 시각 기준으로 가져올까? 피처 정의, 과거 조회 결과, Redis의 피처 값
KFP 예측 작업들을 어떤 순서로 실행하고 파일을 넘길까? 실행 기록, 단계별 로그와 아티팩트

여기서 피처(feature)는 모델에 넣는 입력값이다. 이 예제에서는 학력, 직업, 관계 등의 정보를 사용한다. 아티팩트(artifact)는 실행 중 만들어진 파일을 뜻한다. 모델 파일, 그래프 PNG, 단계 사이에 전달되는 parquet 등이 여기에 해당한다.

2. WSL 환경 준비: 기본 Python과 실습 Python을 분리

본인이 사용하는 WSL 환경에 이미 설치된 Python 3.14.4, git, uv와 실습 환경 간 충돌이 일어나지 않고, Docker, kind, kubectl은 WSL 환경에서 설치하지 않았기에, Docker Desktop 대신 Ubuntu 안에 Docker Engine을 설치하는 방식으로 준비했다.

WSL 터미널에서 필요한 패키지를 설치한다.

sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"

Docker 그룹 변경은 새 로그인 세션에서 반영된다. 기존 WSL 터미널을 계속 쓴다면 newgrp docker로 그룹이 적용된 새 셸에 들어간 뒤 docker info를 확인한다.

kind는 Docker 컨테이너 안에 작은 Kubernetes 클러스터를 만드는 도구다. 별도 서버 여러 대 없이도 Kubernetes의 Pod와 Service를 연습할 수 있다. kubectl은 만들어진 클러스터에 명령을 보내는 도구다.

mkdir -p ~/.local/bin
curl -fsSL https://kind.sigs.k8s.io/dl/v0.27.0/kind-linux-amd64 \
  -o ~/.local/bin/kind
curl -fsSL https://dl.k8s.io/release/v1.32.2/bin/linux/amd64/kubectl \
  -o ~/.local/bin/kubectl
chmod +x ~/.local/bin/kind ~/.local/bin/kubectl
export PATH="$HOME/.local/bin:$PATH"

이번 실행 환경은 아래와 같았다. Ubuntu 시스템 Python을 바꾸지 않고, 책 실습만 Python 3.11 가상환경으로 분리했다.

항목 확인한 값
운영체제 WSL2 / Ubuntu 26.04.1 LTS
Docker Engine 29.1.3
kind 0.27.0
Kubernetes 노드 이미지 kindest/node:v1.31.6
kubectl 클라이언트 1.32.2
uv 0.12.13
실습 Python 3.11.16
MLflow 서버 / Python 클라이언트 3.0.0 / 3.0.0
Feast 0.40.1
KFP 서버 / Python SDK 2.4.1 / 2.9.0
scikit-learn 1.5.2

WSL 운영체제와 Python 환경을 확인한 JupyterLab 화면

가상환경은 이 프로젝트에서만 쓸 Python과 패키지를 모아 두는 폴더다. Ubuntu가 제공하는 Python과 섞지 않으면 책의 버전 조건을 맞추기 편하다.

3. 저장소와 클러스터 준비

Docker 이미지와 Python 패키지는 WSL의 Linux 파일시스템 안에 두었다. 이번 작업 위치는 ~/project/ml-platform-lab/ml-platform-book-env다. 재현할 때는 자신의 홈 디렉터리에 만들어도 된다.

mkdir -p ~/project/ml-platform-lab
cd ~/project/ml-platform-lab
git clone https://github.com/johklo/ml-platform-book-env.git
cd ml-platform-book-env

git clone https://github.com/practical-mlops/chapter-4.git upstream/chapter-4
git clone https://github.com/practical-mlops/chapter-5.git upstream/chapter-5

uv venv .venv-book --python 3.11
uv pip install --python .venv-book -r book/requirements.txt
source .venv-book/bin/activate

python bookenv.py check
python bookenv.py up

python bookenv.py up을 실행하면 kind 클러스터를 만들고 KFP, 저장소, PostgreSQL, Redis, MLflow 서버를 준비한다. 처음 실행할 때는 여러 컨테이너 이미지를 받으므로 기다릴 시간이 필요하다.

이번 실습은 Kubeflow 전체 설치가 아니라 KFP standalone 설치다. Kubeflow Notebook 서버나 통합 인증 화면까지 준비하는 구성은 아니다. JupyterLab은 WSL에서 직접 실행한다.

설치 중 Pod가 재시작했을 때

실행 과정 중 metadata-grpc-deployment에서 CrashLoopBackOff가 보였다. 이 상태는 컨테이너가 실패하고 다시 실행되기를 반복한다는 뜻이다.

kubectl --context kind-book-env -n kubeflow get pods
kubectl --context kind-book-env -n kubeflow \
  logs deploy/metadata-grpc-deployment --tail=20

로그에는 다음 메시지가 있었다.

MySQL database was not initialized. Please ensure your MySQL server is running.

이때 MySQL Pod도 아직 ContainerCreating 상태였다. 이미지 다운로드와 데이터베이스 준비가 끝나기 전에 다른 서비스가 먼저 연결을 시도한 것이다. 바로 매니페스트나 DB 인증 설정을 바꾸기보다 의존 서비스가 준비됐는지 함께 확인했다.

MySQL이 준비된 뒤 재시작한 metadata 서비스도 1/1 Running으로 회복됐다. 이 경우 별도의 설정 수정이나 수동 Pod 재생성은 하지 않았다. 최종적으로 up의 마지막에 The environment is ready. Next: python bookenv.py forward가 출력됐다.

설치 완료 뒤 kubectl get pods로 확인한 서비스 상태

READY 1/1은 Pod의 컨테이너 하나가 준비됐다는 뜻이다. RESTARTS는 지금 장애라는 뜻이 아니라, 지금까지 재시작한 횟수다. 따라서 상태와 준비 여부를 함께 읽었다.

4. Windows 브라우저에서 WSL 서비스를 열기

별도 WSL 터미널에서 가상환경을 활성화하고 포트포워딩을 실행한다.

cd ~/project/ml-platform-lab/ml-platform-book-env
source .venv-book/bin/activate
python bookenv.py forward

포트포워딩은 PC의 특정 포트와 클러스터 안의 서비스 사이에 통로를 만드는 작업이다. 브라우저가 Kubernetes 서비스 주소를 직접 몰라도 localhost로 접속할 수 있게 해 준다.

브라우저 / 클라이언트 주소 용도
http://localhost:8080 KFP UI
http://localhost:5000 MLflow UI 및 API
http://localhost:8888/buckets/ SeaweedFS 저장소 화면
localhost:9000 S3 API
localhost:6379 Redis

JupyterLab은 저장소 화면의 8888과 겹치지 않도록 8890을 사용한다.

jupyter lab --notebook-dir book --port 8890 --ip 127.0.0.1 --no-browser

출력되는 토큰 포함 주소를 Windows 브라우저에서 열면 된다. 이번에는 WSL 서비스를 Windows Chrome에서 열어 확인했다.

버킷은 왜 여러 개 만들까

버킷은 S3 저장소에서 파일을 묶는 최상위 공간이다. 같은 저장소를 쓰더라도 학습 CSV, 모델 파일, 피처 정의, 최종 결과를 나눠 두면 어떤 단계가 무엇을 읽고 쓰는지 확인하기 쉽다.

python bookenv.py buckets
버킷 이번 실습에서 저장하는 것
mlflow-datasets 학습·테스트 CSV
mlflow-artifacts 모델, 그래프, 입력 열 목록
feature-data-sets 피처 원천 parquet, 예측 대상
feature-registry Feast 정의와 접속 설정
inference-datasets 최종 예측 결과
mlpipeline KFP 단계 사이의 중간 파일

이번 클러스터에 생성한 실습 버킷

5. 4장 MLflow: 학습 결과를 다시 찾을 수 있게 기록하기

JupyterLab에서 ch04/01_mlflow_tracking.ipynb를 위에서부터 실행했다.

첫 부분은 데이터 경로와 저장소 접속 설정을 준비한다. 이후 소득 분류 CSV를 읽고 범주별 분포를 살펴본다. 이번 데이터는 30,162행, 9개 열이었다.

MLflow의 income-classifier Experiment 안에는 데이터 탐색 run 한 개와 모델 학습 run 세 개가 기록된다. Experiment는 관련 실행을 모아 두는 폴더와 비슷하고, Run은 그 안의 한 번의 실행 기록이다.

이번에 학습한 모델은 Decision Tree, Random Forest, XGBoost다. 학습 알고리즘은 scikit-learn과 XGBoost가 담당하고, MLflow는 설정·점수·파일을 기록한다.

모델 MLflow run 테스트 ROC AUC 테스트 정확도
Decision Tree fortunate-goat-977 0.815114 0.818333
Random Forest fearless-moose-660 0.857340 0.826786
XGBoost abrasive-crow-116 0.718263 0.821813

ROC AUC는 분류기가 두 클래스를 얼마나 잘 구분하는지 살펴보는 지표다. 여기서는 값이 높을수록 좋게 본다. 다만 원본 코드는 데이터 분할과 일부 모델의 난수 시드를 고정하지 않는다. 같은 명령을 실행해도 이 글이나 참고 가이드와 점수가 정확히 일치하지 않을 수 있다.

또한 원본 XGBoost 셀은 마지막에 binary:hinge 모델의 0/1 예측으로 AUC를 계산한다. 다른 두 모델의 확률 기반 AUC와 같은 조건의 비교라고 해석하기는 어렵다. 이번 점수표는 책 코드가 남긴 결과를 확인한 기록이다.

이번 실행에서 MLflow에 기록된 탐색과 학습 run

모델 파일은 어디에 있을까

MLflow 화면에서 보이는 실행 이름, 설정, 지표 등은 PostgreSQL에 기록된다. 실제 모델과 그래프 파일은 mlflow-artifacts 버킷에 저장된다. 화면이 열린다고 모델 파일까지 정상적으로 저장됐다는 뜻은 아니므로 아티팩트도 확인해야 한다.

원본 노트북은 범주형 입력을 숫자 열로 바꾸고 column_list.pkl에 입력 열 순서를 저장한다. 예를 들어 학력과 직업을 여러 0/1 열로 바꾸면, 추론에서도 같은 열을 같은 순서로 넣어야 한다. 이 파일은 나중에 5장 추론 코드가 내려받는다.

모델 레지스트리에 등록

노트북 마지막 셀은 테스트 AUC가 높은 run을 찾아 random-forest-classifier라는 이름으로 등록한다. 이어서 가이드가 추가한 셀을 실행해 stage를 Production으로 지정한다.

이번에 선택된 원천 run은 fearless-moose-660였고, 테스트 AUC는 0.857340이었다. 모델은 버전 1 / Production으로 등록됐다. 원천 run ID는 489e1a27bc8b428c9b6f8fe9af0248e3다.

등록 모델의 버전과 원천 run

Production은 이번 코드가 모델 버전을 찾을 때 쓰는 표식이다. 이를 지정했다고 예측 API가 자동으로 배포되는 것은 아니다. MLflow 3에서는 stage 사용에 대한 FutureWarning이 나오지만, 이번 책 코드와의 연결에서는 실제로 동작하는지 확인했다.

6. 4장 Feast: 피처 정의와 피처 값을 구분하기

다음으로 ch04/02_feast_feature_store.ipynb를 실행했다.

학습 CSV를 읽는 것과 Feast를 사용하는 것은 서로 다른 과정이다. 이 책 예제의 학습 노트북은 CSV를 직접 읽고, Feast는 그 뒤에 따로 준비한다. 따라서 “학습도 처음부터 Feast로 했다”고 설명하면 이번 실행과 맞지 않는다.

이번 Feast 흐름은 다음과 같다.

원천 parquet 업로드
  → Entity / FeatureView 정의 등록
  → 과거 시점 피처 조회
  → Redis로 값 적재
  → 온라인 피처 조회
용어 쉽게 풀어 보면 이번 예제
Entity 누구의 값인지 찾는 키 user_id
FeatureView 함께 다룰 피처의 정의 demographic, relationship, occupation
Registry 피처의 이름·타입·위치 등을 기록한 목록 S3의 Registry 파일
Offline store 과거 피처를 읽는 원천 S3의 parquet
Online store 빠르게 조회할 최신값을 저장하는 곳 Redis

apply와 materialize는 다른 작업

feast apply는 피처 정의를 Registry에 등록하는 명령이다. 실행 로그에 세 FeatureView를 준비하는 메시지가 나타났다.

Deploying infrastructure for relationship
Deploying infrastructure for demographic
Deploying infrastructure for occupation

그렇다고 모든 피처 값이 Redis에 들어간 것은 아니다. feast materialize가 원천 데이터를 읽어 온라인 저장소로 옮긴다.

이번에는 원본 README와 같은 기간을 사용했다.

feast materialize 2022-09-16T00:00:00 2023-09-17T00:00:00

이 명령을 직접 실행한다면 노트북이 만든 book/ch04/feature_repo에서 실행해야 한다. 노트북의 도우미 셀은 이 작업 위치를 자동으로 지정한다.

과거 조회와 온라인 조회는 왜 나눌까

과거 조회는 사용자 ID와 예측하려는 기준 시각을 함께 사용한다. 그 시점에 맞는 피처를 가져오기 위해서다. 온라인 조회는 Redis에 적재된 최신값을 빠르게 가져오는 데 사용한다.

과거의 예측을 재현하면서 오늘의 최신값을 넣으면, 그때는 알 수 없었던 정보가 섞일 수 있다. 5장의 배치 추론이 get_historical_features()를 사용하는 이유도 이 구분과 연결된다.

실제 과거 조회는 사용자 9f2ac416-06e1-44a0-87bd-d4787c85bf66의 2023-01-30 10:59:42+00:00 기준으로 Sex=Male을 반환했다. 온라인 조회는 원본 스크립트에 지정된 다른 사용자 74b0638a-5742-4d1a-a03f-483794d7aa7c에 대해 Sex=Male을 반환했다. 두 예제가 같은 사용자의 값 변화를 비교한 것은 아니다. 출력 중 None은 print(df.info())가 반환값을 출력한 부분이며, Sex가 결측이라는 의미가 아니다.

Feast 원본 노트북에서 확인한 조회 결과

노트북을 마치면 feature-registry에 로컬용 registry_local.db와 feature_store.yaml이 남는다.

7. WSL의 localhost와 Pod의 localhost는 다름

이번 실습에서 가장 기억할 만한 연결 지점은 주소였다.

WSL에서 실행하는 노트북은 포트포워딩을 통해 localhost:5000과 localhost:9000으로 접속한다. 하지만 Kubernetes Pod 안에서 localhost는 해당 Pod 자체를 가리킨다.

실행 위치 MLflow 접속 주소
WSL의 노트북 http://localhost:5000
KFP 작업 Pod http://mlflow-service.mlflow.svc.cluster.local:5000

S3 API도 Pod에서는 minio-service.kubeflow.svc.cluster.local:9000을 쓴다.

그래서 로컬용 Feast 설정을 그대로 파이프라인에 넘기지 않는다. 5장 준비 과정에서 클러스터용 Registry를 새로 등록하고, feature_store_k8s.yaml이라는 별도 파일을 업로드한다.

두 설정을 분리해 두면 “노트북에서는 되는데 파이프라인에서는 안 된다”는 문제를 살펴볼 때 실행 위치부터 확인할 수 있다.

8. 5장 KFP: 네 단계의 배치 추론 실행

먼저 가이드가 제공하는 준비 스크립트와 Dockerfile을 사용한다.

python book/ch05/prepare.py

docker build -t book-ch5/feast-job:0.1 \
  -f book/ch05/Dockerfile.feast-job upstream/chapter-5/scripts/feast
docker build -t book-ch5/components:0.1 \
  -f book/ch05/Dockerfile.components upstream/chapter-5

python bookenv.py load book-ch5/feast-job:0.1
python bookenv.py load book-ch5/components:0.1

prepare.py는 원본 컴포넌트의 이미지 이름과 Redis 설정을 이 환경에 맞춘다. Docker로 이미지를 만드는 것과 kind 노드에 넣는 것은 별개다. 그래서 빌드 다음에 load를 실행했다.

참고 가이드의 Dockerfile은 오래된 Debian buster 기반 이미지를 bookworm으로 바꾸고, 추론 환경의 scikit-learn을 학습 환경과 같은 1.5.2로 맞춘다. 이 보완은 참고 가이드에 이미 들어 있는 변경이며, 이번 실습에서 새로 발견해 고친 것으로 소개하지 않는다.

클러스터용 Feast 정의와 입력 파일

kubectl --context kind-book-env apply -f book/ch05/feast-job.yaml
kubectl --context kind-book-env -n kubeflow wait \
  --for=condition=complete --timeout=300s job/feast-job
kubectl --context kind-book-env -n kubeflow logs job/feast-job

python book/ch05/upload_inputs.py

cd upstream/chapter-5
python inference_pipeline.py
cd ../..
grep 'image:' upstream/chapter-5/income_classifier_pipeline.yaml

feast-job은 Pod에서 사용할 피처 정의를 등록한다. upload_inputs.py는 클러스터용 설정과 예측 대상 파일을 올린다. 마지막 Python 명령은 파이프라인 정의를 실행용 YAML로 컴파일한다.

이번 이미지 참조는 네 컴포넌트 모두 book-ch5/components:0.1이었다. YAML이 만들어졌다는 것만으로 작업이 실행된 것은 아니다. 실제 실행은 KFP 서버에 등록하고 run을 시작한 뒤 확인해야 한다.

파이프라인 등록과 실행

참고 가이드는 UI에서 YAML을 업로드하고 실행한다. 이번에는 WSL의 Python SDK로 같은 YAML과 파라미터를 등록·제출하고, KFP UI에서 생성된 run과 그래프를 확인했다. 캐시를 끄고 실제로 네 컴포넌트를 실행했다.

import json
from pathlib import Path
import kfp

# 저장소 최상위 폴더에서 실행
client = kfp.Client(host="http://localhost:8080")
pipeline_id = client.get_pipeline_id("income-classifier-pipeline")
if not pipeline_id:
    pipeline = client.upload_pipeline(
        "upstream/chapter-5/income_classifier_pipeline.yaml",
        pipeline_name="income-classifier-pipeline",
    )
    pipeline_id = pipeline.pipeline_id

version = client.list_pipeline_versions(pipeline_id).pipeline_versions[0]
experiment = client.create_experiment("income-classifier")
params = json.loads(Path("book/ch05/run-parameters.json").read_text())
run = client.run_pipeline(
    experiment.experiment_id,
    "income-classifier-wsl-run-1",
    pipeline_id=pipeline_id,
    version_id=version.pipeline_version_id,
    params=params,
    enable_caching=False,
)
print(run.run_id)

SDK로 등록된 파이프라인을 실행하는 과정에서는 다음 오류도 만났다.

both `pipeline_id` and `version_id` are required

내 제출 코드에서 파이프라인 ID만 전달했기 때문이다. 등록된 파이프라인의 버전 ID도 조회해서 run_pipeline()에 함께 전달하니 run이 만들어졌다. 이 오류는 이번에 추가한 SDK 제출 코드의 문제였으며, 책의 추론 컴포넌트가 실패한 것은 아니다.

핵심 파라미터는 다음과 같다. 나머지 값은 가이드의 book/ch05/run-parameters.json을 사용했다.

파라미터 값
대상 파일 feature-data-sets/entity_df.parquet
Feast 설정 feature-registry/feature_store_k8s.yaml
모델 이름 / stage random-forest-classifier / Production
출력 파일 inference-datasets/income_predictions.parquet

KFP의 Experiment와 MLflow의 Experiment는 이름이 같아도 별개다. 전자는 파이프라인 실행을, 후자는 모델 학습 기록을 묶는다.

실제로 실행되는 네 단계는 아래와 같다.

단계 무엇을 하는가
read-data-from-minio 예측 대상 사용자와 기준 시각을 읽는다
retrieve-features-from-feast 대상에 맞는 과거 피처를 조회한다
model-inference MLflow 등록 모델과 입력 열 목록을 받아 예측한다
write-data-to-minio 예측 결과를 최종 버킷에 저장한다

KFP는 앞 단계의 Python 메모리를 다음 단계와 공유하지 않는다. 각 작업은 별도 Pod에서 실행되고 중간 결과는 파일로 전달된다. 화면의 화살표는 작업 순서뿐 아니라 파일 입력·출력의 연결로 읽어야 한다.

이번 run ID는 06e5dad7-2ae6-4e46-95aa-1379f72e5a58다. KFP 화면에서는 네 컴포넌트가 모두 초록색 체크로 바뀌었고, kubectl get workflows에서는 income-classifier-pipeline-j4b4j이 Succeeded로 표시됐다.

파이프라인이 실행 중일 때는 첫 컴포넌트가 동작하는 모습을 확인할 수 있었다.

실행 중인 KFP 파이프라인

이번 WSL 클러스터에서 실행한 KFP 배치 추론 그래프

UI와 kubectl을 함께 확인

실행 중과 완료 뒤에는 아래 명령도 조회했다. Workflow 이름은 이번 실행에서 생성된 실제 이름이다. 다른 실행에서는 get workflows 결과에서 자신의 이름을 확인해 넣으면 된다.

kubectl --context kind-book-env -n kubeflow get workflows
kubectl --context kind-book-env -n kubeflow get pods \
  -l workflows.argoproj.io/workflow=income-classifier-pipeline-j4b4j
kubectl --context kind-book-env -n kubeflow get jobs

kubectl에서 확인한 Workflow 성공과 작업 Pod 완료

작업 Pod들은 0/2 Completed로 표시됐다. 실패해서 준비가 안 된 것이 아니라, 두 컨테이너가 할 일을 마치고 종료됐기 때문이다. MLflow 서버처럼 계속 실행되는 서비스 Pod와 다르게 읽어야 한다.

모델 추론 작업은 main 컨테이너 로그를 확인했다.

kubectl --context kind-book-env -n kubeflow logs \
  income-classifier-pipeline-j4b4j-system-container-impl-3969511708 \
  -c main --tail=18

kubectl logs로 확인한 모델 파일 다운로드와 결과 업로드

로그에서는 아티팩트 다운로드, 추론 결과 파일 업로드, publish success, sub-process exited와 error="<nil>"을 확인했다. 특정 로그 한 줄만으로 성공을 판단하지 않고, Workflow 상태와 최종 결과 파일까지 함께 확인했다.

9. 성공 표시 다음에 실제 파일 확인

KFP 화면에 초록색 체크가 나타나도, 마지막 버킷에 원하는 결과가 저장됐는지까지 확인해야 한다.

python book/ch05/show_predictions.py

이번 파일을 읽은 결과는 다음과 같았다.

rows, columns: (30162, 91)
model input columns: 90
user_id in output: False
missing prediction values: 0

파일 크기는 282,569바이트였다. 예측값의 범위는 0.0–1.0, 평균은 약 0.2494였다. 평균값은 이 파일의 요약 통계이며, 정확도나 모델 품질 지표는 아니다.

참고 가이드에는 92열이라고 되어 있지만 이번 실행은 91열이었다. 저장된 column_list.pkl에는 모델 입력 열이 90개 있고, 결과는 그 입력 열에 예측 열 하나가 더해진 구조였다. 참고 글의 숫자를 옮기지 않고 이번 파일에서 측정한 값으로 기록했다.

최종 버킷에 저장된 이번 실행의 예측 파일

parquet를 읽어 확인한 결과와 데이터 크기

parquet는 테이블 데이터를 저장하는 바이너리 형식이다. 웹 화면에서 파일을 텍스트로 열었을 때 깨진 문자나 PAR1이 보인다고 곧바로 파일이 손상됐다고 판단하면 안 된다. pandas 등으로 실제 테이블을 읽어 확인하는 편이 정확하다.

실행 성공과 결과를 업무에 쓸 수 있다는 것은 별개

이번 원본 결과에는 user_id가 없었다. 추론 코드가 모델 입력에서 ID를 제거하고 결과에 다시 붙이지 않기 때문이다. 모델 입력에서 ID를 빼는 것과 예측 결과의 사용자를 식별할 수 없게 만드는 것은 다른 문제다.

실제 서비스 결과라면 사용자 ID, 기준 시각, 구체 모델 버전도 함께 남기도록 보완해야 한다.

예측 열 이름은 Predicted_Income_Class지만 원본 코드는 predict_proba()의 두 번째 클래스 값을 넣는다. 따라서 0.7 같은 값은 클래스 번호가 아니라 확률 추정치다. 확률을 0/1 판단으로 바꾸는 임계값 설정은 이번 코드에 포함되어 있지 않다.

원본 코드는 테스트 점수로 모델을 선택한다. 운영용 모델 선택에서는 검증셋으로 후보를 고르고 테스트셋은 최종 확인에 사용하는 방식을 별도로 검토할 필요가 있다. 이번 실습에서 예측 파일을 만들었다는 사실만으로 모델의 일반화 성능이나 업무 적합성까지 검증한 것은 아니다.

10. 이번 WSL 실습에서 확인한 점

이번 실행은 책의 모델 학습, 피처 준비, 배치 추론 흐름을 WSL에서도 연결할 수 있는지 확인하는 데 초점을 맞췄다.

Ubuntu 안에 Docker Engine과 CLI 도구를 준비하고, Python 3.11 가상환경에서 책의 노트북 두 개를 실행했다. MLflow의 네 run과 등록 모델, Feast의 과거·온라인 조회, KFP의 네 작업 성공, S3의 최종 예측 파일까지 확인했다. WSL에서 추가로 필요한 준비와 실행 위치에 따른 주소 차이를 함께 확인할 수 있었다.

내가 다시 설명할 수 있어야 할 포인트는 다음과 같다.

  1. MLflow는 모델을 대신 학습하는 도구가 아니라, 학습 설정·결과·모델 파일을 추적하는 도구다.
  2. Feast의 Registry에 정의가 있다고 Redis에 값까지 들어 있는 것은 아니다. apply와 materialize를 구분해야 한다.
  3. 과거 조회에는 기준 시각이 필요하다. 최신 온라인 값과 과거 시점의 값은 사용 목적이 다르다.
  4. WSL 노트북과 Kubernetes Pod는 접속 주소가 다르다. localhost를 복사하기 전에 실행 위치를 확인해야 한다.
  5. KFP 성공 표시 이후에도 최종 파일의 크기, 내용, 사용자 식별 열을 확인해야 한다.

세 도구를 각각 설치하는 것보다, 모델과 데이터가 어디에 남고 다음 단계가 어떤 주소와 파일 이름으로 가져가는지 확인하는 과정이 이번 스터디의 핵심이었다.

다시 실행하거나 종료할 때

환경은 결과를 다시 확인할 수 있도록 남겨 두었다. 다시 접속하려면 클러스터 상태를 확인하고 forward와 JupyterLab을 실행한다.

cd ~/project/ml-platform-lab/ml-platform-book-env
source .venv-book/bin/activate
python bookenv.py status
python bookenv.py forward

다른 터미널에서:

cd ~/project/ml-platform-lab/ml-platform-book-env
source .venv-book/bin/activate
jupyter lab --notebook-dir book --port 8890 --ip 127.0.0.1 --no-browser

Ctrl+C로 포트포워딩이나 JupyterLab을 멈추면 접속 프로세스가 종료된다. 클러스터를 삭제하는 python bookenv.py down은 내부 실습 기록도 지우므로, 이 글의 결과를 확인하기 위해 이번에는 실행하지 않았다.

참고 자료

(’26) 머신러닝 플랫폼 엔지니어링 책 Study를 기반으로 AWS 환경에서 MLOps 플랫폼을 구성하기 위한 운영 요구사항을 정리했다. MLOps는 데이터 준비부터 학습·평가·배포·모니터링과 재훈련까지 연결하고, 이를 반복 가능하게 운영하기 위한 실천 체계다. 이 글에서는 AWS가 소개하는 네 가지 원칙과 Level 0·1·2를 살펴보고, 쿠버네티스 기반 구성요소를 Amazon EKS 환경에 적용했을 때의 역할과 연결 관계를 정리한다.

 

MLOps란 머신러닝에 해당하는 ML (Machine Learning)과 운영에 해당하는 Operations를 줄여서 쓴 Ops가 만난 단어로, 실제 운영 관점에서 볼 때 반복적으로 이루어지는 머신러닝 모델 학습부터 추론, 그리고 관련한 피처 엔지니어링 등 종합적인 부분을 다루는 넓은 범위에 속한다. 스터디에서 기반으로 한 책에서 MLOps 성숙도에 대해 소개하고 있는데, AWS MLOps 성숙도와 함께, 책에서 언급된 쿠버네티스 실습 기반 내용을 Amazon EKS로 시작하는 내용에 대해 글로 정리해본다.

1. 무엇을 운영할 것인가

MLOps 플랫폼 관점에서 중요한 것이 바로 "문제 정의부터 데이터 준비, 학습, 평가, 배포와 모니터링까지 머신러닝 수명주기" 와 "그 과정을 어떻게 반복 가능하고 신뢰할 수 있게 운영하는가"라 볼 수 있겠다.

모델을 학습시켜 원하는 평가 점수를 얻었다고 하더라도, 서비스에 적용하려면 확인해야 할 것이 많다. 학습할 때 사용한 전처리를 추론에서도 동일하게 적용하는지, 어떤 버전의 모델이 배포되어 있는지, 실제 요청을 처리하는 데 필요한 성능을 확보했는지 등을 살펴야 한다. 입력 데이터와 사용 환경이 달라졌을 때 모델을 다시 평가하고 갱신하는 과정도 필요하다.

그래서 MLOps 플랫폼의 운영 대상은 모델이 만들어지고 사용되며 다시 개선되는 전체 과정이라고 볼 수 있겠다.

 

이 흐름의 시작은 해결할 문제를 정하는 것이다. 간단한 규칙으로 충분한지, 학습에 필요한 데이터를 확보할 수 있는지, 어떤 오류를 줄여야 하는지에 따라 이후 설계가 달라진다. 모델의 평가 기준도 문제 정의와 연결되어야 한다.


예를 들어 이상 거래 탐지에서는 정상 거래를 잘못 차단하는 비용과 이상 거래를 놓치는 비용이 다르다. 추천 시스템에서는 오프라인 평가 점수가 좋아졌더라도 실제 사용자의 반응이 개선되는지 확인해야 한다. 플랫폼은 이러한 판단에 필요한 데이터와 실행 결과를 남길 수 있어야 한다.

 

배포 이후에는 서비스가 정상 응답하는지와 예측이 유효한지를 함께 살펴야 한다. API가 HTTP 200을 반환하더라도 잘못된 예측을 하고 있을 수 있기 때문이다. 요청 지연시간과 오류율을 관찰하는 것에 더해, 실제 정답을 확보할 수 있는 시점에 예측 품질을 확인하는 절차가 필요하다.

 

플랫폼이 지원해야 할 연결 관계는 다음 질문으로 정리할 수 있다.

  • 어떤 데이터와 코드로 모델을 만들었는가?
  • 어떤 설정과 실행 환경을 사용했는가?
  • 무엇을 검증한 뒤 배포했는가?
  • 현재 서비스하는 모델은 어느 버전인가?
  • 문제가 생기면 어떤 버전으로 돌아갈 수 있는가?

이 질문에 실행 기록으로 답할 수 있어야 재현, 장애 분석과 복구가 가능해진다.

2. 반복 가능한 운영을 위한 AWS MLOps의 네 가지 원칙

AWS — MLOps란 무엇인가요?에 따르면, AWS는 MLOps의 핵심 원칙으로 버전 제어, 자동화, Continuous X, 모델 거버넌스를 제시한다. 이 원칙들은 모든 단계에 함께 적용되며, 성숙도가 높아질수록 관리 대상과 자동화 범위가 넓어진다.

2.1. 버전 제어: 모델을 만든 조건을 함께 기록한다

코드의 Git 커밋을 알고 있어도 학습 데이터와 설정이 다르면 같은 결과를 기대하기 어렵다. 하나의 모델 릴리스에는 데이터셋 식별자, 전처리 방식, 학습 설정, 실행 환경, 모델 아티팩트와 평가 결과가 연결되어야 한다. 여기에 추론 컨테이너 이미지와 배포 구성을 연결하면 현재 서비스하는 모델의 출처를 따라갈 수 있다.

관리 대상 기록할 정보
코드 전처리·학습·평가·추론 코드의 Git 커밋
데이터 원천 위치, 데이터셋 버전, 분할 조건
설정 피처 정의, 하이퍼파라미터, 난수 시드
실행 환경 라이브러리, 컨테이너 이미지, 실행 조건
모델 모델 버전, 아티팩트 위치, 학습 실행 식별자
평가 평가 데이터, 지표, 기준 모델과의 비교 결과
배포 모델 버전, 이미지 버전, 배포 설정과 시점

예를 들어  S3 Versioning을 사용하면 개별 객체의 여러 버전을 유지할 수 있다. 학습 데이터가 여러 파일로 구성된다면 사용한 버킷·객체 키·버전 ID를 매니페스트로 묶어 하나의 데이터셋을 식별할 수 있다. 이때, 데이터셋 버전과 학습·검증·테스트 분할은 구분해야 한다. 데이터셋 버전은 수집하거나 정제한 데이터의 변화 이력이고, 분할은 해당 버전 안에서 데이터를 어떤 용도로 사용할지 나눈 결과다. 분할 규칙과 시점까지 기록해야 평가 결과를 비교할 근거가 된다.

 

이처럼 데이터, 코드, 실행과 모델 사이의 관계를 연결한 것이 리니지다. 리니지가 있으면 모델을 다시 만들거나, 잘못된 데이터가 어떤 모델에 영향을 주었는지 조사하고, 이전 릴리스로 복구할 수 있다. AWS의 ML 운영 지침도 코드·설정·모델·인프라의 버전 관리와 재현 가능한 환경을 함께 강조한다.

 

2.2. 자동화: 입력과 통과 조건을 명시한다

자동화는 실행할 명령을 순서대로 묶는 것에서 시작할 수 있다. 하지만 운영에 사용하려면 각 단계의 입력과 출력, 성공·실패 조건도 정의해야 한다. 데이터 검증에 실패하면 학습을 중단하고, 평가 기준을 충족하지 못한 모델은 배포 후보로 승격하지 않는 식이다. 실패한 단계와 원인을 확인하고, 필요한 조건을 수정한 뒤 다시 실행할 수 있어야 한다.

프로덕션 승인에 사람의 검토가 필요하다면 평가 결과와 변경 내용을 함께 제공하고 승인 기록을 남긴다. 자동화된 작업과 사람의 판단을 같은 흐름 안에 두면 반복 작업을 줄이면서도 책임 있는 결정을 유지할 수 있다.

실행 환경과 인프라 구성도 코드로 관리하면 개발·검증·운영 환경의 차이를 줄이고 재구성할 수 있다. 파이프라인과 환경의 일관성은 자동화된 결과를 신뢰하기 위한 기반이다.

2.3. Continuous X: 운영 결과를 다음 변경으로 연결한다

AWS는 지속적 활동을 CI (Continuous Integration / 지속적 통합), CD (Continuous Delivery / 지속적 전달), CT (Continuous Training / 지속적 훈련), CM (Continuous Monitoring / 지속적 모니터링)으로 설명한다.

활동 의미 MLOps에서 확인할 내용
지속적 통합, CI 변경을 통합하고 검증 코드 테스트, 데이터 검증, 파이프라인 호환성
지속적 전달, CD 검증된 결과를 배포 가능한 상태로 전달 모델 패키징, 스테이징 검증, 배포 절차
지속적 훈련, CT 새로운 데이터와 조건에 따라 재훈련 데이터 적합성, 후보 모델 생성과 비교
지속적 모니터링, CM 운영 중 상태와 품질을 관찰 서비스 지표, 데이터 변화, 예측 품질, 비즈니스 지표

이 활동들은 하나의 피드백 흐름으로 연결된다. 운영 중 입력 데이터가 달라지거나 예측 품질이 떨어지면 원인을 확인하고 재훈련을 검토한다. 새 후보 모델은 평가 기준을 통과한 뒤 배포 절차로 전달한다. 재훈련과 프로덕션 승격에는 서로 다른 통과 조건이 필요하다. 새로운 데이터로 학습했다는 사실만으로 기존 모델을 교체할 근거가 충분해지는 것은 아니다.

2.4. 모델 거버넌스: 책임과 승인 근거를 남긴다

모델 거버넌스는 모델의 책임자, 검토·승인 기준, 접근 권한과 감사 기록을 관리하는 과정이다.

누가 모델의 비즈니스 목표를 정의하는지, 어떤 평가 결과를 근거로 배포를 승인하는지, 민감 데이터와 모델 접근을 어떻게 통제하는지 정해야 한다. 운영 중 문제가 발생했을 때 판단하고 대응할 담당자도 명확해야 한다.

모델의 승인과 배포 이력에 다음 정보가 연결되면 운영 책임을 설명하기 쉬워진다.

  • 모델 소유자와 운영 담당자
  • 평가 기준과 검증 결과
  • 승인자와 승인 시점
  • 배포된 모델 및 실행 환경
  • 운영 중 발견된 문제와 대응 기록

거버넌스는 데이터 사용부터 모델 검토, 배포, 피드백과 사용 종료까지 수명주기 전체에 적용된다.

3. ML 워크플로 자동화 수준: Level 0·1·2

AWS가 소개하는 Level 0·1·2는 ML 워크플로의 자동화 수준을 설명한다. 이를 이해할 때는 어떤 도구를 설치했는지보다 운영하는 대상이 무엇이며 변경을 어떻게 처리하는지를 살펴보는 것이 도움이 된다.

3.1. Level 0: 사람이 학습한 모델을 전달하는 단계

Level 0에서는 데이터 준비·학습·검증과 단계 간 전환을 사람이 수행하고, 학습한 모델을 엔지니어링 팀에 전달하는 방식이다. EKS를 사용하는 환경에서도 충분히 나타날 수 있는 모습이다. 예를 들어 데이터 사이언티스트가 노트북에서 학습한 모델 파일을 전달하면, 엔지니어가 이를 API에 포함하고 컨테이너 이미지로 만들어 배포할 수 있다.

 

여기서 쿠버네티스/EKS에 배포하는 과정은 API 배포 측에 복잡한 테스트·배포 체계가 존재할 수 있는 것과 같이 이미 자동화되어 있을 수도 있다. ML 수명주기의 자동화 수준을 판단하려면 그 앞에 있는 데이터 준비와 학습, 평가 과정까지 함께 확인해야 한다. 예를 들어, 새 데이터가 들어왔을 때 어느 코드를 어떤 순서로 실행해야 하는지, 모델과 전처리 코드가 서로 맞는지, 평가 결과가 어떤 모델에 해당하는지를 담당자가 연결하고 있다면 그 부분이 개선 대상이라고 볼 수 있겠다.

Level 0 에서는 우선 다음 질문에 대한 답을 찾아보면서 현재의 운영 방식을 점검하는 것을 권장한다.

지금 서비스 중인 모델을 같은 조건으로 다시 만들려면, 누구에게 무엇을 물어봐야 하는가?

 

필요한 정보가 특정 담당자의 노트북이나 기억에 모여 있다면, 이를 코드·설정·실행 기록으로 옮기는 것부터 시작할 수 있다.

3.2. Level 1: 학습 파이프라인을 반복 실행하는 단계

Level 1에서는 새로운 데이터에 따라 학습을 반복할 수 있도록 ML 파이프라인을 자동화한다. 운영 환경에 전달하는 중심 대상이 학습된 모델에서 반복 실행할 학습 파이프라인으로 넓어진다. 데이터 준비 완료나 일정 등의 트리거에 따라 정해진 절차가 실행되며, 개발·검증·운영 환경에서 일관된 파이프라인을 사용하고 생성된 모델을 예측 서비스로 전달하는 흐름을 구성한다.

 

이 단계에서 중요한 것은 한 번의 파이프라인 실행을 설명할 수 있는가다. 어떤 데이터셋을 입력으로 받았고, 어떤 검증을 거쳐, 어떤 설정으로 학습했으며, 어떤 모델과 평가 결과가 생성되었는지를 연결해야 한다.

 

EKS 기반 구성에서는 컨테이너 기반 ML 워크플로를 정의하고 실행하는 Kubeflow Pipelines 를 검토할 수 있다. 파이프라인을 여러 컴포넌트로 나누면 단계별 입력과 출력, 실행 순서와 상태를 관리할 수 있다.

실험 기록과 모델 관리는 역할을 구분한다.

  • MLflow Tracking으로 실행별 데이터·파라미터·지표·아티팩트를 기록하도록 구성한다.
  • MLflow Model Registry 로 등록 모델의 버전과 별칭을 관리하고 학습 실행과 연결한다.

이렇게 하면 선택한 모델이 어떤 실험에서 만들어졌는지 추적할 수 있다.

 

모델은 학습에 사용하지 않은 데이터에서 정해진 기준으로 평가하고, 필요하면 현재 운영 모델과 비교한다. 후보 모델 생성과 프로덕션 승격을 각각 관리하면 재훈련을 반복하면서도 배포 판단의 근거를 유지할 수 있다.

3.3. Level 2: 학습 파이프라인 자체를 CI/CD로 관리하는 단계

Level 2에서는 파이프라인 소스 작성, 컴포넌트의 빌드·테스트·배포, 예측 서비스 운영과 모니터링을 연결한다. AWS는 여러 ML 파이프라인을 조율하는 오케스트레이터와 여러 모델을 추적하는 모델 레지스트리도 이 수준의 구성요소로 설명한다.

 

Level 1과의 차이는 모델을 만드는 방법이 바뀔 때 드러난다. 기존 파이프라인에 새로운 데이터셋을 넣는 경우에는 이미 정의한 절차를 반복 실행한다. 피처 계산 로직을 수정하거나 학습 알고리즘을 교체하거나 평가 단계를 추가하면 파이프라인 자체가 변경된다. 이 변경에도 테스트와 릴리스 절차가 필요하다.

 

Level 2의 흐름은 다음 세 부분으로 나눌 수 있다.

  1. 실험을 바탕으로 파이프라인 소스를 작성하고 변경한다.
  2. 컴포넌트와 파이프라인을 빌드·테스트하고 실행 환경에 배포한다.
  3. 파이프라인 실행으로 생성한 모델을 검증·배포하고 운영 지표를 수집한다.

 

EKS 기반에서는 학습 오케스트레이션과 서빙 배포의 역할을 구분한다. 예를 들어 CI에서 Kubeflow Pipelines의 컴포넌트와 파이프라인 정의를 검증한 뒤 등록하는 흐름을 구성할 수 있다. 서빙 리소스는 Git에 기록한 배포 설정을 Argo CD가 Kubernetes에 동기화하도록 구성할 수 있다.

 

두 흐름은 학습 실행 식별자, 모델 버전, 컨테이너 이미지 버전과 배포 기록으로 연결한다. 그래야 운영 중인 예측 서비스에서 출발해 모델의 생성 과정까지 추적할 수 있다.

 

세 수준을 운영 대상의 변화로 정리하면 다음과 같다. 오케스트레이터나 모델 레지스트리를 Level 1에서 먼저 도입할 수도 있다. 도구의 유무만으로 수준을 판정하기보다, 데이터와 파이프라인의 변경을 어떤 절차로 검증하고 운영에 반영하는지 확인해야 한다.

수준 운영의 중심 확인할 질문
Level 0 학습된 모델의 수동 전달 해당 모델을 만든 조건을 다시 찾을 수 있는가?
Level 1 반복 실행하는 학습 파이프라인 새로운 데이터로 검증된 절차를 실행할 수 있는가?
Level 2 학습 파이프라인 자체의 CI/CD 모델을 만드는 방법의 변경도 검증하고 전달할 수 있는가?

4. 조직과 플랫폼의 확장도 함께 고려하기

여러 팀이 같은 플랫폼을 사용하기 시작하면 자동화 외에도 공통으로 풀어야 할 문제가 늘어난다. 팀별 데이터와 실행 권한은 어떻게 나눌지, 새로운 팀에 환경을 어떻게 제공할지, 각 모델의 운영 책임을 어떻게 유지할지 정해야 한다.

 

이 관점에서 함께 볼 수 있는 자료가 2022년 AWS 블로그에 소개된  「MLOps foundation roadmap for enterprises with Amazon SageMaker」이다. 이 자료에서는 사람·운영·기술을 포함한 기반 구축을 Initial, Repeatable, Reliable, Scalable 네 단계로 설명한다.

단계 중심 목표
Initial 안전한 실험 환경에서 ML의 비즈니스 가능성 검증
Repeatable 학습 파이프라인, 저장소와 모델 관리 방식의 표준화
Reliable 스테이징 검증, 승인, 운영 배포·모니터링·롤백
Scalable 공통 템플릿과 환경 제공을 통한 여러 팀으로의 확장

두 체계는 서로 다른 관점을 제공한다.

  • Level 0·1·2는 ML 워크플로의 자동화 깊이를 설명한다.
  • 네 단계 로드맵은 조직과 플랫폼의 운영 역량을 확장하는 과정을 설명한다.

따라서 두 체계를 일대일로 대응시키기보다 함께 참고하는 편이 적절하다.

 

EKS 기반 플랫폼에 적용하면, 팀마다 환경을 수동으로 만들어 주던 방식에서 공통 템플릿과 표준 배포 절차를 제공하는 방향을 생각할 수 있다. 플랫폼 팀은 공통 실행 환경, 배포와 관측 도구를 제공하고, 각 도메인 팀은 데이터·모델·평가 기준과 운영 책임을 유지하도록 역할을 나눌 수 있다. 공통화할 대상과 팀별로 결정할 대상을 함께 정리해야 플랫폼을 확장하면서도 모델별 요구사항을 반영할 수 있다.

5. Amazon EKS에 어떤 역할을 맡길 것인가

Amazon EKS는 AWS가 Kubernetes 컨트롤 플레인을 관리하는 서비스다. EKS Auto Mode를 사용하면 컴퓨팅·네트워킹·스토리지 등 데이터 플레인 인프라의 프로비저닝, 확장과 유지관리도 AWS에 맡길 수 있다. 이 기반 위에서 학습 작업과 추론 서비스를 실행하고, 데이터 저장·실험 추적·모델 관리·배포 도구를 연결한다 (참고: "What is Amazon EKS? [docs.aws.amazon.com]").

 

이 기반 위에 학습 워크로드와 추론 서비스를 배치하고, 데이터 저장, 실험 추적, 모델 관리와 배포 도구를 연결하는 구성을
생각할 수 있다. 앞서 정리한 운영 요구사항을 기준으로 역할을 나누면 다음과 같다.

운영 영역 구성요소 예시 담당 역할
워크로드 실행 Amazon EKS 컨테이너 기반 학습 작업과 추론 서비스 실행
데이터·아티팩트 저장 Amazon S3 데이터셋, 모델 아티팩트와 평가 결과 보관
컨테이너 이미지 관리 Amazon ECR 학습·추론 애플리케이션 이미지 보관
학습 오케스트레이션 Kubeflow Pipelines 학습·평가 단계의 실행과 의존성 관리
실험 추적 MLflow Tracking 실행별 입력·설정·지표·아티팩트 기록
모델 버전 관리 MLflow Model Registry 등록 모델과 버전·별칭 관리
변경 검증 Git 저장소와 CI 코드·컴포넌트 테스트, 이미지 빌드
서빙 배포 Helm·Argo CD 배포 설정 관리와 Kubernetes 상태 동기화
관측과 대응 Prometheus·Grafana 등 요청·자원 메트릭 수집, 조회·시각화와 규칙 기반 경보

이는 역할을 이해하기 위한 구성 예시로, 실제 구현에서는 요구사항과 팀의 운영 역량에 따라 관리형 서비스를 활용할 범위와 직접 운영할 컴포넌트를 선택할 필요가 있다. 특히, 데이터 드리프트와 예측 품질을 관찰하려면 애플리케이션이나 별도 평가 과정에서 관련 지표를 생성하고, 이를 모니터링·경보 체계에 연결할 필요가 있겠다.

 

어떤 데이터를 학습에 사용할지, 모델이 어떤 평가를 통과해야 하는지, 누가 프로덕션 배포를 승인할지는 플랫폼의 운영 절차로 정의한다.

 

컨테이너 이미지 버전과 모델 버전도 구분해야 한다. 추론 이미지 안에 모델을 포함할 수도 있고, 실행 시 S3 등에서 모델을 가져올 수도 있다. 어느 방식을 선택하든 배포 기록에는 사용한 이미지와 모델 버전이 함께 식별되어야 한다.

 

6. MLOps에서 LLMOps로 확장하면 무엇이 달라지는가

생성형 AI와 RAG 시스템에서도 버전 제어, 자동화, 지속적 개선과 거버넌스는 중요하다. 관리해야 할 대상에는 프롬프트, 검색 데이터, 평가 세트와 모델 제공자의 설정 등이 추가된다. AWS도 생성형 AI 운영에서 이러한 자산과 평가·배포·모니터링의 연결을 강조한다.

운영 영역 LLMOps에서 추가로 관리할 대상
버전 관리 프롬프트, 기반 모델, 추론 설정
데이터 RAG 문서, 청크, 임베딩, 벡터 인덱스
평가 답변의 정확성·관련성·근거 일치
관측 토큰 사용량, 비용, 응답 지연시간
거버넌스 민감 정보, 가드레일, 프롬프트 공격 대응

운영 중인 응답이 어떤 모델과 프롬프트, 검색 문서에서 만들어졌는지 추적할 수 있어야 한다. 변경 전후의 품질을 비교할 평가 세트와 문제가 있는 구성을 되돌릴 절차도 필요하다.

7. 마치며

MLOps 플랫폼을 구성할 때는 모델이 만들어지고 사용되는 과정을 어디까지 연결할 것인지부터 정리해야 한다.

 

AWS의 Level 0·1·2는 운영의 중심이 학습된 모델에서 반복 실행하는 학습 파이프라인으로, 다시 파이프라인 자체의 CI/CD와 운영으로 확장되는 흐름을 이해하는 데 도움이 된다. 기업 도입 로드맵은 이러한 역량을 여러 팀과 사용 사례로 확장할 때 필요한 운영 구조를 함께 생각하게 한다.

 

쿠버네티스는 학습·추론 워크로드를 실행하고 관리하는 기반을 제공한다. 여기에 데이터·코드·모델의 버전, 평가와 승인, 배포와 모니터링 기록을 연결하면 재현하고 복구할 수 있는 플랫폼으로 발전시킬 수 있다. 본 글에서는 이러한 운영 요구사항을 Amazon EKS 기반 구성요소의 역할과 연결 관계로 정리했다.

 

MLOps/LLMOps 플랫폼을 구성하면서 계속 생각해볼만한 질문은 다음과 같다.

지금 서비스하는 모델을 어떤 근거로 배포했는지 설명할 수 있는가? 그리고 같은 과정을 다시 실행하고 복구할 수 있는가?

 

각 구성요소를 연결하고 실행 기록을 남기는 과정에서 이 질문에 답할 수 있는 플랫폼을 구성하는 것이 중요하다고 할 수 있겠다.

 

쿠버네티스를 운영하면서 사용하는 kubectl 버전은 운영하는 Kubernetes 클러스터 버전과 호환되어야 한다. 이 내용은 Kubernetes 공식 문서 내 버전 차이 정책(Version Skew Policy)에서 확인할 수 있으며, Amazon EKS 문서에도 명시적으로 언급되어 있다.

kubectl은 kube-apiserver의 한 단계 마이너 버전(이전 또는 최신) 내에서 지원한다.

예:

  • kube-apiserver은 1.32 이다.
  • kubectl은 1.33, 1.32 및 1.31 을 지원한다.

참고:

HA 클러스터의 kube-apiserver 인스턴스 간에 버전 차이가 있으면 지원되는 kubectl 버전의 범위도 줄어든다.

예:

  • kube-apiserver 인스턴스는 1.32 및 1.31 이다.
  • kubectl은 1.32 및 1.31 에서 지원한다(다른 버전은 kube-apiserver 인스턴스 중에 한 단계 이상의 마이너 버전 차이가 난다).

https://kubernetes.io/ko/releases/version-skew-policy/#kubectl

 

버전 차이(skew) 정책

다양한 쿠버네티스 구성 요소 간에 지원되는 최대 버전 차이

kubernetes.io

 

참고

Amazon EKS 클러스터 제어 영역과 마이너 버전이 하나 다른 kubectl 버전을 사용해야 합니다. 예를 들어, 1.30 kubectl 클라이언트는 Kubernetes 1.29, 1.30, 1.31 클러스터와 함께 작동합니다.

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/install-kubectl.html#kubectl-install-update

 

kubectl 및 eksctl 설정 - Amazon EKS

Amazon EKS 클러스터 제어 영역과 마이너 버전이 하나 다른 kubectl 버전을 사용해야 합니다. 예를 들어, 1.30 kubectl 클라이언트는 Kubernetes 1.29, 1.30, 1.31 클러스터와 함께 작동합니다.

docs.aws.amazon.com

 

그런데 Amazon EKS를 클러스터를 사용하다보면 kubectl 명령어를 실행할 때, `v1.29.0-eks-5e0fdde`와 같은 형식의 버전 문자열을 볼 수 있다 (참고: https://aws.amazon.com/ko/blogs/tech/how-to-upgrade-amazon-eks-worker-nodes-with-karpenter-drift/ ). Amazon EKS 사용자 가이드 - kubectl 및 eksctl 설정 문서에 따르면 이 바이너리는 업스트림 커뮤니티 버전과 동일하다고 언급되어 있다. 이와 같이 동일한 바이너리를 사용하더라도 버전 스트링을 커스터마이징하는 경우가 있는데, 클라우드를 제공하는 프로바이더 등에서 직접 바이너리를 빌드했음을 알려 신뢰성을 확보하거나, 또는 내부 버전 관리 목적을 위해 (예: 특정 환경용 빌드 구분 - `v1.32.0-internal`, 테스트 환경 구분 - `v1.32.0-test`, 개발 단계 구분 - `v1.29.0-dev`) 사용할 수 있겠다.

 

Kubernetes 소스를 직접 활용하여 버전 문자열을 커스터마이징해보자.

 

사전 요구사항 

  • Go 개발 환경: 아래 과정을 사용하면 원하는 kubectl 버전에 맞추어 go를 직접 다운로드하여 바이너리를 빌드하는 과정을 거친다. 해당 Go가 잘 실행될 수 있도록 https://go.dev/wiki/MinimumRequirements 홈페이지에서 언급하는 go 최소 요구 사항을 확인하여 Go 개발 환경을 갖추도록 한다.
  • Git: 이미 git 명령어가 실행되는 환경이라면 추가로 고려하지 않아도 된다. 그렇지 않다면 https://git-scm.com/ 등을 참고하여 git를 설치하도록 한다.
  • Make 도구: 역사가 오래된 도구로 (링크: https://www.gnu.org/software/make/manual/make.html), 소스 코드 내 Makefile 파일을 읽어와 바이너리를 빌드하는 도구이다. "make" 명령어가 실행되지 않는다면 개발 환경 운영체제 및 배포판에 따라 설치하는 가이드를 검색하여 준비한다.

빌드 과정

1. 소스코드 준비

Kubernetes - GitHub 저장소에서 소스를 가져와 해당 디렉토리로 이동한다.

$ git clone https://github.com/kubernetes/kubernetes
$ cd kubernetes

 

2. 기존 버전 태그 확인

아래 명령어를 사용하여 최신 릴리스된 Kubernetes 버전부터 이전 버전까지 목록을 확인할 수 있다. 명령어가 종료되지 않고 : 마크가 앞에 나오는 것을 볼 수 있는데, 스페이스바를 누르면 계속 목록을 볼 수 있으며, q 문자를 누르면 빠져나온다.

$ git tag --sort=-taggerdate

 

 

3. 버전 선택 및 체크아웃

빌드하고자 하는 버전 태그를 로컬 환경 변수에 저장하고, 해당 버전에 대한 소스로 변경하기 위해 "git checkout" 커맨드를 사용한다. 여기서는 TAG라는 이름으로 환경 변수를 사용하여 값을 저장한 다음 $TAG 형태로 해당 값을 사용하여 해당 버전에 대한 소스를 체크아웃하였다.

$ TAG=v1.31.2  # 반드시 git tag 목록에 있는 버전을 사용
$ git checkout $TAG

 

4. 커스텀 태그 설정

Kubernetes 에서 제공하는 빌드 방식은 소스에 대한 Git 저장소에 기존 태그가 이미 저장되어 있어 해당 태그 이름을 우선적으로 활용하여 빌드하도록 구성이 되어 있다. 여기서는 해당 방식을 변경하기보다는, 기존 태그 목록을 제거한 후, 현재 체크아웃한 소스에 대해 커스텀 태그를 설정하는 방식을 적용해보자. 이 때, 임의의 문자열을 사용할 수는 없으며 "v{SEMVER}-{CUSTOM}" 형태를 사용한다. SEMVER는 https://semver.org/lang/ko/ 에서 설명하는 버전 번호에 해당하며, {CUSTOM} 부분에 위에서 설명한 목적에 따라 간단한 문자열을 추가해보는 식으로 설정한다.

# 기존 태그 제거
$ git tag | xargs git tag -d
 
# 원격 태그 가져오기 비활성화
$ git config --local fetch.tags false
 
# 새로운 커스텀 태그 생성
$ git tag $TAG-aewstest

 

5. 태그 적용 확인

아래 명령어를 사용하여 태그가 잘 적용되었는지 확인한다.

 

$ git describe --tags --match='v*'

 

6. kubectl 빌드

이제 make 명령어를 사용하여 빌드해보도록 하자. 아래 명령어를 실행한다.

$ make all WHAT=cmd/kubectl GOFLAGS=-v

 

(kubectl 빌드 시작)

 

(kubectl 빌드 끝!)

 

7. 빌드 결과 확인

빌드된 바이너리는 _output 폴더에 생성된다. 빌드한 kubectl 바이너리를 한 번 실행해보자.

$ _output/bin/kubectl version

 

유의 사항

위에서 설명하였듯이, kubectl 버전은 클러스터 버전과 한 단계 차이까지를 허용한다. 테스트 환경에서 사용할 때에는 충분히 검증 후 사용하며, 프로덕션 환경에서는 공식 배포하는 kubectl 바이너리 사용을 권장한다.

 

 

도구 설치

컴퓨터에서 쿠버네티스 도구를 설정한다.

kubernetes.io

마치며

이와 같이 빌드한 kubectl은 원하는 버전 커스팀 문자열을 가질 수 있으며, 해당 문자열을 활용한다면 CI/CD에서 kubectl 버전을 확인하여 클러스터 관리에 도움을 주는 방향으로 활용하는 것도 가능하겠다. 실제 운영 환경에서는 공식 배포판을 사용하는 것이 안전하며, 커스텀 빌드는 테스트나 개발 환경에서 사용하는 것을 권장한다.

 

+ Recent posts