Danh sách bài viết

Bài 44: CI/CD cho ML — GitHub Actions cho pipeline test/train/deploy

CI/CD cho ML khác CI/CD truyền thống: phải train model chậm, test data, deploy artifact ngoài code. Bài này hướng dẫn xây GitHub Actions workflow cho lint, unit test, train, eval, so sánh baseline, build Docker image và deploy — kèm pattern thực tế, GPU runner, CML, matrix strategy, và cảnh báo chi phí.

27/05/2026
3 lượt xem
1

Mục tiêu bài học

Sau bài này bạn sẽ:

  • ✅ Hiểu tại sao CI/CD cho ML cần xử lý thêm 3 vấn đề mà CI/CD thông thường không có
  • ✅ Viết được GitHub Actions workflow cho CI (lint + test) và tách riêng workflow train
  • ✅ Tích hợp DVC để pull data và chạy pipeline trong Actions
  • ✅ Dùng CML để comment metric report lên PR tự động
  • ✅ Build và push Docker image lên GHCR khi tag release
  • ✅ Biết khi nào nên dùng GPU runner, self-hosted runner
  • ✅ Tránh được các pitfall phổ biến về chi phí, secret leak, và cache
2

CI/CD recap cho người chưa có background

CI (Continuous Integration): mỗi khi developer push code hoặc mở Pull Request, một pipeline tự động chạy — thường gồm lint, unit test, build. Mục đích là phát hiện lỗi sớm trước khi code merge vào nhánh chính.

CD (Continuous Delivery / Deployment): sau khi CI pass, tự động deploy lên môi trường staging hoặc production. Continuous Delivery dừng ở bước "sẵn sàng để deploy, cần human approve". Continuous Deployment đi thêm một bước: tự deploy không cần approve.

GitHub Actions là CI/CD platform tích hợp sẵn trong GitHub. Workflow được định nghĩa bằng file YAML đặt trong thư mục .github/workflows/. Mỗi workflow có thể có nhiều job, mỗi job chạy trên một máy ảo riêng (runner) và gồm nhiều step.

Cấu trúc cơ bản của một workflow file:

name: Tên workflow

on:             # Trigger: khi nào chạy
  push:
    branches: [main]

jobs:
  job-name:
    runs-on: ubuntu-latest    # loại runner
    steps:
      - name: Tên step
        run: lệnh shell       # hoặc uses: action@version

Actions có thể là lệnh shell thuần (run:) hoặc dùng action có sẵn từ Marketplace (uses:). Ví dụ actions/checkout@v4 clone repo về runner, actions/setup-python@v5 cài Python.

3

CI/CD cho ML — 3 đặc thù so với phần mềm thông thường

1. Artifact của CI/CD ML gồm cả model và data

CI/CD truyền thống deploy code (binary, container). CI/CD cho ML cần deploy code + model artifact + config, đôi khi cả data version. Model là một binary lớn không phù hợp lưu trong git — cần tool riêng như DVC (bài 43) hoặc MLflow Model Registry (bài 42).

2. Train rất chậm, không thể chạy mỗi PR

Build Docker image mất vài phút. Train một model thực tế có thể mất vài giờ đến vài ngày. Nếu trigger train mỗi lần push code, chi phí compute sẽ mất kiểm soát và queue sẽ bị tắc nghẽn.

Cách xử lý thực tế: tách workflow thành 2 loại —

  • CI workflow: chạy mỗi push/PR — lint, unit test, validate data schema. Chạy nhanh (dưới 5 phút).
  • Train workflow: chỉ trigger khi cần — workflow_dispatch (bấm tay), schedule (cron), hoặc khi merge vào nhánh đặc biệt.

3. "Test pass" chưa đủ — cần so sánh metric với baseline

Code có thể compile và test pass, nhưng model mới có thể có accuracy thấp hơn model hiện tại ở production. Cần bước evaluation gate: so sánh metric của model mới với baseline, chỉ promote nếu vượt ngưỡng.

Ví dụ: val_accuracy >= 0.85val_accuracy >= current_production_accuracy - 0.02 (cho phép lệch 2% để tránh loại model tốt do random seed).

4

Workflow tổng thể của một ML pipeline

Nhìn từ trên cao, pipeline CI/CD cho ML điển hình gồm các bước sau:

Push / PR
    │
    ▼
Lint + Unit test (chạy mọi PR)
    │
    ▼ (nếu merge vào main hoặc trigger thủ công)
