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:

  1. Kiểm tra mã nguồn Python (lint, unit test).
  2. Xây dựng image Docker chứa môi trường chạy mô hình.
  3. Chạy các pipeline kiểm thử mô hình trên dữ liệu mẫu (validation).
  4. Đẩy image lên registry.
  5. 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_TAG giú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 aptpip sau 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ư Trivy trong 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.yml hoặ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.