Skip to main content
Crashbytes logoCrashbytes
HomeArticlesByte Sized ExamplesOpen SourceServicesAboutContact
Browse Articles
HomeArticlesByte Sized ExamplesOpen SourceServicesAboutContact
Network
Theme
Browse Articles
Crashbytes logoCrashbytes

Expert insights on web development, technology trends, and programming best practices. Learn from real-world experiences and cutting-edge techniques that help you build better software.

Follow Us

Our Sites

  • 🔮 Predictions
  • 📰 Breaking News
  • 🎨 AI Art
  • 📖 Short Stories
  • View All →
  • Products →

Sitemap

  • Home
  • All Articles
  • Open Source
  • Services
  • About Us
  • Contact
  • Donate Compute

Popular Topics

  • Serverless
  • Cloud Architecture
  • DevOps
  • Kubernetes
  • Platform Engineering

Resources

  • Privacy Policy
  • Terms of Service
  • Sitemap
  • RSS Feed
  • PGP Key

Stay Updated

Get the latest articles, tutorials, and insights delivered to your inbox. Join our community of developers and never miss an update.

© 2021-2026 Crashbytes® by Blackhole Software, LLC. All rights reserved.
| Reg. U.S. Pat. & Tm. Off.

Made for the developer community

  1. Home
  2. /
  3. Articles
  4. /
  5. Tutorial: Building Production MLOps Pipelines on Kubernetes with Kubeflow
tutorialSeptember 12, 202520 min read• By Michael Eakins

Tutorial: Building Production MLOps Pipelines on Kubernetes with Kubeflow

Build enterprise-grade MLOps pipelines on Kubernetes with automated training, validation, deployment, and monitoring. Complete implementation with Kubeflow, model registry, and production patterns.

Quick Takeaways

What you'll learn in this article

20 min read
Intermediate
  • 1

    Automates model training with configurable hyperparameter tuning

  • 2

    Validates models against performance benchmarks before deployment

  • 3

    Manages model versions in a centralized registry with lineage tracking

  • 4

    Deploys models with blue-green or canary strategies

  • 5

    Monitors model performance with drift detection and alerting

Keep reading for detailed implementation, code examples, and real-world results

After building MLOps platforms for Fortune 500 enterprises across regulated industries, I've learned that the difference between ML experimentation and production deployment is a robust, automated pipeline that can scale with your organization's ML maturity. The challenge isn't just training models—it's building infrastructure that automates training, validation, deployment, monitoring, and retraining at enterprise scale.

This tutorial walks through building a complete, production-ready MLOps pipeline on Kubernetes that I've successfully deployed in organizations processing millions of predictions daily. You'll learn to implement automated training pipelines, model registries, deployment strategies, monitoring, and rollback procedures—all orchestrated on Kubernetes with Kubeflow.

Tutorial Overview & Learning Objectives

What You'll Build

By the end of this tutorial, you'll have a production-grade MLOps pipeline that:

  • Automates model training with configurable hyperparameter tuning
  • Validates models against performance benchmarks before deployment
  • Manages model versions in a centralized registry with lineage tracking
  • Deploys models with blue-green or canary strategies
  • Monitors model performance with drift detection and alerting
  • Enables automated retraining based on performance degradation
  • Provides audit trails for compliance and debugging

Real-World Use Cases

This MLOps pipeline is designed for enterprise scenarios where ML reliability is critical:

  • Financial services deploying fraud detection models with regulatory requirements
  • Healthcare organizations running diagnostic models requiring audit trails
  • E-commerce platforms deploying recommendation systems at scale
  • Manufacturing implementing predictive maintenance with retraining cycles

Time Commitment

Estimated completion time: 5-6 hours including Kubernetes cluster setup, Kubeflow installation, pipeline implementation, and testing. Production deployment may require additional time for security hardening and integration.

Advertisement

Architecture & Design Overview

System Architecture

Our MLOps pipeline follows a microservices architecture on Kubernetes, with each component handling specific responsibilities:

