Vấn Đề Lúc 2 Giờ Sáng: Model Nào Vậy Nhỉ?
Tin nhắn đến lúc 2 giờ 17 phút sáng — độ chính xác trên production đã giảm đáng kể sau lần deploy model mới nhất. Phản ứng đầu tiên của tôi là roll back, nhưng có một vấn đề lớn hơn: tôi hoàn toàn không biết phiên bản model nào đang chạy, nó được huấn luyện với hyperparameter nào, hay dataset split nào đã tạo ra nó. Tôi đã chạy hàng chục experiment trên ba máy khác nhau trong hai tuần, đặt tên file kiểu như model_final.pkl, model_final_v2.pkl, và cái tên kinh điển nhất là model_ACTUALLY_final.pkl.
Đêm đó chúng tôi mất bốn tiếng đồng hồ lục lọc Jupyter notebooks, git logs và các file CSV để tái tạo lại thứ lẽ ra chỉ cần tra cứu trong năm phút. Nếu bạn đã từng đưa dự án machine learning lên production mà không có experiment tracking bài bản, bạn chắc chắn hiểu cảm giác này. Chính sự cố đó đã thúc đẩy tôi thiết lập MLflow đúng cách — và theo kinh nghiệm thực tế của tôi, đây là một trong những kỹ năng cốt lõi cần nắm vững trước khi bạn đụng vào bất cứ thứ gì giống với hệ thống ML production.
MLflow Thực Sự Làm Gì Trước Khi Ta Dùng Docker
MLflow là nền tảng mã nguồn mở để quản lý toàn bộ vòng đời ML. Không có gì thần bí — đây là hệ thống sổ sách có cấu trúc cho model và experiment của bạn. Nền tảng gồm bốn thành phần:
- Tracking Server — ghi lại tham số, metrics và artifacts (file model, biểu đồ) cho mỗi lần training
- Model Registry — kho lưu trữ trung tâm để quản lý phiên bản, phân giai đoạn và thăng cấp model (Staging → Production → Archived)
- MLflow Models — định dạng đóng gói chuẩn hoạt động với scikit-learn, PyTorch, TensorFlow và các framework khác
- Projects — đóng gói code training có thể tái tạo (ít được dùng hơn, nhưng hữu ích cho nhóm)
Với hầu hết các nhóm, chỉ Tracking Server và Model Registry đã giải quyết được 90% sự hỗn loạn. Đó là thứ chúng ta sẽ deploy hôm nay — với Docker Compose để setup có thể tái tạo và không bị hỏng trên máy tính của ai đó.
Thiết Lập MLflow với Docker Compose
Chúng ta sẽ chạy MLflow với backend là database PostgreSQL (để metadata của các run tồn tại sau khi container restart) và MinIO làm artifact store tương thích S3 (để file model không nằm trong filesystem của container).
Cấu Trúc Thư Mục
mlflow-docker/
├── docker-compose.yml
├── .env
└── mlflow/
└── Dockerfile
File .env
# .env
POSTGRES_USER=mlflow
POSTGRES_PASSWORD=mlflow_secret
POSTGRES_DB=mlflow
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_BUCKET=mlflow-artifacts
MLFLOW_S3_ENDPOINT_URL=http://minio:9000
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
docker-compose.yml
version: "3.8"
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"]
interval: 10s
retries: 5
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
minio-init:
image: minio/mc:latest
depends_on:
- minio
entrypoint: >
/bin/sh -c "
sleep 5;
mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD};
mc mb local/${MINIO_BUCKET} --ignore-existing;
exit 0;
"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_BUCKET: ${MINIO_BUCKET}
mlflow:
image: ghcr.io/mlflow/mlflow:v2.13.0
ports:
- "5000:5000"
environment:
MLFLOW_S3_ENDPOINT_URL: ${MLFLOW_S3_ENDPOINT_URL}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
command: >
mlflow server
--backend-store-uri postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres/${POSTGRES_DB}
--default-artifact-root s3://${MINIO_BUCKET}/
--host 0.0.0.0
--port 5000
depends_on:
postgres:
condition: service_healthy
minio-init:
condition: service_completed_successfully
volumes:
postgres_data:
minio_data:
Khởi Động Stack
docker compose up -d
# Kiểm tra mọi thứ đều hoạt động bình thường
docker compose ps
# MLflow UI → http://localhost:5000
# MinIO Console → http://localhost:9001
Chờ khoảng 30 giây khi khởi động lần đầu trong khi PostgreSQL khởi tạo và MinIO tạo bucket. Khi container mlflow hiển thị trạng thái healthy, giao diện UI đã có thể truy cập tại cổng 5000.
Ghi Lại Experiment Đầu Tiên
Trên máy local (hoặc bất cứ nơi nào code training chạy), cài đặt MLflow client và boto3 để upload artifacts lên S3:
pip install mlflow boto3
Theo Dõi Experiment Cơ Bản
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import os
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://localhost:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minioadmin"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minioadmin"
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("iris-classification")
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
for n_estimators in [50, 100, 200]:
with mlflow.start_run(run_name=f"rf-{n_estimators}-trees"):
model = RandomForestClassifier(n_estimators=n_estimators, random_state=42)
model.fit(X_train, y_train)
accuracy = accuracy_score(y_test, model.predict(X_test))
# Ghi lại tham số và metrics
mlflow.log_param("n_estimators", n_estimators)
mlflow.log_metric("accuracy", accuracy)
# Ghi lại bản thân model như một artifact
mlflow.sklearn.log_model(
sk_model=model,
artifact_path="model",
registered_model_name="iris-random-forest"
)
print(f"n_estimators={n_estimators} → accuracy={accuracy:.4f}")
Chạy script này, sau đó mở http://localhost:5000. Bạn sẽ thấy ba run trong experiment iris-classification, mỗi run có tham số, metrics và model artifacts được lưu trong MinIO. Click vào bất kỳ run nào để xem toàn bộ lineage — thông tin dataset, phiên bản code, môi trường, tất cả những gì cần để tái tạo lại.
Quản Lý Model Registry
Ba run ở trên đã tự động đăng ký model thông qua registered_model_name. Bây giờ hãy quản lý các phiên bản theo giai đoạn — đây chính là thứ biến việc rollback thành tác vụ 30 giây thay vì cơn ác mộng kéo dài 4 tiếng.
Thăng Cấp Model Qua Các Giai Đoạn
from mlflow.tracking import MlflowClient
client = MlflowClient(tracking_uri="http://localhost:5000")
# Liệt kê tất cả phiên bản của model
versions = client.search_model_versions("name='iris-random-forest'")
for v in versions:
print(f"Version {v.version} — run_id={v.run_id} — stage={v.current_stage}")
# Chuyển phiên bản có hiệu suất tốt nhất lên Staging
client.transition_model_version_stage(
name="iris-random-forest",
version="3", # phiên bản 200 cây quyết định
stage="Staging"
)
# Sau khi kiểm tra thành công, chuyển sang Production
client.transition_model_version_stage(
name="iris-random-forest",
version="3",
stage="Production"
)
# Lưu trữ model production cũ
client.transition_model_version_stage(
name="iris-random-forest",
version="1",
stage="Archived"
)
Tải Model Production để Inference
import mlflow.sklearn
import os
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://localhost:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minioadmin"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minioadmin"
mlflow.set_tracking_uri("http://localhost:5000")
# Luôn tải phiên bản đang được gắn tag Production
model = mlflow.sklearn.load_model("models:/iris-random-forest/Production")
sample = [[5.1, 3.5, 1.4, 0.2]]
print(model.predict(sample)) # [0]
Phần quan trọng ở đây là models:/iris-random-forest/Production — code inference của bạn không bao giờ hardcode số phiên bản. Khi bạn thăng cấp version 4 lên Production, lần gọi prediction tiếp theo sẽ tự động sử dụng nó. Rollback cũng đơn giản tương tự: chỉ cần chuyển phiên bản cũ hơn về Production.
Những Gì Bạn Đã Có
Tại thời điểm này, stack cung cấp cho bạn một MLOps backbone hoàn toàn tự host: metadata experiment trong PostgreSQL (tồn tại sau restart, có thể query), model artifacts trong MinIO (tương thích S3, có thể mở rộng), và Model Registry gọn gàng với các giai đoạn thăng cấp ánh xạ trực tiếp vào pipeline deployment của bạn. Mỗi lần training đều có thể kiểm toán — ai đã train, code gì, data split nào, metrics như thế nào.
Lần tới khi có sự cố lúc 2 giờ sáng, bạn mở MLflow UI, lọc theo tên model, kiểm tra phiên bản nào đang ở Production, so sánh metrics training của nó với phiên bản trước, và roll back trong chưa đầy hai phút. Đó là sự khác biệt giữa một sự cố có thể xử lý được và một đám cháy kéo dài nhiều tiếng đồng hồ.
Đối với nhóm, bước tiếp theo tự nhiên là kết nối CI/CD pipeline để tự động đăng ký model sau các lần training thành công và đặt điều kiện thăng cấp lên Production dựa trên ngưỡng metrics — nhưng nền tảng bạn xây dựng ở đây xử lý điều đó rất gọn. Hạ tầng đã sẵn sàng; chính sách workflow là do bạn định nghĩa.

