CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
MLOps 入门/课时/CI/CD for ML
RawBlame
课时目录12 节

MLOps 入门

  1. 01MLOps 是什么
  2. 02数据工程基础
  3. 03版本控制与 DVC
  4. 04训练与实验跟踪
  5. 05MLflow 与血缘
  6. 06编排与调度
  7. 07CI/CD for ML
  8. 08容器化部署
  9. 09云原生 ML
  10. 10监控与可观测
  11. 11可解释 AI
  12. 12综合练习

CI/CD for ML

CML、Jenkins 与 Ansible IaC。

30 分钟

CI/CD for ML

这一课你将能做到

  1. 说清 ML 的 CI 比后端多出的三类关卡:数据校验、训练冒烟、指标门禁
  2. 写 GitHub Actions 工作流:PR 上跑 lint + 冒烟训练并把指标贴回评论,main 合并自动构建镜像
  3. 用 environment 审批把「部署生产」变成需要人工点头的显式动作

为什么要学

第 6 课的编排管「生产节奏」(每天几点跑),CI/CD 管「变更进入主干与生产的路径」。两者的区别:编排跑的是同样的代码在同样的环境按时间重复;CI/CD 跑的是每次代码或数据的变更。

ML 项目的变更面比后端宽:改了特征口径、换了一版数据、动了超参,都可能「本地好好的、合并全完蛋」。所以 CI 里除了常规 lint/单测,还要加三道 ML 特有关卡——数据 schema 校验、小样本训练冒烟、指标门禁。人话:CI 是门卫,代码、数据、模型三者谁要进主干,都得先过秤。

先建立图像

PR 提交
  │
  ├─ job: quality       ruff 静态检查(<1min)
  ├─ job: data-check    数据 schema 校验(<1min)
  └─ job: smoke-train   --smoke 冒烟训练 + 指标门禁 + 评论 PR(~3min)
        │ 全绿才允许 merge
        ▼
merge 到 main
  │
  ├─ job: image         构建 Docker 镜像 → 推送 registry(打 sha 标签)
  └─ job: deploy-prod   environment 审批 → kubectl apply
        (连续训练 CT:生产侧由第 6 课的编排定时触发,产出经注册中心晋升)

跟着做

步骤 1:让训练脚本输出指标文件

给 src/train.py 的 main() 末尾加一行,把指标落成 JSON 供 CI 读取:

# src/train.py main() 中 log_metrics 之后追加
import json
json.dump({"f1": f1, "roc_auc": roc_auc},
          open("metrics.json", "w"))

再加一个独立的数据校验脚本(CI 与生产管道共用):

# src/validate_data.py
import sys
import pandas as pd

REQUIRED = {"submit_id", "problem_id", "lang", "code_lines",
            "rank", "verdict", "time_ms"}

def check(path="data/processed/judge_clean.parquet"):
    df = pd.read_parquet(path)
    missing = REQUIRED - set(df.columns)
    assert not missing, f"缺字段: {missing}"
    assert df["code_lines"].between(1, 10_000).all(), "code_lines 越界"
    assert df["verdict"].isin(["AC", "TLE", "WA", "RE"]).all(), "非法 verdict"
    tle = (df["verdict"] == "TLE").mean()
    assert 0.01 < tle < 0.6, f"TLE 占比异常: {tle:.2f}"   # 分布突变预警
    print(f"data ok, rows={len(df)}, tle_rate={tle:.3f}")

if __name__ == "__main__":
    check()
python src/validate_data.py

步骤 2:编写 GitHub Actions 工作流

# .github/workflows/ml-ci.yml
name: ml-ci

on:
  pull_request:
  push:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11", cache: pip }
      - run: pip install ruff && ruff check src flows

  data-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11", cache: pip }
      - run: pip install -r requirements.txt
      - run: python src/validate_data.py

  smoke-train:
    runs-on: ubuntu-latest
    needs: quality
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11", cache: pip }
      - run: pip install -r requirements.txt mlflow
      - name: 冒烟训练(小样本,分钟级)
        run: python src/train.py --smoke
      - name: 指标门禁
        run: |
          python - <<'PY'
          import json, sys
          m = json.load(open("metrics.json"))
          if m["roc_auc"] < 0.60:
              sys.exit(f"门禁不通过: {m}")
          print("gate passed:", m)
          PY
      - name: 把指标评论到 PR
        if: github.event_name == 'pull_request'
        uses: marocchino/sticky-pull-request-comment@v2
        with: { path: metrics.json }

  image:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    needs: [quality, data-check, smoke-train]
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

  deploy-prod:
    if: github.ref == 'refs/heads/main'
    needs: image
    runs-on: ubuntu-latest
    environment: production          # 关键:需在仓库设置里批准
    steps:
      - uses: actions/checkout@v4
      - run: kubectl set image deploy/timeout-api \
              api=ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

步骤 3:推到 GitHub 看效果

pip freeze > requirements.txt
git add .github src && git commit -m "ci: 冒烟训练与指标门禁"
git push

打开仓库 Actions 页:PR 上能看到三个 job 的绿勾与指标评论;deploy-prod 停在等待 environment 审批的状态,维护者在 Settings → Environments 里点批才继续。

