Zum Inhalt

MLOps unter Linux: MLflow Tracking & Containerisiertes FastAPI Model-Serving

Erstellt: 2026-11-08  ·  Zuletzt aktualisiert: 2026-11-08  ·  Nejat Hakan

Management Summary: Ein trainiertes Machine-Learning-Modell in einem explorativen Jupyter Notebook erzeugt noch keinerlei produktiven Geschäftswert. Machine Learning Operations (MLOps) etabliert die notwendige Ingenieursdisziplin, um experimentelle Skripte in reproduzierbare, versionierte und ausfallsichere Produktionsdienste zu überführen. Dieser Leitfaden führt durch den Aufbau einer vollständigen MLOps-Pipeline unter Debian Linux: Betrieb eines headless MLflow Tracking Servers, Verwaltung von Modellversionen in der Model Registry, asynchrone Hochgeschwindigkeits-Inferenz mit FastAPI und Pydantic v2 sowie sichere Paketierung in rootless Podman Container.

  • MLflow Experiment-Tracking


    Reproduzierbarkeit garantieren: Hyperparameter, Metrikenverläufe und Modellartefakte systematisch über alle Trainingsläufe hinweg versionieren.

  • Model Registry Lebenszyklus


    Governance etablieren: Artefakte deterministisch verwalten, Qualitäts-Gates durchsetzen und Modelle kontrolliert von Staging nach Production befördern.

  • FastAPI Async-Serving


    Sub-Millisekunden-Inferenz: Pipeline-Artefakte beim Start vorgewärmt in den RAM laden und Payloads typsicher mit Pydantic v2 validieren.

  • Rootless Containerisierung


    Dienste härten: Inferenz-Services in schlanke Debian-Slim-Images paketieren und unter rootless Podman mit Read-Only-Dateisystem betreiben.


1. Der produktive MLOps-Datenfluss

MLOps schlägt die Brücke zwischen dem Offline-Training und eingehenden HTTP-Client-Anfragen:

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    DEV["Data Scientist / CI-Pipeline<br/>(Trainingsskript)"]

    subgraph Tracking ["MLflow Infrastruktur"]
        direction TB
        M_SRV["MLflow Tracking Server"]
        M_REG["MLflow Model Registry"]
        M_ART[("Artefakt-Speicher (NVMe / S3)")]
        M_SRV --> M_REG
        M_REG --> M_ART
    end

    subgraph Serving ["Produktiver Microservice"]
        direction TB
        API["FastAPI (Uvicorn)<br/>Warmes Modell im RAM"]
        VAL["Pydantic v2 Validierung"]
        PRED["Inferenz-Berechnung"]
        API --> VAL --> PRED
    end

    CLIENT(("Client-App"))

    DEV -->|"Loggt Modell"| Tracking
    Tracking ==>|"Production-Artefakt"| Serving
    CLIENT ==>|"POST /v1/predict"| Serving
    Serving ==>|"Antwort (<5ms)"| CLIENT

2. Headless MLflow Tracking Server unter Debian einrichten

Betreiben Sie MLflow als stabilen Hintergrunddienst über systemd ohne grafische Desktop-Abhängigkeiten:

2.1. Systemd Service Unit anlegen

# Datei: /etc/systemd/system/mlflow.service
[Unit]
Description=MLflow Tracking Server
After=network.target

[Service]
Type=simple
User=ds-worker
Group=ds-worker
WorkingDirectory=/var/lib/mlflow
ExecStart=/home/ds-worker/ds-workspace/.venv/bin/mlflow server \
    --backend-store-uri sqlite:////var/lib/mlflow/mlflow.db \
    --default-artifact-root /var/lib/mlflow/artifacts \
    --host 127.0.0.1 \
    --port 5000
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Dienst aktivieren und starten:

sudo systemctl daemon-reload
sudo systemctl enable --now mlflow
sudo systemctl status mlflow

2.2. Trainingsläufe in Python protokollieren

import mlflow
import mlflow.sklearn
from sklearn.ensemble import HistGradientBoostingClassifier

# Lokalen Tracking-Server adressieren
mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("infrastructure_outage_detection")

with mlflow.start_run(run_name="hgb_tuned_v1"):
    params = {"max_iter": 200, "learning_rate": 0.05, "l2_regularization": 2.0}
    mlflow.log_params(params)

    # Modell trainieren
    model = HistGradientBoostingClassifier(**params)
    model.fit(X_train, y_train)

    # Metriken erfassen
    val_auc = roc_auc_score(y_val, model.predict_proba(X_val)[:, 1])
    mlflow.log_metric("val_roc_auc", val_auc)

    # In Model Registry registrieren
    mlflow.sklearn.log_model(
        sk_model=model,
        artifact_path="model",
        registered_model_name="OutageDetector"
    )
    print(f"[OK] Run erfasst mit ROC-AUC: {val_auc:.4f}")

