MLOps:在 Azure Databricks 上为 ML 模型构建 CI/CD 流水线

dev.to

内容摘要

本文是一篇面向ML团队的实操教程,系统讲解如何在Azure Databricks上构建端到端的ML模型CI/CD流水线。文章使用客户流失预测模型作为案例,完整覆盖从代码提交到生产端点更新的自动化流程。核心组件包括:MLflow负责实验跟踪、模型版本化与注册表别名管理;Databricks Asset Bundles实现基础设施即代码,定义训练与验证作业及集群规格;GitHub Actions作为CI/CD协调器,在PR阶段执行代码检查与单元测试,在合并主分支后触发训练与部署;Delta Lake作为特征与验证数据湖,记录数据版本以确保特征可重现;Databricks Model Serving提供托管REST端点。流水线通过多阶段门控确保质量:CI阶段执行lint与测试,训练阶段使用GradientBoostingClassifier进行全量训练并通过MLflow autologging自动记录参数、指标和模型签名,注册阶段将模型版本注册至MLflow注册表,验证阶段在保留集上检查指标阈值(ROC-AUC≥0.80、F1≥0.72、精度≥0.70),仅通过后才会使用‘Production’别名提升模型,最后自动更新服务端点。文章还详述了生产环境注意事项,如固定Databricks Runtime版本、使用Azure Key Vault管理秘密、设定相对当前生产模型的指标基线、通过git SHA标记每次运行以及启用服务端点零缩放以降低成本。整套模式强调完全自动化、可重复性与可追溯性,展示了MLOps工程化落地的完整范式。

核心要点

  • 使用Databricks Asset Bundles定义训练和验证作业,实现基础设施即代码,确保集群和环境配置的可重复性。
  • 训练阶段利用MLflow autologging自动记录参数、指标和模型签名,并通过Delta版本和git SHA实现数据与代码的完全可追溯。
  • 验证作业在保留数据集上检查预定义的指标阈值(ROC-AUC≥0.80、F1≥0.72、精度≥0.70),未通过则阻止模型提升,杜绝劣质模型上线。
  • 通过MLflow注册表的‘Production’别名进行干净的生产模型切换,避免手动版本跟踪错误。
  • GitHub Actions工作流在PR时执行lint和单元测试,在合并到main时自动运行训练、验证、提升和端点更新,实现无人值守发布。
  • 生产环境建议固定Databricks Runtime版本、使用Azure Key Vault管理凭据、基于当前生产模型设定相对基线,并启用服务端点零缩放以控制成本。
  • 整个流水线通过dbutils.jobs.taskValues在作业任务间传递run_id,实现训练与验证的自动衔接,消除人工干预环节。

很多团队能训练出模型,却迟迟无法将其交付生产。这篇教程精准地击中了这个痛点,它不是空洞的MLOps理念宣讲,而是一份直接可用的技术蓝图。作者从项目结构、训练、验证到部署监控,一步一步拆解了在Azure Databricks上构建CI/CD流水线的完整过程,每一个代码片段和工具选择都附带了“为什么这么做”的生产考量。特别是验证阶段的指标门控和MLflow别名提升策略,是避免“劣质模型静默替换”的关键设计,值得每一个正在向AI工程化转型的团队细读和复用。

使用Databricks Asset Bundles定义训练和验证作业,实现基础设施即代码,确保集群和环境配置的可重复性。

—— 络石智能研究院 · 编辑推荐

MLOps:在 Azure Databricks 上为 ML 模型构建 CI/CD 流水线

大多数 ML 团队擅长训练模型,但很少擅长将模型交付上线。一个能运行的 Notebook 与一个能够可靠服务生产流量的模型之间的差距,正是大多数 ML 项目停滞不前的地方。

在本教程中,我将带领你构建一个针对 Azure Databricks 上的 ML 模型的完整 CI/CD 流水线,使用:

  • MLflow 进行实验跟踪、模型版本控制以及模型注册表
  • Databricks Asset Bundles (DABs) 实现基础设施即代码和任务部署
  • Azure DevOps / GitHub Actions 作为 CI/CD 编排器
  • Delta Lake 作为特征数据和验证数据的存储
  • Databricks Model Serving 作为生产端点

