Azure ML & MLOps

Written by

in

Advanced

Training a model in a notebook is the easy part. Getting that model into production — with reproducible pipelines, automated retraining, version control, fairness checks, and zero-downtime deployments — is where most ML projects fail. Azure Machine Learning is Microsoft’s enterprise platform for the full machine learning lifecycle, and MLOps is the discipline that ties it all together. This guide covers the end-to-end journey from raw data to monitored production models.

We will walk through workspace provisioning, component-based training pipelines, AutoML, the model registry, managed endpoints, the Responsible AI dashboard, and CI/CD automation with GitHub Actions. Every section includes production-ready code using the Azure ML SDK v2 and CLI v2.


The MLOps lifecycle

MLOps applies DevOps principles to machine learning. The goal is to make model training, validation, deployment, and monitoring as repeatable and automated as building and shipping software. The diagram below shows how each stage feeds into the next in a continuous loop:

AZURE ML — MLOps LIFECYCLE Data Data Assets Versioning Prepare Feature Eng. Pipelines Train AutoML / Custom Compute Clusters Evaluate Metrics & RAI Responsible AI Register Model Registry Version & Stage Deploy Managed Endpoints Online & Batch Monitor Data Drift Performance Retrain trigger Azure ML Workspace — Platform Layer Compute Datastores Environments Key Vault App Insights

Each stage in this lifecycle maps to a specific Azure ML capability. The platform layer at the bottom provides the shared infrastructure that every stage relies on.

100+ML Frameworks Supported
AutoMLAutomated Model Selection
ManagedOnline & Batch Endpoints
RAIResponsible AI Dashboard

Provisioning an Azure ML workspace

The workspace is the top-level resource in Azure ML. It groups compute, data, models, endpoints, and experiments. Behind the scenes it creates a Storage Account, Key Vault, Application Insights, and an optional Container Registry.

CLI v2 setup

# Install the Azure ML CLI v2 extension
az extension add -n ml -y

# Create a resource group
az group create \
  --name rg-mlops-prod \
  --location eastus2

# Create the workspace
az ml workspace create \
  --name mlw-production \
  --resource-group rg-mlops-prod \
  --location eastus2 \
  --display-name "Production ML Workspace"

# Create a compute cluster for training
az ml compute create \
  --name gpu-cluster \
  --resource-group rg-mlops-prod \
  --workspace-name mlw-production \
  --type AmlCompute \
  --size Standard_NC6s_v3 \
  --min-instances 0 \
  --max-instances 4 \
  --idle-time-before-scale-down 120

SDK v2 setup

from azure.ai.ml import MLClient
from azure.ai.ml.entities import Workspace, AmlCompute
from azure.identity import DefaultAzureCredential

# Connect to the workspace
ml_client = MLClient(
    credential=DefaultAzureCredential(),
    subscription_id="your-subscription-id",
    resource_group_name="rg-mlops-prod",
    workspace_name="mlw-production",
)

# Create a compute cluster programmatically
gpu_cluster = AmlCompute(
    name="gpu-cluster",
    size="Standard_NC6s_v3",
    min_instances=0,
    max_instances=4,
    idle_time_before_scale_down=120,
)
ml_client.compute.begin_create_or_update(gpu_cluster)
Cost tip: Always set min_instances to 0 and configure idle_time_before_scale_down. GPU compute is expensive — a 4-node NC6s_v3 cluster left running 24/7 costs over $4,000/month. Auto-scaling to zero when idle can cut training compute costs by 80% or more.

Datasets and data assets

Azure ML treats data as a first-class, versioned resource. Every dataset registered in the workspace is immutable and traceable, so you can always reproduce which data a given model was trained on.

Registering data assets

from azure.ai.ml.entities import Data
from azure.ai.ml.constants import AssetTypes