Data Sources → Feature Store → Training Pipeline → Model Registry → Deployment → Production Serving
                                      ↓                    ↓              ↓
                              Experiment Tracking    Version Control   Monitoring
                                      ↓                    ↓              ↓
                              Hyperparameter Tuning  A/B Testing    Drift Detection
                                                                          ↓
                                                                  Automated Retraining

Key architectural decisions:

  1. Kubernetes-native: All components run as Kubernetes resources for consistency and scalability
  2. Pipeline-as-code: ML pipelines defined in Python using Kubeflow Pipelines SDK
  3. Declarative deployments: Model serving configurations managed through Kubernetes manifests
  4. Event-driven retraining: Automatic retraining triggered by performance metrics

Technology Stack Rationale

Kubeflow: Chosen as the comprehensive MLOps platform built specifically for Kubernetes. Kubeflow Pipelines provides workflow orchestration, while KServe handles model serving. The Kubeflow documentation provides extensive guidance for enterprise deployments.

MLflow: Integrated for experiment tracking and model registry. MLflow's flexibility and broad ML framework support make it ideal for heterogeneous ML environments. MLflow tracking integrates seamlessly with Kubeflow.

KServe: Production-grade model serving with autoscaling, canary deployments, and multi-framework support. KServe replaced KFServing as the standard for Kubernetes ML serving.

Prometheus + Grafana: Industry-standard monitoring stack for tracking model performance metrics, latency, and infrastructure health.

Design Decisions and Tradeoffs

Kubeflow vs. Custom Pipeline: Kubeflow provides battle-tested components but adds complexity. We chose Kubeflow because its benefits (standardization, community support, feature completeness) outweigh setup complexity for enterprise use cases.

Centralized vs. Federated: This tutorial implements centralized model training on Kubernetes. For multi-region deployments or federated learning, consider distributed training patterns with Kubeflow Training Operator.

Real-time vs. Batch: The pipeline supports both real-time serving (via KServe) and batch inference (via Kubernetes Jobs). Choose based on latency requirements and throughput needs.

Setup & Environment Configuration

Prerequisites Verification

Before starting, verify your environment meets these requirements:

# Check Kubernetes cluster access
kubectl version --client
kubectl cluster-info

# Verify you have cluster-admin permissions
kubectl auth can-i create namespace

# Check available resources (minimum: 16 vCPUs, 32GB RAM)
kubectl top nodes

# Verify storage class for persistent volumes
kubectl get storageclass

Kubernetes Cluster Setup

If you don't have a cluster, create one using your preferred provider:

Option 1: Local Development (Kind)

# Install kind
brew install kind  # macOS
# OR
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64

# Create cluster with sufficient resources
cat <<EOF | kind create cluster --name mlops --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 31380
    hostPort: 8080
  - containerPort: 31381
    hostPort: 8081
- role: worker
- role: worker
EOF

Option 2: Cloud Provider (GKE Example)

# Create GKE cluster optimized for ML workloads
gcloud container clusters create mlops-cluster \
    --zone us-central1-a \
    --machine-type n1-standard-8 \
    --num-nodes 3 \
    --enable-autoscaling \
    --min-nodes 3 \
    --max-nodes 10 \
    --disk-size 100 \
    --disk-type pd-ssd \
    --enable-ip-alias \
    --addons HorizontalPodAutoscaling,HttpLoadBalancing

# Get credentials
gcloud container clusters get-credentials mlops-cluster --zone us-central1-a

Kubeflow Installation

Install Kubeflow using the official manifests:

# Set Kubeflow version
export KUBEFLOW_VERSION=1.8.0

# Clone Kubeflow manifests
git clone https://github.com/kubeflow/manifests.git
cd manifests
git checkout v${KUBEFLOW_VERSION}

# Install Kubeflow components
# Note: This takes 10-15 minutes
while ! kustomize build example | kubectl apply -f -; do
  echo "Retrying to apply resources"
  sleep 10
done

# Wait for all pods to be ready
kubectl wait --for=condition=Ready pods --all -n kubeflow --timeout=600s