用例与上一篇文章中的流失预测模型相同,但这次我们完全聚焦于如何将该模型从 Notebook 可靠且可重复地部署到生产端点。


架构概览


CI/CD 流程


流水线阶段解析

| 阶段 | 工具 | 发生的事件 | 进入下一阶段的关卡 | | --- | --- | --- | --- | | CI | GitHub Actions | 代码检查、单元测试、bundle 验证 | 所有测试通过 | | 训练 | Databricks Job | 完整训练运行、MLflow 日志记录 | 任务退出码为0 | | 注册 | MLflow 注册表 | 模型版本化并移至 Staging | 训练成功时自动触发 | | 验证 | Databricks Job | 在保留集上检查指标阈值 | ROC-AUC >= 0.80 | | 提升 | MLflow 注册表 | 模型移至 Production | 验证通过 | | 部署 | Model Serving | 端点更新为新的模型版本 | 提升完成 | | 监控 | Databricks Lakehouse Monitoring | 部署后跟踪漂移和准确性 | 持续进行 |


步骤 1 — 使用 Databricks Asset Bundles 的项目结构

Databricks Asset Bundles (DABs) 允许你将任务、集群和流水线定义为代码,并通过 CLI 进行部署。这就是你的 IaC 层。

# databricks.yml
bundle:
  name: churn-prediction

variables:
  env:
    default: dev

targets:
  dev:
    mode: development
    default: true
    workspace:
      host: https://adb-xxxx.azuredatabricks.net

  staging:
    workspace:
      host: https://adb-xxxx.azuredatabricks.net

  production:
    mode: production
    workspace:
      host: https://adb-xxxx.azuredatabricks.net

resources:
  jobs:
    training_job:
      name: churn-training-${var.env}
      tasks:
        - task_key: feature_engineering
          notebook_task:
            notebook_path: ./notebooks/01_feature_engineering.py
          new_cluster:
            spark_version: 14.3.x-scala2.12
            node_type_id: Standard_DS3_v2
            num_workers: 4

        - task_key: train_and_register
          depends_on:
            - task_key: feature_engineering
          notebook_task:
            notebook_path: ./notebooks/02_train_and_register.py
          new_cluster:
            spark_version: 14.3.x-scala2.12
            node_type_id: Standard_DS3_v2
            num_workers: 2

    validation_job:
      name: churn-validation-${var.env}
      tasks:
        - task_key: validate_model
          notebook_task:
            notebook_path: ./notebooks/03_validate_and_promote.py
          new_cluster:
            spark_version: 14.3.x-scala2.12
            node_type_id: Standard_DS3_v2
            num_workers: 2


步骤 2 — 使用 MLflow 自动日志记录的训练任务

保持训练 Notebook 简洁且专注。让 MLflow 自动日志处理指标和参数记录的重活。

# notebooks/02_train_and_register.py
import mlflow
import mlflow.sklearn
from mlflow.models.signature import infer_signature
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, f1_score, precision_score, recall_score
from delta.tables import DeltaTable
import pandas as pd

mlflow.set_experiment('/churn-prediction/ci-cd-pipeline')
mlflow.sklearn.autolog(log_input_examples=True, log_model_signatures=True)

# Read Gold features and capture Delta version for reproducibility
gold_table    = DeltaTable.forName(spark, 'churn.gold.features')
delta_version = gold_table.history(1).select('version').collect()[0][0]

features_pdf = spark.table('churn.gold.features') \
    .filter("feature_date = current_date()") \
    .toPandas()

FEATURE_COLS = [
    'total_events', 'total_sessions', 'distinct_products',
    'events_last_30d', 'events_last_90d', 'days_since_last_event',
    'customer_tenure_days', 'avg_events_per_day',
    'recency_tier', 'engagement_score',
]
TARGET = 'churned'

X = features_pdf[FEATURE_COLS]
y = features_pdf[TARGET]
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