# Register a file dataset (e.g., CSV in blob storage)
training_data = Data(
    name="customer-churn-train",
    description="Training data for churn prediction model",
    path="azureml://datastores/workspaceblobstore/paths/data/churn_train.csv",
    type=AssetTypes.URI_FILE,
    version="1",
    tags={"source": "crm-export", "date": "2025-07-01"},
)
ml_client.data.create_or_update(training_data)

# Register a folder dataset (e.g., images)
image_data = Data(
    name="product-images-v2",
    description="Product catalog images for classification",
    path="azureml://datastores/workspaceblobstore/paths/images/products/",
    type=AssetTypes.URI_FOLDER,
    version="2",
)
ml_client.data.create_or_update(image_data)

# Register an MLTable for structured tabular data
tabular_data = Data(
    name="sales-forecast-features",
    path="./data/sales_features/",  # folder with MLTable file
    type=AssetTypes.MLTABLE,
    version="3",
)
ml_client.data.create_or_update(tabular_data)
Data versioning matters: When you update a data asset, create a new version instead of overwriting. This lets you trace exactly which data version was used for each training run, which is essential for audit trails and debugging model regressions.

Training with component-based pipelines

Azure ML pipelines decompose training workflows into reusable components. Each component is a self-contained step (data prep, training, evaluation) with defined inputs, outputs, and a runtime environment. This makes pipelines modular, testable, and easy to maintain.

Defining a training component

from azure.ai.ml import command, Input, Output
from azure.ai.ml.entities import Environment

# Define a curated environment or custom one
env = Environment(
    name="sklearn-training-env",
    image="mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04",
    conda_file="./envs/conda.yml",
)

# Training component
train_component = command(
    name="train_model",
    display_name="Train churn prediction model",
    inputs={
        "train_data": Input(type="uri_file"),
        "learning_rate": Input(type="number", default=0.01),
        "n_estimators": Input(type="integer", default=100),
    },
    outputs={
        "model_output": Output(type="mlflow_model"),
        "metrics_output": Output(type="uri_file"),
    },
    code="./src/train",
    command=("python train.py "
             "--train-data ${{inputs.train_data}} "
             "--learning-rate ${{inputs.learning_rate}} "
             "--n-estimators ${{inputs.n_estimators}} "
             "--model-output ${{outputs.model_output}} "
             "--metrics-output ${{outputs.metrics_output}}"),
    environment=env,
    compute="gpu-cluster",
)

Building the full pipeline

from azure.ai.ml.dsl import pipeline

@pipeline(
    description="End-to-end churn prediction pipeline",
    default_compute="gpu-cluster",
)
def churn_pipeline(raw_data, learning_rate, n_estimators):
    # Step 1: Prepare data
    prep_step = prep_component(
        raw_data=raw_data,
        test_split_ratio=0.2,
    )

    # Step 2: Train model
    train_step = train_component(
        train_data=prep_step.outputs.train_data,
        learning_rate=learning_rate,
        n_estimators=n_estimators,
    )

    # Step 3: Evaluate model
    eval_step = eval_component(
        model=train_step.outputs.model_output,
        test_data=prep_step.outputs.test_data,
    )

    return {
        "trained_model": train_step.outputs.model_output,
        "evaluation_report": eval_step.outputs.report,
    }

# Instantiate and submit
pipeline_job = churn_pipeline(
    raw_data=Input(path="azureml:customer-churn-train:1"),
    learning_rate=0.01,
    n_estimators=200,
)
submitted_job = ml_client.jobs.create_or_update(pipeline_job)
print(f"Pipeline submitted: {submitted_job.studio_url}")

YAML pipeline definition (alternative)

# pipeline.yml — declarative pipeline definition
$schema: https://azuremlschemas.azureedge.net/latest/pipelineJob.schema.json
type: pipeline
display_name: churn-prediction-pipeline
experiment_name: churn-experiment

settings:
  default_compute: azureml:gpu-cluster

inputs:
  raw_data:
    path: azureml:customer-churn-train:1
    type: uri_file