# Verify installation
kubectl get pods -n kubeflow

Port Forward to Access Kubeflow Dashboard:

kubectl port-forward -n istio-system svc/istio-ingressgateway 8080:80
# Access at http://localhost:8080

MLflow Setup

Deploy MLflow for experiment tracking:

# Create namespace
kubectl create namespace mlflow

# Deploy MLflow with PostgreSQL backend
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mlflow-pvc
  namespace: mlflow
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mlflow
  namespace: mlflow
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mlflow
  template:
    metadata:
      labels:
        app: mlflow
    spec:
      containers:
      - name: mlflow
        image: ghcr.io/mlflow/mlflow:v2.9.0
        ports:
        - containerPort: 5000
        command:
        - mlflow
        - server
        - --host
        - 0.0.0.0
        - --port
        - "5000"
        - --backend-store-uri
        - sqlite:////mlflow/mlflow.db
        - --default-artifact-root
        - /mlflow/artifacts
        volumeMounts:
        - name: mlflow-storage
          mountPath: /mlflow
      volumes:
      - name: mlflow-storage
        persistentVolumeClaim:
          claimName: mlflow-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: mlflow-service
  namespace: mlflow
spec:
  type: ClusterIP
  ports:
  - port: 5000
    targetPort: 5000
  selector:
    app: mlflow
EOF

# Verify MLflow is running
kubectl wait --for=condition=Ready pod -l app=mlflow -n mlflow --timeout=300s

# Port forward to access MLflow UI
kubectl port-forward -n mlflow svc/mlflow-service 5000:5000
# Access at http://localhost:5000

Project Structure

Create the project repository structure:

mkdir mlops-pipeline-tutorial && cd mlops-pipeline-tutorial

# Create directory structure
mkdir -p {src/{training,serving,preprocessing},k8s/{base,overlays/{dev,prod}},pipelines,tests,notebooks}

# Initialize git
git init

# Create .gitignore
cat > .gitignore <<EOF
__pycache__/
*.pyc
*.pyo
.pytest_cache/
*.egg-info/
dist/
build/
.env
.venv/
venv/
*.ipynb_checkpoints
.DS_Store
mlruns/
artifacts/
EOF

Step-by-Step Implementation

Step 1: Model Training Code

Create src/training/model.py with production-ready training code:

"""
Production ML Model Training Module

Implements training pipeline with experiment tracking, validation,
and artifact management for Kubernetes deployment.
"""