Validate data (schema, missing, distribution)
    │
    ▼
Train (chậm — có thể chạy trên GPU runner)
    │
    ▼
Evaluate — so sánh metric với baseline
    │
    ▼ (nếu pass evaluation gate)
Register model vào Model Registry (MLflow, W&B)
    │
    ▼
Build Docker image + push lên registry
    │
    ▼
Deploy (Render hook, K8s rollout, ...)

Không nhất thiết phải implement hết ngay từ đầu. Phần lớn team bắt đầu với CI workflow đơn giản (lint + test), thêm train workflow sau khi có Model Registry, rồi tích hợp deploy tự động khi cần.

5

GitHub Actions cơ bản — CI workflow

File .github/workflows/ci.yml — chạy mỗi push và PR:

name: CI

on:
  push:
    branches: [main, dev]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"          # cache pip packages theo requirements

      - run: pip install -r requirements.txt -r requirements-dev.txt

      - run: ruff check .       # lint

      - run: pytest --cov=src tests/

Một vài điểm cần chú ý:

  • actions/checkout@v4: luôn pin version (v4) để tránh breaking change từ action author.
  • cache: "pip" trong setup-python: tự động cache ~/.cache/pip theo nội dung requirements*.txt. Giảm thời gian install từ ~2 phút xuống còn vài giây khi cache hit.
  • Tách requirements.txt (production deps) và requirements-dev.txt (pytest, ruff, mypy, ...) để không đưa dev tool vào Docker image.
  • ruff check .: ruff (Rust-based) nhanh hơn flake8/pylint đáng kể — phù hợp cho CI.

Thêm bước validate data schema

Nếu repo chứa script ingest/validate data, có thể thêm vào CI:

      - name: Validate data schema
        run: python scripts/validate_data.py --data-dir data/raw

Script này kiểm tra: cột có đúng không, kiểu dữ liệu có đúng không, không có toàn NaN trong cột quan trọng. Dùng pandera hoặc great_expectations để viết schema validation dễ maintain hơn assert thủ công.

6

Workflow train và eval

File .github/workflows/train.yml — tách riêng, chỉ trigger khi cần:

name: Train Model

on:
  workflow_dispatch:        # bấm tay từ UI hoặc gh CLI
  schedule:
    - cron: "0 2 * * 1"     # mỗi thứ 2, 2h sáng UTC

jobs:
  train:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - run: pip install -r requirements.txt

      # Pull data từ DVC remote (S3, GCS, ...)
      - uses: iterative/setup-dvc@v1

      - name: Pull data
        run: |
          dvc remote modify storage --local \
            access_key_id ${{ secrets.AWS_KEY }}
          dvc remote modify storage --local \
            secret_access_key ${{ secrets.AWS_SECRET }}
          dvc pull

      # Chạy pipeline DVC (train + eval steps)
      - name: Train
        run: dvc repro

      # Kiểm tra metric pass ngưỡng
      - name: Check metrics
        run: |
          python scripts/check_metrics.py \
            --metric val_accuracy \
            --threshold 0.85

      # Register model vào MLflow nếu pass
      - name: Register model
        run: |
          python scripts/register_model.py \
            --model-name iris-classifier \
            --run-id $(cat run_id.txt)
        env:
          MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_URI }}

Ví dụ script check_metrics.py đơn giản:

import argparse
import json
import sys

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--metric", required=True)
    parser.add_argument("--threshold", type=float, required=True)
    args = parser.parse_args()

    # DVC lưu metrics vào metrics.json sau khi chạy dvc repro
    with open("metrics.json") as f:
        metrics = json.load(f)

    value = metrics[args.metric]
    print(f"{args.metric} = {value:.4f} (threshold = {args.threshold})")

    if value < args.threshold:
        print(f"FAIL: {value:.4f} < {args.threshold}")
        sys.exit(1)

    print("PASS")

if __name__ == "__main__":
    main()

Script exit code 1 làm job fail, GitHub Actions sẽ đánh dấu workflow failed và không chạy bước register model tiếp theo.

7

GPU runner và self-hosted runner

Runner mặc định (ubuntu-latest) là máy ảo CPU. Không có GPU.

GitHub-hosted GPU runner (paid)

GitHub ra mắt GPU runner từ 2024 cho GitHub Teams/Enterprise. Label: ubuntu-22.04-gpu. Specs: NVIDIA T4 GPU, 4 vCPU, 28 GB RAM. Giá tính theo phút, xem pricing tại docs.github.com.