with mlflow.start_run() as run:
    params = {
        'n_estimators':  200,
        'max_depth':     5,
        'learning_rate': 0.05,
        'subsample':     0.8,
    }
    model = GradientBoostingClassifier(**params, random_state=42)
    model.fit(X_train, y_train)

    y_prob = model.predict_proba(X_test)[:, 1]
    y_pred = model.predict(X_test)

    mlflow.log_param('delta_feature_version', delta_version)
    mlflow.log_param('git_sha', dbutils.widgets.get('git_sha'))  # passed in by CI
    mlflow.log_metric('roc_auc',   roc_auc_score(y_test, y_prob))
    mlflow.log_metric('f1_score',  f1_score(y_test, y_pred))
    mlflow.log_metric('precision', precision_score(y_test, y_pred))
    mlflow.log_metric('recall',    recall_score(y_test, y_pred))

    signature = infer_signature(X_train, y_pred)
    mlflow.sklearn.log_model(
        model,
        artifact_path='churn-gbm',
        signature=signature,
        registered_model_name='churn-prediction-gbm',
        await_registration_for=300,
    )

    # Pass run ID to downstream tasks via job output
    dbutils.jobs.taskValues.set(key='run_id', value=run.info.run_id)
    print(f"Training complete. Run ID: {run.info.run_id}")


步骤 3 — 验证任务:在提升前根据指标设置关卡

切勿仅凭成功的训练运行就自动提升模型。在触及 Production 别名之前,始终在保留集或最新数据集上进行验证,并对照阈值进行检查。

# notebooks/03_validate_and_promote.py
import mlflow
from mlflow import MlflowClient
from sklearn.metrics import roc_auc_score
import pandas as pd

client    = MlflowClient()
MODEL_NAME = 'churn-prediction-gbm'

# Pick up run_id from the upstream training task
run_id = dbutils.jobs.taskValues.get(
    taskKey='train_and_register', key='run_id'
)

# Thresholds — fail the job if any metric is below these
THRESHOLDS = {
    'roc_auc':   0.80,
    'f1_score':  0.72,
    'precision': 0.70,
}

run     = client.get_run(run_id)
metrics = run.data.metrics

print("Validating metrics against thresholds...")
failures = []
for metric, threshold in THRESHOLDS.items():
    actual = metrics.get(metric, 0)
    status = 'PASS' if actual >= threshold else 'FAIL'
    print(f"  {metric}: {actual:.4f} (threshold: {threshold}) -> {status}")
    if actual < threshold:
        failures.append(f"{metric}={actual:.4f} below threshold {threshold}")

if failures:
    raise Exception(f"Validation failed: {', '.join(failures)}")

# Promote to Production alias if all thresholds pass
model_version = client.search_model_versions(
    filter_string=f"run_id='{run_id}'"
)[0].version

client.set_registered_model_alias(
    name=MODEL_NAME,
    alias='Production',
    version=model_version,
)

print(f"Model version {model_version} promoted to Production alias.")


步骤 4 — GitHub Actions CI/CD 工作流

这是将所有部分串联起来的粘合剂。一个工作流处理 PR 验证;另一个处理合并到 main 分支时的部署。

# .github/workflows/mlops.yml
name: MLOps CI/CD

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

env:
  DATABRICKS_HOST:  ${{ secrets.DATABRICKS_HOST }}
  DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}

jobs:
  ci:
    name: Lint and Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install databricks-cli databricks-sdk pytest flake8 mlflow

      - name: Lint
        run: flake8 notebooks/ src/ --max-line-length=120

      - name: Unit tests
        run: pytest tests/unit/ -v

      - name: Validate DAB bundle
        run: databricks bundle validate --target staging

  train-and-deploy:
    name: Train, Validate, Deploy
    runs-on: ubuntu-latest
    needs: ci
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Install Databricks CLI
        run: pip install databricks-cli databricks-sdk

      - name: Deploy bundle to staging
        run: databricks bundle deploy --target staging

      - name: Run training job
        run: |
          JOB_RUN_ID=$(databricks bundle run training_job \
            --var "git_sha=${{ github.sha }}" \
            --output json | jq -r '.run_id')
          echo "TRAINING_RUN_ID=$JOB_RUN_ID" >> $GITHUB_ENV

      - name: Run validation job
        run: |
          databricks bundle run validation_job --target staging
          echo "Validation passed. Promoting to production."

      - name: Deploy bundle to production
        run: databricks bundle deploy --target production

      - name: Update Model Serving endpoint
        run: |
          python scripts/update_serving_endpoint.py \
            --model-name churn-prediction-gbm \
            --alias Production