import os
import json
import argparse
from typing import Dict, Tuple, Any
import logging

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import tensorflow as tf
from tensorflow import keras
import mlflow
import mlflow.tensorflow

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class ModelTrainer:
    """
    Production-ready model training with MLflow integration.

    Handles data loading, preprocessing, training, validation,
    and model artifact creation for Kubernetes deployment.
    """

    def __init__(
        self,
        mlflow_tracking_uri: str,
        experiment_name: str,
        model_type: str = "classification"
    ):
        """
        Initialize trainer with MLflow configuration.

        Args:
            mlflow_tracking_uri: MLflow server URI
            experiment_name: Experiment name for tracking
            model_type: Type of model (classification/regression)
        """
        self.mlflow_tracking_uri = mlflow_tracking_uri
        self.experiment_name = experiment_name
        self.model_type = model_type

        # Configure MLflow
        mlflow.set_tracking_uri(mlflow_tracking_uri)
        mlflow.set_experiment(experiment_name)

        logger.info(f"Initialized ModelTrainer for experiment: {experiment_name}")

    def load_data(self, data_path: str) -> Tuple[np.ndarray, np.ndarray]:
        """
        Load and preprocess training data.

        Args:
            data_path: Path to training data (CSV or numpy)

        Returns:
            Tuple of (features, labels)
        """
        logger.info(f"Loading data from {data_path}")

        if data_path.endswith('.csv'):
            df = pd.read_csv(data_path)
            # Assume last column is target
            X = df.iloc[:, :-1].values
            y = df.iloc[:, -1].values
        elif data_path.endswith('.npy'):
            data = np.load(data_path)
            X = data[:, :-1]
            y = data[:, -1]
        else:
            raise ValueError(f"Unsupported data format: {data_path}")

        logger.info(f"Loaded {len(X)} samples with {X.shape[1]} features")
        return X, y

    def create_model(
        self,
        input_dim: int,
        hidden_layers: list = [128, 64, 32],
        dropout_rate: float = 0.3
    ) -> keras.Model:
        """
        Create neural network architecture.

        Args:
            input_dim: Input feature dimension
            hidden_layers: List of hidden layer sizes
            dropout_rate: Dropout rate for regularization

        Returns:
            Compiled Keras model
        """
        model = keras.Sequential()

        # Input layer
        model.add(keras.layers.Dense(
            hidden_layers[0],
            activation='relu',
            input_shape=(input_dim,)
        ))
        model.add(keras.layers.Dropout(dropout_rate))

        # Hidden layers
        for units in hidden_layers[1:]:
            model.add(keras.layers.Dense(units, activation='relu'))
            model.add(keras.layers.Dropout(dropout_rate))

        # Output layer
        if self.model_type == "classification":
            model.add(keras.layers.Dense(1, activation='sigmoid'))
            model.compile(
                optimizer='adam',
                loss='binary_crossentropy',
                metrics=['accuracy', 'AUC']
            )
        else:
            model.add(keras.layers.Dense(1))
            model.compile(
                optimizer='adam',
                loss='mse',
                metrics=['mae']
            )

        return model

    def train(
        self,
        data_path: str,
        epochs: int = 50,
        batch_size: int = 32,
        validation_split: float = 0.2,
        **hyperparameters
    ) -> Dict[str, Any]:
        """
        Train model with experiment tracking.

        Args:
            data_path: Path to training data
            epochs: Number of training epochs
            batch_size: Training batch size
            validation_split: Validation data fraction
            **hyperparameters: Additional model hyperparameters

        Returns:
            Dictionary containing training metrics and model info
        """
        with mlflow.start_run() as run:
            # Log parameters
            mlflow.log_params({
                "epochs": epochs,
                "batch_size": batch_size,
                "validation_split": validation_split,
                **hyperparameters
            })

            # Load data
            X, y = self.load_data(data_path)

            # Split data
            X_train, X_val, y_train, y_val = train_test_split(
                X, y,
                test_size=validation_split,
                random_state=42,
                stratify=y if self.model_type == "classification" else None
            )

            # Create model
            model = self.create_model(
                input_dim=X_train.shape[1],
                **hyperparameters
            )

            # Log model architecture
            model_summary = []
            model.summary(print_fn=lambda x: model_summary.append(x))
            mlflow.log_text('\n'.join(model_summary), "model_architecture.txt")

            # Setup callbacks
            callbacks = [
                keras.callbacks.EarlyStopping(
                    monitor='val_loss',
                    patience=5,
                    restore_best_weights=True
                ),
                keras.callbacks.ReduceLROnPlateau(
                    monitor='val_loss',
                    factor=0.5,
                    patience=3
                )
            ]

            # Train model
            logger.info("Starting model training...")
            history = model.fit(
                X_train, y_train,
                validation_data=(X_val, y_val),
                epochs=epochs,
                batch_size=batch_size,
                callbacks=callbacks,
                verbose=1
            )

            # Evaluate model
            logger.info("Evaluating model...")
            val_predictions = (model.predict(X_val) > 0.5).astype(int).flatten()

            metrics = {
                "val_accuracy": accuracy_score(y_val, val_predictions),
                "val_precision": precision_score(y_val, val_predictions),
                "val_recall": recall_score(y_val, val_predictions),
                "val_f1": f1_score(y_val, val_predictions)
            }

            # Log metrics
            for metric_name, metric_value in metrics.items():
                mlflow.log_metric(metric_name, metric_value)

            # Log training curves
            for metric in history.history.keys():
                for epoch, value in enumerate(history.history[metric]):
                    mlflow.log_metric(metric, value, step=epoch)

            # Save and log model
            logger.info("Saving model...")
            mlflow.tensorflow.log_model(
                model,
                "model",
                registered_model_name=self.experiment_name
            )

            # Log model size
            model_path = f"/tmp/model_{run.info.run_id}"
            model.save(model_path)
            model_size_mb = sum(
                os.path.getsize(os.path.join(dirpath, filename))
                for dirpath, dirnames, filenames in os.walk(model_path)
                for filename in filenames
            ) / (1024 * 1024)
            mlflow.log_metric("model_size_mb", model_size_mb)

            logger.info(f"Training completed. Run ID: {run.info.run_id}")
            logger.info(f"Metrics: {json.dumps(metrics, indent=2)}")

            return {
                "run_id": run.info.run_id,
                "metrics": metrics,
                "model_uri": f"runs:/{run.info.run_id}/model"
            }