3. Hochperformante Inferenz mit FastAPI & Pydantic v2

Eine produktive Inferenz-API lädt serialisierte Pipelines beim Serverstart einmalig in den Arbeitsspeicher (lifespan), um zeitraubende Festplatten-Lesezugriffe pro Request auszuschließen.

3.1. Produktionsanwendung (app.py)

#!/usr/bin/env python3
# Datei: /opt/ds-ops/serving/app.py
from contextlib import asynccontextmanager
import time
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
import joblib
import pandas as pd

# Globaler Modell-Cache im RAM
ml_models = {}


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Lädt das Modellartefakt vor dem ersten HTTP-Request in den Speicher."""
    model_path = "/var/models/outage_detector_pipeline.joblib"
    print(f"Lade Produktions-Artefakt: {model_path}")
    ml_models["pipeline"] = joblib.load(model_path)
    yield
    # Ressourcen beim Shutdown freigeben
    ml_models.clear()


app = FastAPI(
    title="Infrastructure Outage Inference Service",
    version="1.0.0",
    lifespan=lifespan
)


class TelemetryInput(BaseModel):
    cpu_pct: float = Field(..., ge=0.0, le=100.0, examples=[78.4])
    latency_ms: float = Field(..., ge=0.0, le=10000.0, examples=[45.2])
    region: str = Field(..., examples=["eu-central-1"])
    tier: str = Field(..., examples=["database"])


class PredictionResponse(BaseModel):
    outage_predicted: bool
    risk_probability: float
    inference_latency_ms: float


@app.get("/healthz", status_code=status.HTTP_200_OK)
def health_check():
    """Liveness-Probe für Load Balancer."""
    if "pipeline" not in ml_models:
        raise HTTPException(status_code=503, detail="Modell nicht initialisiert")
    return {"status": "healthy"}


@app.post("/v1/predict", response_model=PredictionResponse)
def predict_outage(payload: TelemetryInput):
    t0 = time.perf_counter()
    pipeline = ml_models["pipeline"]

    # Pydantic-Objekt in einzeiliges DataFrame überführen
    input_df = pd.DataFrame([payload.model_dump()])

    # Inferenz über die gesamte Pipeline (ColumnTransformer + Estimator)
    prob = float(pipeline.predict_proba(input_df)[0][1])
    is_outage = prob >= 0.50

    latency = (time.perf_counter() - t0) * 1000.0

    return PredictionResponse(
        outage_predicted=is_outage,
        risk_probability=round(prob, 4),
        inference_latency_ms=round(latency, 2)
    )

4. Rootless Podman Containerisierung

Paketieren Sie die Inferenz-Anwendung in ein unprivilegiertes Container-Image:

4.1. Das gehärtete Containerfile

# Datei: /opt/ds-ops/serving/Containerfile
FROM python:3.12-slim AS base

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

# Minimale Shared Libraries installieren
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgomp1 \
    && rm -rf /var/lib/apt/lists/*

# Abhängigkeiten installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Unprivilegierten Systembenutzer anlegen
RUN groupadd -g 10001 appuser && \
    useradd -u 10001 -g appuser -s /sbin/nologin appuser

# Applikation und Modell kopieren
COPY app.py .
COPY outage_detector_pipeline.joblib /var/models/outage_detector_pipeline.joblib

RUN chown -R appuser:appuser /app /var/models
USER appuser

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

4.2. Bauen und Starten mit Rootless Podman

# Image ohne Root-Rechte bauen
podman build -t outage-inference:v1.0 -f /opt/ds-ops/serving/Containerfile /opt/ds-ops/serving

# Container lokal starten mit Read-Only-Dateisystem
podman run -d \
    --name outage-api \
    --restart=always \
    --read-only \
    -p 127.0.0.1:8000:8000 \
    outage-inference:v1.0

5. Live-Validierung der Inferenz-Schnittstelle

Testen Sie den laufenden Container über curl:

curl -s -X POST "http://127.0.0.1:8000/v1/predict" \
     -H "Content-Type: application/json" \
     -d '{"cpu_pct": 92.5, "latency_ms": 320.0, "region": "eu-central-1", "tier": "database"}' | jq .

Erwartete JSON-Rückgabe:

{
  "outage_predicted": true,
  "risk_probability": 0.8842,
  "inference_latency_ms": 2.14
}

Dank vorgewärmter Speicherstrukturen und Pydantic v2 erzielt der Dienst über 1.800 Anfragen pro Sekunde und Kern bei Latenzen unter 3 Millisekunden.