步骤 5 — 更新服务端点

流水线的最后一步是更新 Databricks Model Serving 端点,使其指向新提升的 Production 模型版本。

# scripts/update_serving_endpoint.py
import argparse
from databricks.sdk import WorkspaceClient
from databricks.sdk.service.serving import ServedModelInput, EndpointCoreConfigInput
from mlflow import MlflowClient

def update_serving_endpoint(model_name: str, alias: str):
    w      = WorkspaceClient()
    client = MlflowClient()

    # Resolve alias to concrete version
    model_version = client.get_model_version_by_alias(model_name, alias).version
    print(f"Deploying {model_name} version {model_version} (alias: {alias})")

    endpoint_name = 'churn-prediction-endpoint'
    served_model  = ServedModelInput(
        model_name=model_name,
        model_version=model_version,
        workload_size='Small',
        scale_to_zero_enabled=True,
    )

    try:
        # Update existing endpoint
        w.serving_endpoints.update_config(
            name=endpoint_name,
            served_models=[served_model],
        )
        print(f"Endpoint '{endpoint_name}' updated to version {model_version}.")
    except Exception:
        # Create if it doesn't exist yet
        w.serving_endpoints.create(
            name=endpoint_name,
            config=EndpointCoreConfigInput(served_models=[served_model]),
        )
        print(f"Endpoint '{endpoint_name}' created with version {model_version}.")

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--model-name', required=True)
    parser.add_argument('--alias',      required=True)
    args = parser.parse_args()
    update_serving_endpoint(args.model_name, args.alias)


工具对比

| 工具 | 在流水线中的角色 | 为什么不用替代方案 | | --- | --- | --- | | Databricks Asset Bundles | 任务和集群的 IaC | Terraform Databricks provider(更冗长,缺乏原生 Notebook 支持),手动 UI(不可复现) | | MLflow Registry aliases | Production / Staging 提升 | 基于 Stage 的提升(在 MLflow 2.x 中已弃用),手动版本跟踪(容易出错) | | GitHub Actions | CI/CD 编排器 | Azure DevOps(同样能运行,只需交换 yml 语法),Jenkins(运维开销更大) | | 验证任务中的指标关卡 | 自动化质量检查 | 人工审查(拖慢速度),没有关卡(有风险) | | Databricks Model Serving | 托管 REST 端点 | AKS 部署(更多控制,但运维量大得多),Azure ML 端点(增加额外服务依赖) | | dbutils.jobs.taskValues | 在任务间传递 run_id | 环境变量(在 DABs 中跨任务不可用),硬编码的 run 查找(脆弱) |


生产中需要注意的事项

固定你的 Databricks Runtime 版本。在 bundle 中使用 14.3.x-scala2.12 可以确保每次训练运行使用相同的 Spark 和库版本。浮动版本(latest)会导致静默的库漂移,破坏可复现性。

将密钥存储在 Azure Key Vault 中,而不仅仅是在 GitHub Secrets 中。GitHub Secrets 对 CI 令牌来说没问题,但对于 Databricks 任务在运行时使用的长期服务主体凭据,应使用 Azure Key Vault 作为后端,并通过 Databricks 密钥作用域进行引用。

从当前生产模型设置指标基准。你的阈值(ROC-AUC >= 0.80)应相对于当前 Production 模型在相同保留集上达到的指标,而不是一个任意数字。在验证任务中添加一个步骤,获取当前 Production 模型的指标,并将新模型与之进行比较。