def main():
    """Main training entry point for Kubernetes Jobs"""
    parser = argparse.ArgumentParser(description="Train ML model")
    parser.add_argument("--data-path", required=True, help="Path to training data")
    parser.add_argument("--mlflow-uri", required=True, help="MLflow tracking URI")
    parser.add_argument("--experiment-name", required=True, help="Experiment name")
    parser.add_argument("--epochs", type=int, default=50, help="Training epochs")
    parser.add_argument("--batch-size", type=int, default=32, help="Batch size")
    parser.add_argument("--hidden-layers", type=str, default="128,64,32",
                       help="Comma-separated hidden layer sizes")
    parser.add_argument("--dropout-rate", type=float, default=0.3,
                       help="Dropout rate")

    args = parser.parse_args()

    # Parse hidden layers
    hidden_layers = [int(x) for x in args.hidden_layers.split(',')]

    # Initialize trainer
    trainer = ModelTrainer(
        mlflow_tracking_uri=args.mlflow_uri,
        experiment_name=args.experiment_name
    )

    # Train model
    results = trainer.train(
        data_path=args.data_path,
        epochs=args.epochs,
        batch_size=args.batch_size,
        hidden_layers=hidden_layers,
        dropout_rate=args.dropout_rate
    )

    # Save results for pipeline
    with open('/tmp/training_results.json', 'w') as f:
        json.dump(results, f, indent=2)

    logger.info("Training job completed successfully")


if __name__ == "__main__":
    main()

Step 2: Kubeflow Pipeline Definition

Create pipelines/training_pipeline.py:

"""
Kubeflow Training Pipeline

Orchestrates ML training workflow with automated validation,
model registration, and deployment decisions.
"""

from typing import NamedTuple
from kfp import dsl
from kfp import compiler
from kfp.dsl import component, pipeline, Output, Input, Artifact, Model, Metrics


@component(
    base_image="python:3.11-slim",
    packages_to_install=["pandas==2.1.0", "scikit-learn==1.3.0"]
)
def validate_data(
    data_path: str,
    validation_report: Output[Artifact]
) -> NamedTuple('Outputs', [('valid', bool), ('num_samples', int)]):
    """Validate input data quality and format"""
    import pandas as pd
    import json

    # Load data
    df = pd.read_csv(data_path)

    # Validation checks
    checks = {
        "has_data": len(df) > 0,
        "no_nulls": df.isnull().sum().sum() == 0,
        "balanced": abs(df.iloc[:, -1].mean() - 0.5) < 0.2
    }

    valid = all(checks.values())
    num_samples = len(df)

    # Save validation report
    report = {
        "valid": valid,
        "num_samples": num_samples,
        "checks": checks
    }

    with open(validation_report.path, 'w') as f:
        json.dump(report, f, indent=2)

    from collections import namedtuple
    output = namedtuple('Outputs', ['valid', 'num_samples'])
    return output(valid, num_samples)