jobs:
  train-gpu:
    runs-on: ubuntu-22.04-gpu
    steps:
      - uses: actions/checkout@v4
      - run: nvidia-smi         # kiểm tra GPU available
      - run: python train.py --device cuda

Self-hosted runner

Nếu đã có GPU machine riêng (on-premise hoặc cloud VM), có thể register nó làm runner của repo:

  1. Repo Settings → Actions → Runners → New self-hosted runner.
  2. Chạy script cài đặt trên máy GPU.
  3. Runner tự kết nối về GitHub, nhận job khi có workflow trigger.
jobs:
  train:
    runs-on: [self-hosted, gpu]    # label tự đặt khi setup runner
    steps:
      - uses: actions/checkout@v4
      - run: python train.py --device cuda

Self-hosted runner phù hợp khi: cần GPU thường xuyên (cost thấp hơn cloud runner), cần access network nội bộ (data không ra ngoài internet), hoặc cần môi trường đặc biệt (CUDA version cụ thể).

Lưu ý bảo mật: không dùng self-hosted runner cho public repo — PR từ fork có thể trigger job và chạy code tùy ý trên máy của bạn.

8

Secret management

Workflow cần nhiều credential: AWS key để pull data từ S3, MLflow URI, API key deploy. Những thông tin này không được hard-code trong file yml.

GitHub Secrets

Thêm secret tại: Repo Settings → Secrets and variables → Actions → New repository secret.

Dùng trong workflow:

env:
  MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_URI }}
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

GitHub tự động mask giá trị secret trong log — nếu secret xuất hiện trong output, nó sẽ bị thay bằng ***. Tuy nhiên cơ chế mask không hoàn hảo: nếu secret bị encode (base64, URL encode) hoặc bị split qua nhiều dòng, có thể không mask được.

Quy tắc cứng

  • Không echo $SECRET ra stdout — nếu cần debug, dùng hash: echo $SECRET | sha256sum.
  • Không dùng set -x (trace all commands) khi có secret trong env.
  • Secret chỉ available cho jobs trong cùng repo, không truyền qua fork PR (theo mặc định).

Organization-level secrets

Nếu nhiều repo dùng chung credential (ví dụ: cùng AWS account, cùng MLflow server), dùng Organization Secrets thay vì add vào từng repo: Settings → Secrets → Organization secrets.

Environment secrets

GitHub hỗ trợ deployment environments (staging, production) với secret riêng và require-approval gate. Job phải được approve trước khi chạy và trước khi nhận secret của environment đó.

jobs:
  deploy-prod:
    environment: production     # sẽ trigger approval gate nếu đã cấu hình
    runs-on: ubuntu-latest
    steps:
      - run: deploy-to-prod.sh
        env:
          PROD_API_KEY: ${{ secrets.PROD_API_KEY }}
9

Build và deploy Docker image

File .github/workflows/release.yml — trigger khi push tag dạng v1.2.3:

name: Build & Deploy

on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}    # token tự động, không cần tạo thủ công

      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
          cache-from: type=gha          # dùng GitHub Actions cache cho Docker layer
          cache-to: type=gha,mode=max

  deploy:
    needs: build                        # chỉ chạy sau khi build xong
    runs-on: ubuntu-latest
    steps:
      - name: Trigger deploy hook
        run: |
          curl -X POST "${{ secrets.RENDER_DEPLOY_HOOK }}"

Giải thích một số chi tiết:

  • secrets.GITHUB_TOKEN: GitHub tự inject token này vào mọi workflow, có quyền push image lên GHCR (GitHub Container Registry) của repo. Không cần tạo Personal Access Token riêng.
  • cache-from: type=gha: lưu Docker layer cache vào GitHub Actions cache storage, dùng lại ở lần build sau. Giảm build time đáng kể khi chỉ thay đổi code app (layers base image và pip install không rebuild).
  • needs: build: job deploy chỉ chạy khi job build thành công. Nếu build fail, deploy không trigger.
  • Deploy hook của Render/Railway là URL dạng https://api.render.com/deploy/srv-xxx?key=yyy — POST vào đó là đủ để trigger redeploy với image mới nhất.

Tag image nhiều tag cùng lúc

Thường muốn tag cả v1.2.3 lẫn latest:

      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=raw,value=latest

      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
10

CML — Continuous Machine Learning

