正在加载,请稍候…

ML Model Deployment with Kubernetes and Seldon Core

Deploy ML models at scale with Kubernetes, Seldon Core, and KServe. Learn canary deployments, A/B testing, shadow mode, and production model serving patterns.

ML Model Deployment with Kubernetes

Seldon Core Deployment

# seldon-deployment.yaml
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
  name: churn-model
  namespace: ml-serving
spec:
  name: churn-predictor
  predictors:
    - name: default
      replicas: 3
      graph:
        name: classifier
        implementation: SKLEARN_SERVER
        modelUri: gs://my-models/churn-v1.2
        envSecretRefName: gcp-credentials
      componentSpecs:
        - spec:
            containers:
              - name: classifier
                resources:
                  requests:
                    cpu: "0.5"
                    memory: "1Gi"
                  limits:
                    cpu: "1"
                    memory: "2Gi"

KServe InferenceService

# kserve-inference.yaml
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: sklearn-classifier
spec:
  predictor:
    sklearn:
      storageUri: "gs://my-models/sklearn/v1"
      resources:
        requests:
          cpu: "500m"
          memory: "1Gi"
  transformer:
    containers:
      - name: preprocessor
        image: myregistry/preprocessor:v1
        resources:
          requests:
            cpu: "250m"
            memory: "512Mi"

Canary Deployment

apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
  name: churn-model-canary
spec:
  predictors:
    - name: main
      replicas: 4
      traffic: 90  # 90% traffic
      graph:
        name: v1
        modelUri: gs://models/churn-v1

    - name: canary
      replicas: 1
      traffic: 10  # 10% canary traffic
      graph:
        name: v2
        modelUri: gs://models/churn-v2

Python Model Server

from seldon_core.python_wrapper import SeldonComponent
import numpy as np
import joblib

class ChurnPredictor(SeldonComponent):
    def __init__(self):
        self.model = None

    def load(self):
        self.model = joblib.load("/mnt/models/model.pkl")
        self.scaler = joblib.load("/mnt/models/scaler.pkl")

    def predict(self, X: np.ndarray, features_names: list = None) -> np.ndarray:
        X_scaled = self.scaler.transform(X)
        probs = self.model.predict_proba(X_scaled)
        return probs

    def health_status(self) -> dict:
        return {"status": "OK", "model_loaded": self.model is not None}

Model Monitoring with Prometheus

from prometheus_client import Counter, Histogram, start_http_server
import time

prediction_counter = Counter("predictions_total", "Total predictions", ["model_version", "outcome"])
prediction_latency = Histogram("prediction_latency_seconds", "Prediction latency")
feature_drift = Gauge("feature_drift_score", "Feature drift score", ["feature"])

class MonitoredPredictor:
    def predict(self, X: np.ndarray) -> np.ndarray:
        start = time.time()

        predictions = self.model.predict(X)

        latency = time.time() - start
        prediction_latency.observe(latency)

        for pred in predictions:
            prediction_counter.labels(
                model_version=self.version,
                outcome=str(pred)
            ).inc()

        # Check feature drift
        for i, col in enumerate(self.feature_names):
            drift = self.drift_detector.score(X[:, i])
            feature_drift.labels(feature=col).set(drift)

        return predictions

Shadow Mode Testing

# Shadow mode: shadow receives traffic but doesn't serve responses
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
spec:
  predictors:
    - name: production
      traffic: 100
      graph:
        name: v1
        implementation: SKLEARN_SERVER
        modelUri: gs://models/v1

    - name: shadow
      shadow: true  # Shadow mode - receives mirrored traffic
      traffic: 0
      graph:
        name: v2
        implementation: SKLEARN_SERVER
        modelUri: gs://models/v2

A/B Testing Framework

import hashlib

class ABTestRouter:
    def __init__(self, experiment_config: dict):
        self.experiments = experiment_config

    def get_variant(self, user_id: str, experiment: str) -> str:
        config = self.experiments.get(experiment, {})
        variants = config.get("variants", ["control"])
        traffic = config.get("traffic", [100])

        # Deterministic routing based on user_id
        hash_val = int(hashlib.md5(f"{user_id}:{experiment}".encode()).hexdigest(), 16) % 100

        cumulative = 0
        for variant, pct in zip(variants, traffic):
            cumulative += pct
            if hash_val < cumulative:
                return variant

        return variants[-1]

    def record_outcome(self, user_id: str, experiment: str, variant: str, metric: float):
        # Store in database for analysis
        pass

router = ABTestRouter({
    "churn_model": {
        "variants": ["control", "treatment"],
        "traffic": [50, 50],
    }
})

variant = router.get_variant("user-123", "churn_model")
# Use appropriate model version based on variant

Deployment Checklist

Stage Check
Pre-deployment Load test, latency benchmarks
Canary Monitor error rates, p99 latency
A/B test Statistical significance, business metrics
Full rollout Gradual traffic increase
Post-deployment Drift monitoring, feedback loops