@component(
    base_image="tensorflow/tensorflow:2.14.0",
    packages_to_install=["mlflow==2.9.0", "scikit-learn==1.3.0"]
)
def train_model(
    data_path: str,
    mlflow_uri: str,
    experiment_name: str,
    epochs: int,
    batch_size: int,
    model_artifact: Output[Model],
    metrics_artifact: Output[Metrics]
) -> NamedTuple('Outputs', [('run_id', str), ('accuracy', float)]):
    """Train ML model with experiment tracking"""
    import sys
    sys.path.append('/app/src')

    from training.model import ModelTrainer
    import json

    # Initialize trainer
    trainer = ModelTrainer(
        mlflow_tracking_uri=mlflow_uri,
        experiment_name=experiment_name
    )

    # Train model
    results = trainer.train(
        data_path=data_path,
        epochs=epochs,
        batch_size=batch_size
    )

    # Save model URI
    with open(model_artifact.path, 'w') as f:
        f.write(results['model_uri'])

    # Save metrics
    with open(metrics_artifact.path, 'w') as f:
        json.dump(results['metrics'], f)

    from collections import namedtuple
    output = namedtuple('Outputs', ['run_id', 'accuracy'])
    return output(
        results['run_id'],
        results['metrics']['val_accuracy']
    )


@component(base_image="python:3.11-slim")
def validate_model(
    accuracy: float,
    threshold: float = 0.85
) -> bool:
    """Validate model meets performance requirements"""
    return accuracy >= threshold


@component(
    base_image="python:3.11-slim",
    packages_to_install=["mlflow==2.9.0"]
)
def register_model(
    run_id: str,
    mlflow_uri: str,
    model_name: str,
    stage: str = "Staging"
):
    """Register model in MLflow Model Registry"""
    import mlflow
    from mlflow.tracking import MlflowClient

    mlflow.set_tracking_uri(mlflow_uri)
    client = MlflowClient()

    # Get model version
    model_uri = f"runs:/{run_id}/model"
    model_details = mlflow.register_model(model_uri, model_name)

    # Transition to staging
    client.transition_model_version_stage(
        name=model_name,
        version=model_details.version,
        stage=stage
    )

    print(f"Registered model {model_name} version {model_details.version}")


@pipeline(
    name="MLOps Training Pipeline",
    description="End-to-end ML training with validation and registration"
)
def training_pipeline(
    data_path: str,
    mlflow_uri: str = "http://mlflow-service.mlflow:5000",
    experiment_name: str = "production-training",
    model_name: str = "production-model",
    epochs: int = 50,
    batch_size: int = 32,
    accuracy_threshold: float = 0.85
):
    """
    Complete MLOps training pipeline.

    Steps:
    1. Validate input data
    2. Train model with experiment tracking
    3. Validate model performance
    4. Register model if validation passes
    """

    # Step 1: Validate data
    validation_task = validate_data(data_path=data_path)

    # Step 2: Train model (only if validation passes)
    with dsl.Condition(validation_task.outputs['valid'] == True):
        training_task = train_model(
            data_path=data_path,
            mlflow_uri=mlflow_uri,
            experiment_name=experiment_name,
            epochs=epochs,
            batch_size=batch_size
        )

        # Step 3: Validate model performance
        validation_result = validate_model(
            accuracy=training_task.outputs['accuracy'],
            threshold=accuracy_threshold
        )

        # Step 4: Register model if it passes validation
        with dsl.Condition(validation_result.output == True):
            register_model(
                run_id=training_task.outputs['run_id'],
                mlflow_uri=mlflow_uri,
                model_name=model_name,
                stage="Staging"
            )


if __name__ == "__main__":
    # Compile pipeline
    compiler.Compiler().compile(
        pipeline_func=training_pipeline,
        package_path='training_pipeline.yaml'
    )
    print("Pipeline compiled successfully!")

Step 3: Model Serving Configuration

Create k8s/base/inference-service.yaml for KServe deployment:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: production-model
  namespace: kubeflow-user-example-com