CML (Continuous Machine Learning) là tool mã nguồn mở của Iterative (cùng nhóm làm DVC). CML cho phép workflow tự comment kết quả train lên PR dưới dạng report gồm text, bảng metric, và biểu đồ.

      - uses: iterative/setup-cml@v2

      - name: Generate and post report
        run: |
          echo "## Model Metrics" >> report.md
          cat metrics.json | python -m json.tool >> report.md
          echo "" >> report.md
          echo "## Training Curve" >> report.md
          cml asset publish plots/loss.png --md >> report.md
          cml comment create report.md
        env:
          REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Kết quả: mỗi khi workflow train chạy xong, bot tự comment lên PR (hoặc commit) với nội dung như:

## Model Metrics
{
  "val_accuracy": 0.8712,
  "val_loss": 0.4231
}

## Training Curve
[biểu đồ loss curve được nhúng vào comment]

Reviewer có thể thấy ngay model mới đạt accuracy bao nhiêu so với run trước, không cần tự pull artifact về chạy. Đặc biệt hữu ích khi team có nhiều người review PR model thay đổi.

cml asset publish upload file ảnh lên GitHub và trả về markdown image link. cml comment create post comment lên PR/commit hiện tại.

So sánh với baseline tự động

Có thể mở rộng report để so sánh với baseline (metric của model đang ở production):

# scripts/generate_report.py
import json

with open("metrics.json") as f:
    current = json.load(f)

with open("baseline_metrics.json") as f:
    baseline = json.load(f)

delta = current["val_accuracy"] - baseline["val_accuracy"]
sign = "+" if delta >= 0 else ""

print(f"## Evaluation Report\n")
print(f"| Metric | Baseline | Current | Delta |")
print(f"|--------|----------|---------|-------|")
print(f"| val_accuracy | {baseline['val_accuracy']:.4f} | {current['val_accuracy']:.4f} | {sign}{delta:.4f} |")
11

PR-driven ML workflow

Pattern phổ biến trong team ML:

  1. Dev tạo nhánh, thay đổi code train / feature engineering / hyperparameter.
  2. Mở PR → CI chạy: lint, test, train mini (dùng 10% data để kiểm tra pipeline không bị lỗi, không để đánh giá chất lượng).
  3. CML bot comment metric của mini-run lên PR.
  4. Reviewer thấy pipeline không crash, code sạch → approve.
  5. Merge vào main → trigger full train workflow (toàn bộ data).
  6. Full train xong → evaluation gate → nếu pass → register model → deploy.

Train mini trên PR có thể cấu hình qua environment variable:

      - name: Train (mini on PR)
        run: |
          python train.py \
            --max-samples 1000 \
            --epochs 2
        if: github.event_name == 'pull_request'

      - name: Train (full on main)
        run: python train.py
        if: github.ref == 'refs/heads/main'

if: condition cho phép step chỉ chạy theo điều kiện — rất hữu ích để phân biệt hành vi trên PR và trên main.

12

Matrix strategy — train nhiều config song song

GitHub Actions cho phép chạy nhiều job song song với cấu hình khác nhau thông qua strategy.matrix:

jobs:
  hyperparameter-search:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        lr: [0.001, 0.0001]
        batch_size: [32, 64]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: |
          python train.py \
            --lr ${{ matrix.lr }} \
            --batch-size ${{ matrix.batch_size }} \
            --run-name "lr=${{ matrix.lr }}_bs=${{ matrix.batch_size }}"
        env:
          MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_URI }}

Với 2×2 matrix, GitHub spin up 4 job chạy song song. Mỗi job nhận một tổ hợp (lr, batch_size) khác nhau. Kết quả 4 run được log lên MLflow để so sánh. Tổng thời gian gần bằng thời gian của 1 run thay vì 4 run tuần tự.

Chú ý: 4 job song song = 4 runner đồng thời. GitHub Free tier giới hạn concurrent jobs (20 jobs với public repo, ít hơn với private). Tổng phút cũng tính gộp: 4 job × 30 phút = 120 phút tiêu thụ.

Matrix với fail-fast

Mặc định, nếu một job trong matrix fail, GitHub hủy các job còn lại (fail-fast: true). Để chạy hết tất cả dù có job fail:

    strategy:
      fail-fast: false
      matrix:
        lr: [0.001, 0.0001]
        batch_size: [32, 64]
13

Caching để tăng tốc