讲清楚要点

  1. CI for ML 的三道特有关卡:数据校验(schema/分布突变)、训练冒烟(小样本证明管道没断)、指标门禁(阈值不过就退出非零)。任何一环失败都挡住合并。
  2. 冒烟训练的价值是「管道保活」:它不追求模型质量,只证明「取数→特征→训练→评估」整条链路在干净环境能跑通。依赖缺失、路径写死、接口变更这类问题当场暴露,而不是三天后的编排任务里。
  3. 门禁阈值要写进代码而不是长在脑子里:roc_auc < 0.60 就失败 是可审计的;「我觉得不太行」不可审计。阈值可以随基线演进,但必须显式。
  4. CD 的对象是镜像而不是模型:CI/CD 发布「服务代码 + 推理环境」;模型文件走第 5 课的注册中心指针。两条发布流解耦,服务频繁发、模型按门禁节奏发。
  5. CT(持续训练)的触发源有三种:定时(编排器)、事件(新数据到位/漂移告警)、手动(业务要求)。本课程的组合是:编排器每天触发 + 漂移告警可触发,CI/CD 负责把训练代码的变更安全送达。

你可能会懵的地方

  • CI runner 没有 GPU,深度学习项目怎么办? CI 只跑冒烟:小模型、小数据、CPU 几分钟内完成,验证链路而非效果。真正训练交给第 6 课的编排任务或第 9 课的云托管训练。也可以用 self-hosted runner 挂 GPU,但要评估并发与安全。
  • 模型文件怎么在 job 之间传递? 用 actions/upload-artifact 把模型或 metrics.json 存为工作流产物,下游 job 用 download-artifact 取。跨仓库/长期共享则走注册中心或对象存储,CI 只存临时产物。
  • 坏数据进主干怎么防? 两道防线:CI 的 data-check 拦「代码仓库内的样例数据/校验逻辑」;生产侧第 10 课的漂移监控拦「线上真实数据突变」。CI 校验的是规则,监控校验的是现实。
  • 为什么不把「训练 + 晋升 champion」也放进 CI? 完整训练慢且贵,放在 PR 流程会拖死开发;且模型发布应由门禁 + 审批控制节奏。经验法则:CI 验证能力(能不能跑、指标底线),CD 发布环境,CT 发布模型。
  • secrets 怎么管理? 只用平台 secrets(secrets.GITHUB_TOKEN、仓库级 secrets),注入环境变量;绝不把 token 写进代码、日志或镜像层。本地开发用 .env 且确认进了 .gitignore。

错法对照

错法问题正解
CI 里跑全量训练几小时PR 反馈极慢,开发者绕过流程--smoke 小样本分钟级,全量训练归编排
门禁只有 lint 没有指标模型劣化照样合并发布metrics.json + 阈值判断 + 失败退出
镜像统一打 latest 标签无法回滚、无法定位版本打 git sha 与语义化双标签
部署不需要审批,合并即上生产一次误合并直接打穿生产environment 保护规则强制人工批准
训练脚本 secrets 写死在代码里泄漏进 Git 历史无法挽回平台 secrets 注入环境变量

卡点急救

  1. job 里 python: command not found:没用 setup-python 或版本装错;每个 job 都要显式 uses: actions/setup-python@v5。
  2. pip 安装超时:加 cache: pip 并提供 requirements.txt;依赖多时先跑缓存预热 job。
  3. kubectl 部署报 unauthorized:runner 没有集群凭据;用 azure/k8s-set-context 或把 kubeconfig 存为 secret 注入,最小权限只授予目标 namespace。

面试常问

  • ML 项目的 CI/CD 流水线你会怎么设计? PR 阶段:lint、单测、数据 schema 校验、冒烟训练、指标门禁、PR 评论指标;合并 main:构建镜像推 registry(sha 标签)+ environment 审批部署;模型本体走注册中心,由编排触发的 CT 或漂移事件经门禁晋升。关键原则:快(PR 反馈分钟级)、门禁代码化、服务与模型发布解耦。
  • 什么是 CT(Continuous Training)?触发条件有哪些? 持续训练是 ML 特有的自动化:按条件自动重训并走门禁晋升。触发条件:定时节奏(每日/每周)、数据漂移告警、新标注数据到位、业务指标衰减、手动。核心是与第 5 课门禁、第 10 课监控闭环联动,而不是「到点就上线」。
  • 模型发布和代码发布为什么解耦? 节奏不同(代码周级、模型按门禁触发)、风险不同(模型影响面靠监控评估)、载体不同(镜像 vs 注册中心指针)。解耦后各自回滚互不牵连:代码回滚动镜像 tag,模型回滚动 champion 指针。

自测三问

  1. ML 的 CI 比后端 CI 多出的三道关卡是什么?各拦截什么问题?
  2. 冒烟训练证明什么、不证明什么?
  3. 为什么模型文件不走 CI/CD 发布,而走注册中心?

小结一句话

CI/CD for ML = 后端门卫职责(lint/审批/镜像发布)+ ML 特有秤(数据校验、训练冒烟、指标门禁),服务发布与模型晋升解耦。

延伸

  • GitHub Actions 官方文档——workflow 语法、secrets 与 environment 保护规则。
  • 下一课:容器化部署——把推理服务打包成不可变镜像,再交给 K8s 弹性伸缩。
加载本节练习…
← 上一课编排与调度下一课 →容器化部署