spec:
  predictor:
    minReplicas: 2
    maxReplicas: 10
    scaleTarget: 80 # Target 80% CPU utilization
    scaleMetric: cpu
    model:
      modelFormat:
        name: tensorflow
      runtime: kserve-tensorflow
      storageUri: 'gs://my-bucket/models/production-model' # Update with your path
      resources:
        requests:
          cpu: '1'
          memory: '2Gi'
        limits:
          cpu: '2'
          memory: '4Gi'
      env:
        - name: MLFLOW_TRACKING_URI
          value: 'http://mlflow-service.mlflow:5000'
---
apiVersion: v1
kind: Service
metadata:
  name: production-model-predictor
  namespace: kubeflow-user-example-com
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    serving.kserve.io/inferenceservice: production-model
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: production-model-route
  namespace: kubeflow-user-example-com
spec:
  hosts:
    - production-model.kubeflow-user-example-com.example.com
  gateways:
    - kubeflow-gateway.kubeflow.svc.cluster.local
  http:
    - match:
        - uri:
            prefix: /v1/models/production-model
      route:
        - destination:
            host: production-model-predictor.kubeflow-user-example-com.svc.cluster.local
            port:
              number: 80
          weight: 100

Due to length constraints, the complete implementation including monitoring setup, testing strategies, CI/CD integration, and production deployment patterns is available in the GitHub repository.

Advertisement

Testing & Validation

Pipeline Testing

Create tests/test_pipeline.py:

"""
Integration tests for MLOps pipeline components
"""

import pytest
import numpy as np
from kfp import dsl
from kfp.client import Client


def test_pipeline_compilation():
    """Test pipeline compiles without errors"""
    from pipelines.training_pipeline import training_pipeline
    from kfp import compiler

    compiler.Compiler().compile(
        pipeline_func=training_pipeline,
        package_path='/tmp/test_pipeline.yaml'
    )

    # Verify file was created
    import os
    assert os.path.exists('/tmp/test_pipeline.yaml')


def test_data_validation():
    """Test data validation component"""
    # Create test data
    import pandas as pd
    import tempfile

    df = pd.DataFrame({
        'feature1': np.random.randn(100),
        'feature2': np.random.randn(100),
        'label': np.random.randint(0, 2, 100)
    })

    with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f:
        df.to_csv(f.name, index=False)
        data_path = f.name

    # Test validation passes
    # This would call the validation component
    assert True  # Placeholder for actual test


@pytest.mark.integration
def test_end_to_end_pipeline():
    """Test complete pipeline execution"""
    # This requires a running Kubeflow cluster
    # Skip in CI environments
    pytest.skip("Requires Kubeflow cluster")

Deployment & Production Considerations

CI/CD Integration

Create .github/workflows/mlops-pipeline.yml:

name: MLOps Pipeline CI/CD

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

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

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

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov

      - name: Run tests
        run: |
          pytest tests/ -v --cov=src

      - name: Compile pipeline
        run: |
          python pipelines/training_pipeline.py

      - name: Upload pipeline artifact
        uses: actions/upload-artifact@v3
        with:
          name: compiled-pipeline
          path: training_pipeline.yaml

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Configure kubectl
        uses: azure/k8s-set-context@v3
        with:
          method: kubeconfig
          kubeconfig: ${{ secrets.KUBE_CONFIG }}

      - name: Deploy to Kubernetes
        run: |
          kubectl apply -f k8s/base/

Next Steps & Advanced Topics

Performance Optimization

Distributed Training: Implement distributed training using Kubeflow Training Operator for large models:

  • Multi-GPU training with TensorFlow distributed strategies
  • Parameter server architecture for massive datasets
  • Gradient accumulation for memory-constrained environments

Model Optimization: Apply optimization techniques before deployment:

  • Quantization to reduce model size by 4x
  • Pruning to remove unnecessary parameters
  • Knowledge distillation for faster inference

Advanced Features

A/B Testing: Implement canary deployments with KServe:

  • Route 10% traffic to new model version
  • Compare performance metrics automatically
  • Gradual rollout based on success criteria

Feature Stores: Integrate Feast for feature management:

  • Centralized feature definitions
  • Online and offline feature serving
  • Time-travel capabilities for training data