Runner GitHub Actions là máy ảo sạch mỗi lần chạy — không giữ lại gì giữa các run. Cache là cơ chế lưu thư mục vào storage, restore lại ở run tiếp theo nếu cache key khớp.

Pip cache

actions/setup-python@v5 với cache: "pip" tự xử lý cache ~/.cache/pip. Key tự động dựa trên hash của requirements*.txt. Không cần cấu hình thêm.

Docker layer cache

Dùng cache-from: type=gha trong docker/build-push-action như đã thấy ở bước 9. Quan trọng: sắp xếp Dockerfile để layers thay đổi ít (base image, pip install) đứng trước layers thay đổi nhiều (copy source code).

HuggingFace model cache

Nếu workflow download model từ HuggingFace Hub, cache lại để tránh download mỗi lần:

      - uses: actions/cache@v4
        with:
          path: ~/.cache/huggingface
          key: hf-${{ hashFiles('requirements.txt') }}
          restore-keys: |
            hf-

      - run: python scripts/download_model.py
        env:
          HF_TOKEN: ${{ secrets.HF_TOKEN }}

Key dùng hash của requirements.txt: khi đổi version transformers/torch thì cache miss → download lại. restore-keys: hf- là fallback: nếu không có exact match, dùng cache cũ nhất có prefix hf- (vẫn có ích hơn download từ đầu).

DVC cache

DVC remote (S3, GCS) bản thân đã là persistent storage. Không cần thêm Actions cache cho DVC artifacts — dvc pull chỉ download file chưa có ở local và đã thay đổi.

Giới hạn cache

GitHub Actions cache tối đa 10 GB per repo (GitHub Free). Cache entry hết hạn sau 7 ngày không dùng. Nếu hết quota, cache cũ nhất bị xóa tự động.

14

Trigger pattern

Tổng hợp các trigger phổ biến và use case tương ứng:

Trigger Cú pháp Use case
Push to main push: branches: [main] Deploy to production sau khi merge
Push to staging push: branches: [staging] Deploy to staging environment
Pull request pull_request: branches: [main] CI: lint, test, mini-train trên PR
Tag release push: tags: ['v*'] Build Docker image, tạo GitHub Release
Cron schedule schedule: - cron: "0 2 * * 1" Retrain định kỳ (hàng tuần, hàng ngày)
Manual dispatch workflow_dispatch: Train thủ công, trigger từ UI hoặc gh workflow run
Manual với input workflow_dispatch: inputs: ... Train với hyperparameter tùy chọn

workflow_dispatch với input

on:
  workflow_dispatch:
    inputs:
      model_name:
        description: "Tên model để train"
        required: true
        default: "resnet50"
      epochs:
        description: "Số epoch"
        required: false
        default: "10"
        type: number

jobs:
  train:
    runs-on: ubuntu-latest
    steps:
      - run: |
          python train.py \
            --model ${{ inputs.model_name }} \
            --epochs ${{ inputs.epochs }}

Khi trigger từ GitHub UI, người dùng được hỏi giá trị cho các input trước khi workflow chạy. Trigger từ CLI: gh workflow run train.yml -f model_name=efficientnet -f epochs=20.

15

Chi phí và cảnh báo

GitHub Actions Free tier:

  • Public repo: unlimited phút.
  • Private repo: 2000 phút/tháng (Teams: 3000 phút, Enterprise: 50000 phút).
  • Storage: 500 MB artifact + 500 MB cache (Free).

Nhân hệ số: Linux runner tính ×1, Windows runner ×2, macOS runner ×10. Phần lớn ML workflow chạy Linux nên tính ×1.

Train job 1 giờ × 30 lần/tháng = 1800 phút — gần hết quota Free tier. Vì vậy:

  • Chỉ trigger train khi thực sự cần: workflow_dispatch hoặc schedule, không trigger mỗi push.
  • Dùng concurrency: để hủy run đang chờ nếu có run mới hơn:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Với cấu hình này, nếu bạn push 3 lần liên tiếp vào cùng một nhánh, chỉ run cuối cùng được thực thi — 2 run trước bị cancel.

GPU runner paid được tính theo phút ×GPU multiplier. Một T4 GPU runner (~$0.07/phút theo giá 2024) chạy 1 tiếng = ~$4.2. Cần theo dõi qua Settings → Billing → Actions.

16

Common pitfalls

Trigger train mỗi push

Nếu workflow train có on: push: branches: [main], mỗi merge vào main đều chạy train. Với team push 10 lần/ngày, quota hết trong vài ngày. Fix: đổi sang workflow_dispatch hoặc schedule.