jobs:
  prep_data:
    type: command
    component: ./components/prep/component.yml
    inputs:
      raw_data: ${{parent.inputs.raw_data}}

  train_model:
    type: command
    component: ./components/train/component.yml
    inputs:
      train_data: ${{parent.jobs.prep_data.outputs.train_data}}
      learning_rate: 0.01

  evaluate:
    type: command
    component: ./components/eval/component.yml
    inputs:
      model: ${{parent.jobs.train_model.outputs.model_output}}
      test_data: ${{parent.jobs.prep_data.outputs.test_data}}
Pipeline pitfall: Each component runs in its own isolated environment. Do not assume shared file system state between steps. Pass data explicitly through declared inputs and outputs. Implicit file path sharing will cause hard-to-debug failures when pipelines run on different compute targets.

AutoML: automated model selection

When you do not have a strong prior on which algorithm to use, AutoML evaluates dozens of models and hyperparameter combinations for you. It handles featurization, model selection, and hyperparameter tuning — then returns the best model with full explainability.

from azure.ai.ml import automl, Input

# Classification task with AutoML
classification_job = automl.classification(
    compute="gpu-cluster",
    experiment_name="churn-automl",
    training_data=Input(
        path="azureml:customer-churn-train:1",
        type="mltable",
    ),
    target_column_name="churned",
    primary_metric="AUC_weighted",
    # Guardrails
    enable_model_explainability=True,
    enable_early_termination=True,
    # Time and iteration limits
    limits=automl.ClassificationLimits(
        timeout_minutes=120,
        max_trials=50,
        max_concurrent_trials=4,
        enable_early_termination=True,
    ),
    # Allowed algorithms (optional filter)
    allowed_training_algorithms=[
        "LightGBM", "XGBoost", "RandomForest",
        "LogisticRegression", "GradientBoosting",
    ],
    # Cross-validation
    n_cross_validations=5,
)

# Submit the AutoML job
returned_job = ml_client.jobs.create_or_update(classification_job)
print(f"AutoML job: {returned_job.studio_url}")

# After completion — retrieve the best model
best_run = ml_client.jobs.get(returned_job.name)
print(f"Best algorithm: {best_run.properties['best_model_algorithm']}")
print(f"Best AUC: {best_run.properties['best_primary_metric_score']}")

AutoML supports classification, regression, time-series forecasting, computer vision (image classification, object detection), and NLP (text classification, NER) tasks. For vision and NLP tasks, it fine-tunes pre-trained deep learning models automatically.

AutoML best practice: Use AutoML for initial exploration to establish a baseline, then build custom pipelines for production. The enable_model_explainability flag generates feature importance scores that help you understand what the model learned — critical for building trust with stakeholders.

Model registry: versioning and promotion

The model registry is a central repository for all trained models. Every model gets a name, version, and metadata. You can tag models with stage labels to track their lifecycle from development through staging to production.

from azure.ai.ml.entities import Model
from azure.ai.ml.constants import AssetTypes

# Register a model from a training run
model = Model(
    name="churn-prediction",
    version="3",
    path=f"azureml://jobs/{training_job.name}/outputs/model_output",
    type=AssetTypes.MLFLOW_MODEL,
    description="LightGBM churn classifier — AUC 0.94",
    tags={
        "algorithm": "LightGBM",
        "auc": "0.94",
        "dataset_version": "1",
        "stage": "staging",
    },
    properties={
        "training_job": training_job.name,
        "approved_by": "",
    },
)
ml_client.models.create_or_update(model)

# List all versions of a model
versions = ml_client.models.list(name="churn-prediction")
for v in versions:
    print(f"v{v.version} — {v.tags.get('stage', 'dev')} — {v.description}")

# Promote to production (update tags)
prod_model = ml_client.models.get(name="churn-prediction", version="3")
prod_model.tags["stage"] = "production"
prod_model.properties["approved_by"] = "ml-lead@company.com"
ml_client.models.create_or_update(prod_model)
MLflow integration: Azure ML natively supports MLflow models. Register models as MLFLOW_MODEL type and you get automatic dependency tracking, signature validation, and framework-agnostic serving. This means a model trained with scikit-learn, PyTorch, or TensorFlow can be deployed the same way.