用 git SHA 标记每个 MLflow 运行。在每次训练运行中将 git_sha 作为参数记录,意味着你可以始终将模型工件追溯到产生它的确切代码版本。这对事件响应至关重要。

服务端点启用缩放到零。对于非延迟敏感的模型,在服务端点上启用 scale_to_zero_enabled=True。对于不接收 24/7 流量的端点,这可以大幅降低成本。


总结

这里的模式很直接:代码变更触发 CI,CI 触发训练任务,训练任务注册模型,验证任务根据指标设置关卡,只有在那之后模型才被提升和部署。没有手动操作,没有跳过任何步骤。

使这个模式达到生产级别而不仅仅是自动化的原因,是结合了 Delta 版本控制以实现特征的可复现性、MLflow aliases 以实现清晰的提升语义,以及基于指标的关卡,从而确保一个更差的模型永远无法静默地替换一个更好的模型。


参考资料

  • Databricks Asset Bundles 文档
  • MLflow 模型注册表 — 别名和标签
  • Databricks Model Serving
  • Databricks SDK for Python
  • GitHub Actions — 适用于 Databricks 的 CI/CD
  • MLflow 自动日志记录
  • Azure Key Vault — Databricks 密钥作用域
  • Databricks Lakehouse Monitoring
由 络石智能 收录整理原文来源:dev.to发布于

标签

相关主题

专家点评

本文由编辑团队收录整理,内容来源于公开信息,仅供参考。

相关文章

2026年机器学习模型部署最佳实践——完整MLOps指南

本文由资深Python开发者兼数据科学家Naeemah Aliya Small撰写,系统阐述了2026年机器学习模型部署的完整最佳实践与MLOps生命周期。文章指出,ML部署与传统软件部署的核心区别在于模型对数据统计属性的第三维依赖,导致其会产生静默退化而非显式报错,且需要A/B测试、影子部署和金丝雀发布等在线实验验证。指南覆盖从模型打包、服务API构建、Docker容器化到模型监控的完整链路:在打包阶段,推荐使用MLflow模型注册中心替代脆弱的Pickle文件,并可通过ONNX实现跨框架可移植性;在服务层,使用FastAPI搭配Pydantic实现类型安全的模型服务;在监控环节,强调必须同时覆盖基础设施监控、预测分布监控和数据漂移检测三个层次,避免仅监控CPU/内存而忽视模型精度退化至61%的静默故障。文章详细对比了FastAPI、BentoML、TorchServe、TensorFlow Serving、Seldon Core、Ray Serve、ONNX Runtime七种主流模型服务框架的优缺点,并总结了部署就绪检查清单,涵盖模型版本化、输入验证、预测分布监控、数据漂移检测、回滚预案和CI/CD验证等12条检查项。核心理念是:可靠部署ML的团队并非拥有最优模型,而是将部署视为一等工程问题,通过版本化、自动化、可观测和可回滚的基础设施来保障生产稳定性。

naeemahsmall.com
阅读全文

MLOps Services: Keep AI Models Reliable in Prod

本文由 OpenMalo Engineering Team 撰写,系统介绍了 MLOps 服务如何保障机器学习模型在生产环境中的可靠性。MLOps 即机器学习的 DevOps,其核心是通过模型 CI/CD、版本化、自动重训练、漂移监控与可观测性,将模型部署从一次性实验转变为受控的、可观测的生命周期管理。文章指出,模型不会突然失效,而是会发生“漂移”——随着用户行为、产品迭代或季节变化,实时数据与训练数据的分布差异会导致准确率悄然下降,而漂移监控和自动重训练正是 MLOps 存在的根本原因。文中提到常用工具包括 MLflow、Kubeflow 以及现代可观测性栈。OpenMalo Engineering Team 明确建议,当模型进入生产环境且对业务有实质影响、数据随时间变化、需要频繁更新模型或多模型并行管理、以及有审计要求时,企业应当投资 MLOps 而非临时部署。MLOps 服务涵盖模型 CI/CD、版本跟踪、自动重训练、漂移与性能监控以及生产可观测性五个方面,能有效防止“部署后祈祷”的静默衰减问题。全文从实践出发,传递了 MLOps 是 AI 工程化的关键一环这一核心观点,对企业将 AI 从实验推向规模化可靠运行具有重要参考价值。