Secret leak qua log

GitHub mask secret trong log, nhưng không hoàn toàn. Ví dụ nguy hiểm:

      # NGUY HIỂM — có thể leak nếu value có ký tự đặc biệt
      - run: echo "Connecting to ${{ secrets.DB_URL }}"

Fix: không log secret, dùng action official thay vì tự viết connection string ra log.

PR từ fork có thể trigger workflow với secret

Với pull_request event, PR từ fork không có access secret (GitHub chặn theo mặc định). Nhưng pull_request_target event chạy trong context của repo gốc, có access secret — dùng trigger này cẩn thận, dễ bị tấn công nếu cho phép PR từ fork chạy code tùy ý.

Cache không invalidate

Cache key không đổi dù requirements đã thay đổi. Thường xảy ra khi hard-code key: key: pip-cache. Fix: dùng hash: key: pip-${{ hashFiles('**/requirements*.txt') }}.

Self-hosted runner không được update

Runner software cần update định kỳ. Runner quá cũ có thể bị GitHub ngừng kết nối, hoặc có lỗ hổng bảo mật. Nên bật auto-update hoặc đặt lịch update thủ công.

Notify step không chạy khi job fail

Step cuối "Notify Slack" bị skip nếu job trước fail. Fix: thêm if: always():

      - name: Notify Slack
        if: always()       # chạy dù step trước fail hay pass
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "Workflow ${{ job.status }}"}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Workflow chạy quá lâu không có timeout

Job mặc định có timeout 6 giờ. Nếu script train bị hang (ví dụ chờ input, deadlock), job sẽ chạy cho đến khi hết quota. Đặt timeout rõ ràng:

jobs:
  train:
    runs-on: ubuntu-latest
    timeout-minutes: 120     # tối đa 2 tiếng
17

Alternatives

GitHub Actions phù hợp cho hầu hết team vì tích hợp sẵn với GitHub. Các lựa chọn khác:

Tool Phù hợp khi Điểm cần lưu ý
GitLab CI Repo ở GitLab, on-premise GitLab Cú pháp YAML tương tự, native Kubernetes runner
CircleCI Cần resource class linh hoạt (GPU, large memory) Paid plan, cấu hình phức tạp hơn
Jenkins Legacy enterprise, on-premise hoàn toàn Cần tự quản lý infrastructure, Groovy DSL
Argo Workflows Kubernetes-native, DAG phức tạp Cần K8s cluster, learning curve cao hơn
Kubeflow Pipelines Team lớn, pipeline ML phức tạp, K8s Setup nặng, overkill cho team nhỏ
AWS CodePipeline Stack AWS hoàn toàn (SageMaker, ECR, ...) Lock-in AWS, integrate tốt với SageMaker
GCP Cloud Build Stack GCP (Vertex AI, Artifact Registry) Trigger từ Cloud Source Repos hoặc GitHub

Với team nhỏ (<10 người) dùng GitHub, GitHub Actions là lựa chọn có ít overhead nhất để bắt đầu. Khi cần pipeline ML phức tạp hơn (branching, artifact dependency), chuyển sang Argo Workflows hoặc Kubeflow khi đã có K8s cluster.

18

Tóm tắt

  • CI/CD cho ML có 3 đặc thù: artifact phức tạp hơn (code + model + data), train chậm nên không chạy mỗi PR, cần evaluation gate so sánh metric với baseline.
  • Tách CI workflow (chạy mọi PR, nhanh) khỏi train workflow (trigger thủ công hoặc schedule).
  • GitHub Actions workflow cơ bản: checkout@v4setup-python@v5 với cache: pip → install → lint → test.
  • Train workflow tích hợp DVC pull data, dvc repro chạy pipeline, script check metric, register model vào MLflow.
  • GPU runner: GitHub-hosted (ubuntu-22.04-gpu, paid) hoặc self-hosted runner với label [self-hosted, gpu].
  • Secret luôn dùng ${{ secrets.NAME }}, không echo ra log.
  • Build Docker image với docker/build-push-action@v6, push lên GHCR, trigger deploy hook.
  • CML tự comment metric report + plot lên PR — reviewer thấy kết quả không cần pull artifact về.
  • Matrix strategy spin up nhiều job song song để hyperparameter search.
  • Đặt timeout-minutes, dùng concurrency để tránh tốn quota không cần thiết.