AutoML Integration: Add hyperparameter tuning with Katib:

  • Automated architecture search
  • Bayesian optimization for hyperparameters
  • Multi-objective optimization

Conclusion

Building production MLOps pipelines on Kubernetes requires more than just model training code. The system we've built demonstrates enterprise-grade patterns:

  • Automated workflows that reduce manual intervention
  • Experiment tracking for reproducibility and compliance
  • Model validation to ensure quality before deployment
  • Scalable serving with auto-scaling and load balancing
  • Monitoring and observability for production reliability

The complete implementation with monitoring dashboards, security configurations, and advanced deployment strategies is available in the GitHub repository.

From deploying MLOps platforms across multiple enterprises, successful implementations share these characteristics:

  1. Start with manual processes: Automate incrementally as patterns emerge
  2. Instrument everything: Comprehensive logging and metrics enable debugging
  3. Plan for failures: Implement rollback procedures and fallback strategies
  4. Iterate on feedback: Continuously improve based on user needs

The MLOps landscape evolves rapidly. Stay current by monitoring Kubeflow updates, CNCF MLOps Working Group, and MLOps.org community.

For related topics, see our guides on AI Governance Frameworks and LLM Guardrails Implementation.

Remember: MLOps isn't just about technology—it's about enabling data scientists and ML engineers to deliver value faster while maintaining reliability and compliance. With proper pipeline automation, your organization can deploy ML models confidently at scale.

Advertisement

Was this article helpful?

Your feedback helps us improve our content and create more valuable resources

We appreciate honest feedback - it helps us serve you better

Work with us

This analysis is what we do for clients

CrashBytes consults on enterprise AI strategy and implementation, builds custom web and mobile software, and places senior engineers on corp-to-corp engagements.

See Services

Enjoyed this? Get the next one.

Join developers getting CrashBytes articles, tutorials, and predictions in their inbox. No spam, unsubscribe anytime.

Related Topics

tutorialhands-onmlopskubernetesmachine-learningkubeflowproduction-deploymentml-engineeringdevopsci-cdmodel-deploymentbest-practicesartificial intelligencecode-examples
Back to Articles
← PreviousEnterprise AI Security and Resilience: Building Crisis-Ready Infrastructure for Mission-Critical Systems in 2025Next →Quantum Networking Strategic Impact: How Quantum Communication Reshapes Enterprise Security and Infrastructure

From across the CrashBytes network

More than the blog — predictions, news, fiction, and AI art.

PredictionCustom AI Chips Reach Commodity Status by Q4 2027: Cloud Provider Competition Drives Democratization
NewsWeek In Review July 19-25, 2026 - The Week The Money Moved To The Metering Layer
Short StoryThe Answer Key
AI ArtThe Room That Remembers

Continue Your Learning Journey

Explore more articles related to tutorial and expand your knowledge.

📄tutorial

Tutorial: Enterprise AI Model Monitoring and Observability in Production Kubernetes Environments

Learn to build production-grade AI model monitoring with drift detection, performance tracking, and automated alerting. Complete implementation with Prometheus, Grafana, and Kubernetes deployment patterns.

18 min readRead more
📄tutorial

Tutorial: Building Distributed ML Training Pipelines with Horovod and PyTorch for Multi-GPU Environments

Learn to build production-scale distributed training systems with Horovod and PyTorch. Complete implementation with gradient aggregation, fault tolerance, and efficient data loading for enterprise ML.

20 min readRead more
📄tutorial

Tutorial: Building Production-Ready LLM Guardrails with Python and FastAPI

Learn to build enterprise-grade LLM guardrails with content filtering, PII detection, toxicity scoring, and rate limiting. Complete implementation with monitoring, testing, and deployment strategies.

18 min readRead more
📄tutorial

Tutorial: GitHub Actions CI/CD Complete Guide - Workflow Automation from Zero to Production

Master GitHub Actions from scratch with hands-on examples. Build complete CI/CD pipelines with testing, linting, deployment, and advanced workflow patterns for modern development teams.

22 min readRead more