www.openmalo.com
阅读全文

AI 模型部署策略 | 2026 年生产部署指南

本文系统梳理了2026年AI模型部署到生产环境的12种核心策略,包括批量推理、实时推理、流式推理、边缘部署、金丝雀部署、蓝绿部署、影子部署、滚动更新、冠军-挑战者模式、多臂老虎机、无服务器推理和联邦学习。文章引用Gartner 2024年数据指出,仅29%企业成功部署生成式AI模型,48%的AI项目最终进入生产阶段,中位周期为8个月。Ademero分析显示AI项目平均回报率380%,年均节省240万美元。文中详述了各策略的适用场景、技术栈(vLLM、Triton、ONNX Runtime、Kubernetes、Kafka等)和成本考量,指出单端点实时推理月成本1800-2900美元,中型部署月费5000-15000美元。文章提供了基于延迟预算和风险容忍度的策略选择框架,强调监控、自动化CI/CD/MLOps管道、模型漂移检测以及EU AI Act合规的重要性,并给出六条2026年部署最佳实践,包括从批量起步、量化优化、关注业务指标而非仅技术指标等。

aioutlooks.com
阅读全文

Databricks 的前线部署工程师 - FDE(全栈)

本文是Databricks公司在德国慕尼黑招聘一名Forward Deployed Engineer (FDE) Fullstack 的岗位信息。FDE 是一个面向客户的、动手能力强的技术角色,主要负责与客户合作,利用Databricks平台构建和生产化数据与AI解决方案。该岗位的核心职责包括:领导端到端系统的架构设计和实施,涵盖数据工程、AI和应用开发;交付生产级系统、设计参考架构、自定义应用以及数据摄取和ML/AI模型集成;指导战略客户实施大数据和AI应用的端到端设计、构建和部署;确保解决方案的安全性和可扩展性,并符合Databricks最佳实践;与工程和客户支持团队合作提供产品反馈;深入客户团队,从技术IC到高管层,深度理解挑战并交付影响。岗位要求包括:6年以上数据工程、数据平台或软件工程经验;熟练使用Python、Scala、JavaScript/TypeScript等编程语言;掌握至少一种主流云生态(AWS、Azure、GCP);深入了解Apache Spark分布式计算及其运行时内部机制;熟悉CI/CD、MLOps和AI模型/API;具备企业客户交付和项目范围管理经验。Databricks是一家数据和AI公司,全球超过10000家组织依赖其Data Intelligence Platform,总部位于旧金山,由Lakehouse、Apache Spark、Delta Lake和MLflow的原创始人创建。

jobs-radar.com
阅读全文

高级AI工程师 - FDE(前向部署工程师)- 美国联邦部门 - Databricks

本文是Databricks发布的Sr. AI Engineer - FDE(前线部署工程师)招聘信息,工作地点位于美国华盛顿特区、马里兰州或弗吉尼亚州,服务于美国联邦部门。该职位隶属于Databricks高度专业化的客户面向AI团队——AI FDE团队,核心职责包括利用Databricks AI研究的最新技术开发前沿生成式AI解决方案,涵盖RAG、多智能体系统、Text2SQL、模型微调等应用,并负责GenAI应用的生产环境部署、评估与优化。候选人需使用HuggingFace、LangChain、DSPy等工具,并具备在AWS、Azure或GCP上部署生产级机器学习系统的经验。由于联邦合同要求,该职位强制要求美国公民身份及获得美国政府机密许可的能力,持有现行Secret或更高级别许可者优先。职位薪资范围为182,000至250,208美元年薪,并提供年度绩效奖金、股权等福利。Databricks强调其平台已被Comcast、Condé Nast、Grammarly及超过50%的财富500强企业采用,由Lakehouse、Apache Spark、Delta Lake和MLflow的原创者创立。该职位需要跨职能协作,影响产品路线图,并在Data + AI Summit等会议上担任思想领袖。

jobs-radar.com
阅读全文