Giới thiệu
Trong môi trường phát triển Machine Learning (ML), việc đưa mô hình từ giai đoạn nghiên cứu tới production đòi hỏi quy trình tự động hoá chặt chẽ để giảm thiểu lỗi, tăng tốc độ triển khai và đảm bảo tính tái lặp. CI/CD (Continuous Integration / Continuous Deployment) đã trở thành tiêu chuẩn trong phát triển phần mềm, nhưng áp dụng cho ML có những thách thức riêng: quản lý dữ liệu lớn, phụ thuộc môi trường Python, và việc kiểm thử mô hình không chỉ là unit test mà còn là validation trên dữ liệu thực tế. Bài viết sẽ hướng dẫn chi tiết cách xây dựng một pipeline CI/CD cho dự án ML bằng Docker và GitLab CI, từ việc chuẩn bị môi trường Docker, viết Dockerfile tối ưu, cấu hình .gitlab-ci.yml, tới việc kiểm thử và triển khai mô hình.
Khái niệm cơ bản
CI/CD cho Machine Learning
CI/CD cho ML (thường được gọi là MLOps) bao gồm các bước:
- Kiểm tra mã nguồn Python (lint, unit test).
- Xây dựng image Docker chứa môi trường chạy mô hình.
- Chạy các pipeline kiểm thử mô hình trên dữ liệu mẫu (validation).
- Đẩy image lên registry.
- Triển khai image lên môi trường serving (Docker Compose, Kubernetes, …).
Mục tiêu là mọi thay đổi trong repository đều được kiểm tra tự động và nếu vượt qua, sẽ được triển khai mà không cần can thiệp thủ công.
Chuẩn bị môi trường Docker cho Machine Learning
Dockerfile chuẩn cho Python + scikit‑learn
Dockerfile dưới đây sử dụng python:3.11-slim làm base image, cài đặt pip và các thư viện thường dùng trong dự án ML như numpy, pandas, scikit-learn, joblib. Để giảm dung lượng image, chúng ta xóa cache apt và các file tạm.
# syntax=docker/dockerfile:1
FROM python:3.11-slim AS base
# Thiết lập biến môi trường để Python không ghi ra buffer
ENV PYTHONUNBUFFERED=1
# Cài đặt các gói hệ thống cần thiết (git, build‑essential) – chỉ trong stage build
RUN apt-get update \
&& apt-get install -y --no-install-recommends git build-essential \
&& rm -rf /var/lib/apt/lists/*
# Tạo thư mục làm việc
WORKDIR /app
# Sao chép file requirements.txt và cài đặt phụ thuộc
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Sao chép toàn bộ mã nguồn (chỉ trong stage final)
COPY . .
# Đặt lệnh khởi chạy mặc định – chạy API FastAPI để phục vụ mô hình
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
File requirements.txt mẫu:
numpy==1.26.2 pandas==2.1.3 scikit-learn==1.3.2 joblib==1.3.2 fastapi==0.103.0 uvicorn[standard]==0.23.2
Với cấu trúc đa stage như trên, chúng ta có thể tách phần build (cài gcc, git) ra khỏi image production, giảm kích thước cuối cùng xuống dưới 150 MB.
Xây dựng pipeline với GitLab CI
File .gitlab-ci.yml mẫu
GitLab CI cho phép định nghĩa các job trong các stage: lint, test, build, push, deploy. Dưới đây là một cấu hình đầy đủ, sử dụng Docker-in-Docker (DinD) để xây dựng image trong runner.
stages:
- lint
- test
- build
- push
- deploy
variables:
# Sử dụng Docker daemon trong runner
DOCKER_HOST: tcp://docker:2375/
DOCKER_TLS_CERTDIR: ""
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
# Job lint – kiểm tra mã Python bằng flake8
lint:
stage: lint
image: python:3.11-slim
script:
- pip install flake8
- flake8 .
# Job test – chạy pytest, bao gồm kiểm thử mô hình trên dữ liệu mẫu
test:
stage: test
image: python:3.11-slim
services:
- name: docker:dind
alias: docker
script:
- pip install -r requirements.txt pytest
- pytest tests/ --maxfail=1 --disable-warnings
# Job build – tạo Docker image
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker build -t $IMAGE_TAG .
# Job push – đẩy image lên GitLab Container Registry
push:
stage: push
image: docker:latest
services:
- docker:dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker push $IMAGE_TAG
# Job deploy – triển khai lên server test bằng SSH và Docker Compose
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client bash
- mkdir -p ~/.ssh && echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts
script:
- ssh $DEPLOY_USER@$DEPLOY_HOST "docker pull $IMAGE_TAG && cd /opt/ml_service && docker-compose up -d"
only:
- main
Giải thích một số điểm quan trọng:
- DinD: Cần để runner có thể chạy Docker commands.
- Biến môi trường
IMAGE_TAGgiúp gắn commit SHA vào tag, dễ trace. - Deploy sử dụng SSH để pull image và khởi chạy lại service bằng Docker Compose.
Quản lý dữ liệu và mô hình trong Docker
Volume và cache cho dữ liệu lớn
Trong quá trình training, dữ liệu thường được lưu trên host hoặc trong volume riêng để tránh việc tải lại mỗi lần container khởi động. Ví dụ, tạo volume ml_data để lưu dataset:
docker volume create ml_data docker run -v ml_data:/data -v $(pwd):/app -w /app my-ml-image python train.py
Đối với mô hình đã được train, chúng ta có thể lưu vào volume ml_models và mount vào container serving:
docker volume create ml_models docker run -d -p 8000:8000 \ -v ml_models:/app/models \ my-ml-image
Việc tách dữ liệu và mô hình ra các volume giúp tái sử dụng khi cập nhật code mà không cần tải lại dataset.
Kiểm thử mô hình trong CI
Unit test và integration test cho pipeline
Kiểm thử mô hình không chỉ là kiểm tra hàm Python mà còn là kiểm tra đầu ra trên một tập dữ liệu mẫu. Dưới đây là một ví dụ pytest để đánh giá độ chính xác (accuracy) của mô hình lưu trong models/model.joblib:
import joblib
import pandas as pd
from sklearn.metrics import accuracy_score
def test_model_accuracy():
# Load mô hình đã train
model = joblib.load('models/model.joblib')
# Load dữ liệu test mẫu (được commit trong repo)
df = pd.read_csv('tests/data/sample_test.csv')
X = df.drop('target', axis=1)
y_true = df['target']
y_pred = model.predict(X)
acc = accuracy_score(y_true, y_pred)
# Đảm bảo accuracy >= 0.85 cho môi trường CI
assert acc >= 0.85, f'Accuracy {acc:.2f} thấp hơn ngưỡng cho phép'
Khi job test trong .gitlab-ci.yml chạy, nếu độ chính xác không đạt ngưỡng, pipeline sẽ dừng và không đẩy image lên registry.
Triển khai mô hình lên môi trường production
Docker Compose cho service FastAPI
Để phục vụ mô hình, chúng ta thường dùng FastAPI + Uvicorn. Dưới đây là file docker-compose.yml mô tả service API và một service Redis cache (để lưu kết quả inference tạm thời).
version: '3.8'
services:
api:
image: $CI_REGISTRY_IMAGE:latest
container_name: ml_api
restart: always
ports:
- "8000:8000"
environment:
- REDIS_HOST=redis
depends_on:
- redis
volumes:
- ml_models:/app/models
redis:
image: redis:7-alpine
container_name: ml_redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
ml_models:
redis_data:
Sau khi pipeline push hoàn tất, job deploy thực hiện lệnh docker-compose up -d trên server, tự động khởi động lại service với image mới.
Best practices và lưu ý bảo mật
- Giữ Dockerfile tối thiểu: Chỉ cài các gói cần thiết, xóa cache
aptvàpipsau khi cài. - Sử dụng multi‑stage build để tách phần build (cài gcc) ra khỏi image production.
- Quản lý secret bằng GitLab CI variables (ví dụ
CI_REGISTRY_PASSWORD,SSH_PRIVATE_KEY) thay vì hard‑code trong file. - Quét lỗ hổng image bằng công cụ như
Trivytrong một job riêng trước khi đẩy lên registry. - Giới hạn tài nguyên cho container (CPU, memory) trong
docker-compose.ymlhoặc Kubernetes để tránh quá tải server. - Phiên bản mô hình: Gắn tag version vào tên file mô hình (ví dụ
model_v1.joblib) và lưu metadata trong một file JSON để trace.
Kết luận
Việc thiết lập một pipeline CI/CD cho dự án Machine Learning không chỉ giúp tự động hoá quá trình kiểm thử và triển khai, mà còn tạo ra một môi trường reproducible, giảm thiểu rủi ro khi đưa mô hình vào production. Bằng cách kết hợp Docker để chuẩn hoá môi trường và GitLab CI để điều phối các job, chúng ta có thể xây dựng một quy trình MLOps mạnh mẽ, linh hoạt và dễ bảo trì. Đối với những người mới bắt đầu hoặc muốn nâng cao kỹ năng Docker trong bối cảnh AI, Tham khảo khóa học "Làm chủ Docker từ cơ bản đến nâng cao" tại đây để có nền tảng vững chắc hơn.