Managed endpoints: online and batch

Azure ML provides two types of managed endpoints that handle infrastructure, scaling, and networking for you.

Online endpoints (real-time inference)

from azure.ai.ml.entities import (
    ManagedOnlineEndpoint,
    ManagedOnlineDeployment,
    CodeConfiguration,
)

# Create the endpoint
endpoint = ManagedOnlineEndpoint(
    name="churn-prediction-endpoint",
    description="Real-time churn prediction API",
    auth_mode="key",
    tags={"environment": "production"},
)
ml_client.online_endpoints.begin_create_or_update(endpoint).result()

# Deploy the model (blue deployment)
blue_deployment = ManagedOnlineDeployment(
    name="blue",
    endpoint_name="churn-prediction-endpoint",
    model="azureml:churn-prediction:3",
    instance_type="Standard_DS3_v2",
    instance_count=2,
    request_settings={
        "request_timeout_ms": 3000,
        "max_concurrent_requests_per_instance": 10,
    },
)
ml_client.online_deployments.begin_create_or_update(blue_deployment).result()

# Route 100% traffic to the blue deployment
endpoint.traffic = {"blue": 100}
ml_client.online_endpoints.begin_create_or_update(endpoint).result()

# Test the endpoint
result = ml_client.online_endpoints.invoke(
    endpoint_name="churn-prediction-endpoint",
    request_file="./test-request.json",
)
print(result)

Blue-green deployment (zero-downtime updates)

# Deploy new model version as "green"
green_deployment = ManagedOnlineDeployment(
    name="green",
    endpoint_name="churn-prediction-endpoint",
    model="azureml:churn-prediction:4",  # newer version
    instance_type="Standard_DS3_v2",
    instance_count=2,
)
ml_client.online_deployments.begin_create_or_update(green_deployment).result()

# Canary: send 10% traffic to green
endpoint.traffic = {"blue": 90, "green": 10}
ml_client.online_endpoints.begin_create_or_update(endpoint).result()

# After validation — shift all traffic to green
endpoint.traffic = {"blue": 0, "green": 100}
ml_client.online_endpoints.begin_create_or_update(endpoint).result()

# Clean up old deployment
ml_client.online_deployments.begin_delete(
    name="blue",
    endpoint_name="churn-prediction-endpoint",
)

Batch endpoints (large-scale scoring)

from azure.ai.ml.entities import BatchEndpoint, BatchDeployment

# Create a batch endpoint for scoring millions of records
batch_endpoint = BatchEndpoint(
    name="churn-batch-scoring",
    description="Score customer churn risk in bulk",
)
ml_client.batch_endpoints.begin_create_or_update(batch_endpoint).result()

batch_deployment = BatchDeployment(
    name="lgbm-v3",
    endpoint_name="churn-batch-scoring",
    model="azureml:churn-prediction:3",
    compute="gpu-cluster",
    mini_batch_size=100,
    max_concurrency_per_instance=2,
    output_action="append_row",
    output_file_name="predictions.csv",
)
ml_client.batch_deployments.begin_create_or_update(batch_deployment).result()

# Invoke the batch endpoint
job = ml_client.batch_endpoints.invoke(
    endpoint_name="churn-batch-scoring",
    input=Input(
        path="azureml://datastores/workspaceblobstore/paths/scoring/july_customers.csv",
        type="uri_file",
    ),
)
Endpoint security: For production deployments, use auth_mode="aad_token" instead of "key". Key-based auth works for development but is a security risk in production. Azure AD token auth integrates with RBAC, conditional access policies, and audit logs.

Responsible AI dashboard

The Responsible AI (RAI) dashboard provides a unified view of model fairness, interpretability, error analysis, and causal reasoning. It is not optional — it is a requirement for any model that affects people’s outcomes (credit scoring, hiring, healthcare, etc.).

from azure.ai.ml import Input
from azure.ai.ml.entities import (
    ResponsibleAiInsights,
    ErrorAnalysisConfig,
    ExplanationConfig,
    FairnessConfig,
    CausalConfig,
)

# Configure the RAI dashboard components
rai_config = ResponsibleAiInsights(
    model="azureml:churn-prediction:3",
    train_data=Input(path="azureml:customer-churn-train:1", type="mltable"),
    test_data=Input(path="azureml:customer-churn-test:1", type="mltable"),
    target_column="churned",
    components=[
        # Error analysis: find where the model fails
        ErrorAnalysisConfig(
            max_depth=4,
            num_leaves=31,
        ),
        # Explanations: feature importance per prediction
        ExplanationConfig(),
        # Fairness: check for bias across groups
        FairnessConfig(
            sensitive_features=["age_group", "gender", "region"],
            fairness_metric="demographic_parity",
        ),
        # Causal: what-if analysis
        CausalConfig(
            treatment_features=["discount_offered", "support_calls"],
        ),
    ],
)

# Submit the RAI dashboard job
rai_job = ml_client.jobs.create_or_update(rai_config)
print(f"RAI dashboard: {rai_job.studio_url}")

The dashboard answers four critical questions:

  • Error analysis — Where does the model make the most mistakes? Which subgroups have the highest error rates?
  • Explanations — Which features drive each individual prediction? Is the model relying on the right signals?
  • Fairness — Does the model perform equally across demographic groups? Are there disparate impact patterns?
  • Causal inference — What would happen if we changed a feature? Does offering a discount actually reduce churn?
Compliance requirement: Regulations like the EU AI Act and NYC Local Law 144 require bias audits for automated decision systems. The RAI dashboard gives you documented evidence of fairness analysis that auditors and regulators expect.

CI/CD for ML with GitHub Actions

MLOps automation connects your Git repository to the Azure ML platform. When code is merged to main, the pipeline should automatically retrain, evaluate, and — if metrics pass — deploy the new model.

GitHub Actions workflow

# .github/workflows/mlops-pipeline.yml
name: MLOps Training Pipeline
on:
  push:
    branches: [main]
    paths:
      - "src/train/**"
      - "src/evaluate/**"
      - "data/**"
      - "pipeline.yml"

jobs:
  train-and-evaluate:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Azure Login (OIDC)
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Install Azure ML CLI
        run: az extension add -n ml -y

      - name: Submit training pipeline
        run: |
          az ml job create \
            --file pipeline.yml \
            --resource-group rg-mlops-prod \
            --workspace-name mlw-production \
            --stream

      - name: Validate metrics
        run: |
          python scripts/check_metrics.py \
            --min-auc 0.90 \
            --min-f1 0.85

  deploy:
    needs: train-and-evaluate
    runs-on: ubuntu-latest
    if: success()
    steps:
      - uses: actions/checkout@v4

      - name: Azure Login (OIDC)
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Install Azure ML CLI
        run: az extension add -n ml -y

      - name: Deploy to staging (green)
        run: |
          az ml online-deployment create \
            --file deploy/green-deployment.yml \
            --resource-group rg-mlops-prod \
            --workspace-name mlw-production

      - name: Smoke test
        run: |
          python scripts/smoke_test.py \
            --endpoint churn-prediction-endpoint \
            --deployment green

      - name: Shift traffic
        run: |
          az ml online-endpoint update \
            --name churn-prediction-endpoint \
            --traffic "blue=0 green=100" \
            --resource-group rg-mlops-prod \
            --workspace-name mlw-production
OIDC over secrets: Use federated identity credentials (OIDC) for GitHub Actions instead of storing service principal passwords as secrets. OIDC tokens are short-lived and automatically rotated — no secrets to leak or expire.

Platform capabilities at a glance

Component Pipelines

Modular, reusable steps with defined inputs/outputs. Python SDK or YAML definitions.

AutoML

Automated featurization, model selection, and hyperparameter tuning across ML tasks.

📊

Model Registry

Versioned model store with staging labels, lineage tracking, and MLflow support.

🚀

Managed Endpoints

Real-time and batch inference with blue-green deployments and autoscaling.

Responsible AI

Fairness, interpretability, error analysis, and causal inference in one dashboard.

🔍

Data Drift Monitoring

Detect statistical changes in input data that signal model degradation.

🛠

Environments

Docker + Conda environments versioned alongside code for perfect reproducibility.

🔒

Enterprise Security

VNet injection, private endpoints, managed identity, RBAC, and CMK encryption.

Azure ML vs. the competition

CapabilityAzure MLDatabricks MLflowAWS SageMakerGCP Vertex AI
Pipeline orchestrationComponent pipelines (SDK v2 + YAML)Workflows + Delta Live TablesSageMaker PipelinesVertex Pipelines (KFP)
AutoMLBuilt-in (classification, regression, vision, NLP, forecasting)AutoML via Databricks AutoMLSageMaker AutopilotVertex AutoML
Model registryNative + MLflow integrationUnity Catalog / MLflow RegistrySageMaker Model RegistryVertex Model Registry
Managed servingOnline + Batch endpointsModel Serving (Serverless)Real-time + Batch TransformOnline + Batch Prediction
Responsible AIIntegrated dashboard (fairness, explanations, error analysis, causal)MLflow + third-party libsClarify (bias + explainability)Vertex Explainable AI
CI/CD integrationGitHub Actions, Azure DevOps, CLI v2Databricks Asset BundlesSageMaker Projects + CodePipelineCloud Build + Vertex
Notebook experienceIntegrated notebooks + VS CodeCollaborative notebooks (strong)SageMaker Studio notebooksColab Enterprise
DifferentiatorDeep Azure ecosystem + Responsible AISpark-native + lakehouseBroadest service catalogBigQuery + TensorFlow native

Choose Azure ML when your organization is invested in the Microsoft ecosystem, needs built-in Responsible AI tooling, or requires tight integration with Azure DevOps and GitHub. Choose Databricks when your workflows are Spark-heavy and you want a unified data + ML platform. Each platform has matured significantly — the deciding factor is usually which ecosystem your team already lives in.

Production MLOps checklist

  1. Version everything — data assets, environments, pipeline definitions, and models should be in source control with immutable versions.
  2. Automate the pipeline — manual training runs do not scale. Use CI/CD triggers so that code changes automatically kick off training and evaluation.
  3. Gate deployments on metrics — set minimum thresholds for AUC, F1, precision, or recall. A model that fails the gate never reaches production.
  4. Run the Responsible AI dashboard — before any model that affects real people goes live, verify fairness across sensitive groups and review error patterns.
  5. Monitor for data drift — production data evolves. Configure drift monitors that alert you when input distributions shift beyond a threshold, triggering retraining.
  6. Use blue-green deployments — never replace a production model in-place. Deploy the new version alongside the old, validate, then shift traffic.
  7. Scale compute to zero — training clusters and batch compute should auto-scale to zero when idle. GPU hours left running can consume budget rapidly.
  8. Secure the workspace — use private endpoints, managed identity (not keys), and RBAC. Store secrets in Key Vault, never in code or environment variables.

Next steps

  1. Create a workspace using the CLI v2 and register your first data asset.
  2. Build a two-step pipeline — data preparation followed by training — and submit it to a compute cluster.
  3. Run AutoML on a tabular dataset to establish a baseline, then compare against your custom model.
  4. Deploy to a managed endpoint and test the REST API with sample data.
  5. Generate a Responsible AI dashboard and review the fairness analysis before promoting the model.
  6. Read the docs: learn.microsoft.com/azure/machine-learning
Ready to operationalize your ML models? Azure Machine Learning and MLOps give you the platform, automation, and governance to take models from notebooks to production with confidence. Share your MLOps challenges in the comments — I will help you design the pipeline architecture.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *