Veröffentlicht am

YOLO Inference auf Kubernetes: GPU-Scheduling und Autoscaling

Teilen:
Authors

TL;DR

  • YOLO-Inference laeuft als Kubernetes-Deployment mit GPU-Requests ueber das NVIDIA Device Plugin. Jeder Pod fordert exakt eine GPU an.
  • Horizontal Pod Autoscaler (HPA) skaliert Inference-Pods basierend auf Custom Metrics wie GPU-Auslastung oder Queue-Laenge statt CPU.
  • Triton Inference Server oder TorchServe als Model-Serving-Layer entkoppeln das Modell vom Applikationscode und ermoeglichen A/B-Testing zwischen Modellversionen.
  • Fuer Latenz-sensitive Anwendungen (unter 100ms pro Frame) ist ein dedizierter GPU-Node-Pool mit Taints der sauberste Ansatz.
  • MinIO als S3-kompatibler Object Store im Cluster speichert Modell-Artefakte und Inference-Ergebnisse On-Premise.

Ausgangslage: Warum YOLO auf Kubernetes

YOLO (You Only Look Once) ist der De-facto-Standard fuer Echtzeit-Objekterkennung. YOLOv8 und seine Nachfolger erreichen bei 640x640 Pixel Eingabegroesse Inferenzzeiten von 5-15ms auf einer NVIDIA T4 -- schnell genug fuer 30fps Video-Streams.

Das Problem beginnt, wenn die Loesung skalieren soll. Ein einzelner GPU-Server mit einem Python-Skript funktioniert im Labor. In der Produktion braucht man Hochverfuegbarkeit, automatisches Scaling bei Lastspitzen, Rolling Updates ohne Downtime und reproduzierbare Deployments. Genau das liefert Kubernetes.

Typische Anwendungsfaelle, die wir in der Praxis sehen: Qualitaetskontrolle auf Fertigungslinien (Kratzer, Risse, Fehlteile), automatische Inventarisierung in der Logistik, Sicherheitsmonitoring auf Betriebsgelaenden und Anomalie-Erkennung in der Lebensmittelproduktion.

GPU-Setup: NVIDIA Device Plugin und Node-Konfiguration

Bevor Kubernetes GPUs an Pods vergeben kann, muss das NVIDIA Device Plugin installiert sein. Es registriert GPUs als Extended Resources (nvidia.com/gpu) beim Kubelet. Die Installation laeuft als DaemonSet.

Voraussetzungen auf den GPU-Nodes:

  • NVIDIA-Treiber (mindestens 525.x fuer CUDA 12)
  • NVIDIA Container Toolkit (ehemals nvidia-docker2)
  • containerd als Container Runtime mit NVIDIA-Runtime konfiguriert
# NVIDIA Device Plugin als DaemonSet installieren
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml

# Pruefen ob GPUs erkannt werden
kubectl describe node gpu-worker-01 | grep -A5 "Allocatable"
# Erwartete Ausgabe enthaelt: nvidia.com/gpu: 2

# GPU-Nodes labeln fuer gezielte Scheduling-Entscheidungen
kubectl label nodes gpu-worker-01 gpu-worker-02 \
  accelerator=nvidia-t4 \
  node-role=gpu-inference

# Taint setzen, damit nur GPU-Workloads hier landen
kubectl taint nodes gpu-worker-01 gpu-worker-02 \
  gpu-only=true:NoSchedule

Der Taint stellt sicher, dass keine CPU-only-Pods auf den teuren GPU-Nodes landen. Inference-Pods muessen eine entsprechende Toleration mitbringen. Details zum GPU-Cluster-Setup beschreibt der Artikel GPU-Workloads auf Kubernetes.

Deployment-Manifest: YOLO Inference Service

Das folgende Manifest deployt einen YOLO-Inference-Service mit FastAPI als HTTP-Endpunkt. Es nutzt eine GPU, hat Health Checks und ist fuer Rolling Updates konfiguriert.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: yolo-inference
  namespace: cv-production
  labels:
    app: yolo-inference
    model-version: yolov8s-v2
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: yolo-inference
  template:
    metadata:
      labels:
        app: yolo-inference
        model-version: yolov8s-v2
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/metrics"
    spec:
      tolerations:
      - key: "gpu-only"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"
      nodeSelector:
        accelerator: nvidia-t4
      containers:
      - name: inference
        image: registry.internal/cv/yolo-serve:2.1.0
        ports:
        - containerPort: 8080
          name: http
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
            nvidia.com/gpu: 1
          limits:
            cpu: "4"
            memory: "8Gi"
            nvidia.com/gpu: 1
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 15
        env:
        - name: MODEL_PATH
          value: "/models/yolov8s-custom.pt"
        - name: CONFIDENCE_THRESHOLD
          value: "0.65"
        - name: DEVICE
          value: "cuda:0"
        - name: MAX_BATCH_SIZE
          value: "8"
        volumeMounts:
        - name: model-store
          mountPath: /models
          readOnly: true
      initContainers:
      - name: model-loader
        image: registry.internal/cv/model-loader:1.0.0
        command: ["python", "download_model.py"]
        env:
        - name: S3_ENDPOINT
          value: "minio.storage.svc.cluster.local:9000"
        - name: MODEL_BUCKET
          value: "cv-models"
        - name: MODEL_KEY
          value: "yolov8s-custom-v2.pt"
        volumeMounts:
        - name: model-store
          mountPath: /models
      volumes:
      - name: model-store
        emptyDir:
          sizeLimit: 2Gi
---
apiVersion: v1
kind: Service
metadata:
  name: yolo-inference
  namespace: cv-production
spec:
  selector:
    app: yolo-inference
  ports:
  - port: 80
    targetPort: 8080
    name: http
  type: ClusterIP

Ein paar Design-Entscheidungen, die hier wichtig sind:

initContainer fuer Model Loading. Das Modell wird beim Pod-Start aus MinIO geladen, nicht ins Container-Image eingebaut. Das haelt die Images klein (unter 2GB statt 5GB+) und erleichtert den Modell-Wechsel ohne Image-Rebuild.

maxUnavailable: 0. Waehrend eines Rolling Updates bleibt immer mindestens die volle Replica-Anzahl verfuegbar. Es wird erst ein neuer Pod hochgefahren, bevor ein alter terminiert wird.

Separate readiness- und liveness-Probe. Das Laden des YOLO-Modells in den GPU-Speicher dauert 15-30 Sekunden. Die readinessProbe hat ein laengeres initialDelaySeconds, damit der Pod erst Traffic bekommt, wenn das Modell tatsaechlich geladen ist.

Autoscaling: HPA mit Custom Metrics

Standard-HPA skaliert nach CPU-Auslastung. Fuer GPU-Workloads ist das unbrauchbar, weil die CPU oft idle ist, waehrend die GPU am Limit laeuft. Stattdessen skalieren wir nach einer Custom Metric: der Anzahl wartender Inference-Requests in der Queue.

Voraussetzung: Prometheus Adapter ist installiert und exponiert Custom Metrics an die Kubernetes Metrics API.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: yolo-inference-hpa
  namespace: cv-production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: yolo-inference
  minReplicas: 2
  maxReplicas: 6
  metrics:
  - type: Pods
    pods:
      metric:
        name: inference_queue_depth
      target:
        type: AverageValue
        averageValue: "5"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 1
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Pods
        value: 1
        periodSeconds: 120

Die scaleDown.stabilizationWindowSeconds: 300 verhindern, dass nach einem kurzen Traffic-Dip sofort Pods entfernt werden. GPU-Pods brauchen 30+ Sekunden zum Starten -- schnelles Hoch- und Runterskalieren ist kontraproduktiv. Mehr zu HPA-Konfigurationen unter Kubernetes HPA.

Modellversionierung und A/B-Testing

In der Praxis laeuft selten nur ein Modell. Oft wird eine neue Modellversion parallel zur bestehenden getestet, bevor sie vollstaendig uebernommen wird. Das laesst sich in Kubernetes ueber zwei Deployments mit unterschiedlichen Labels und gewichteter Traffic-Verteilung ueber einen Ingress Controller oder Service Mesh loesen.

AnsatzVorteileNachteile
Separate Deployments + Ingress-WeightsEinfach, keine Extra-ToolsManuelles Traffic-Splitting
Triton Inference ServerMulti-Model, Batching, MetricsKomplexeres Setup
Seldon Core / KServeKubernetes-native, CRDsHoher Overhead fuer kleine Setups
Canary Deployment (Argo Rollouts)Automatisiert, Metric-basiertSetzt Argo voraus

Fuer die meisten mittelstaendischen Setups empfiehlt sich der Ansatz mit separaten Deployments und einem Label-basierten Service-Switch. Wer mehr Automatisierung will, findet Details unter Canary Deployments.

Storage: Modelle und Ergebnisse

Zwei Storage-Anforderungen dominieren:

Modell-Artefakte (Read-Heavy). YOLO-Modelle sind typisch 20-200MB gross. Sie muessen schnell lesbar sein, werden aber selten geschrieben. MinIO als S3-kompatibler Object Store im Cluster ist ideal. Der initContainer im Deployment laedt das Modell beim Pod-Start.

Inference-Ergebnisse (Write-Heavy). Jeder erkannte Defekt, jedes annotierte Bild muss gespeichert werden -- fuer Audit-Trails, Retraining und Qualitaetsberichte. Hier bietet sich eine Kombination aus PostgreSQL fuer strukturierte Metadaten (Bounding Boxes, Confidence Scores, Timestamps) und MinIO fuer die annotierten Bilder an.

Wer tiefer in Storage-Optionen einsteigen will, findet Vergleiche unter Kubernetes Storage.

Monitoring: Die richtigen Metriken

Ein Inference-Service ohne Monitoring ist ein Blindflug. Folgende Metriken sollten in Prometheus erfasst und in Grafana visualisiert werden:

MetrikBeschreibungAlert-Schwelle
inference_latency_seconds (p95)Zeit pro Inference-Requestueber 200ms
inference_queue_depthWartende Requestsueber 20 (skalieren)
gpu_utilization_percentGPU-Kernauslastungueber 95% (sustained)
gpu_memory_used_bytesGenutzter GPU-Speicherueber 90% der Kapazitaet
model_confidence_avgDurchschnittlicher Confidence Scoreunter 0.4 (Modell-Drift)
inference_errors_totalFehlgeschlagene Requestsueber 1% der Gesamtanfragen

Der Confidence-Score-Durchschnitt ist besonders nuetzlich: Wenn er ueber Zeit sinkt, deutet das auf Model Drift hin -- die Eingabedaten haben sich veraendert und das Modell muss nachtrainiert werden. Ein Artikel zu Monitoring-Best-Practices findet sich unter Kubernetes Monitoring.

YOLO vs. SAM: Wann welches Modell

KriteriumYOLO (v8/v9)SAM (Segment Anything)
AufgabeBounding Box DetectionInstanz-Segmentierung
Inferenzzeit (T4 GPU)5-15ms50-200ms
OutputKlasse + Box + ConfidencePixelgenaue Maske
Typischer Use CaseDefekt-Erkennung, ZaehlungPraezise Konturerfassung
Modellgroesse20-200MB300MB-2.5GB
GPU-Speicherbedarf1-4GB4-12GB
TrainingsaufwandGering (Transfer Learning)Hoch (Foundation Model)

In der Praxis wird oft eine Kombination eingesetzt: YOLO fuer die schnelle Erkennung und Filterung, SAM fuer die praezise Segmentierung der relevanten Detektionen. Das spart GPU-Ressourcen, weil SAM nur auf den relevanten Bildausschnitten laeuft.

Kostenvergleich: On-Premise vs. Cloud GPU

PostenOn-Premise (2x T4)Cloud (2x T4 Spot)Cloud (2x T4 On-Demand)
Hardware/Instanz5.000 EUR einmaligca. 400 EUR/Monatca. 1.200 EUR/Monat
Kubernetes Clustereigenes Ops-TeamManaged (GKE/EKS/AKS)Managed
Stromkostenca. 100 EUR/Monatinklusiveinklusive
Amortisation (Break-Even)--ca. 5 Monate vs. On-Demand
Verfuegbarkeitsrisikokeines (lokal)Spot-Unterbrechungenkeines
Datensouveraenitaetmaximalabhaengig vom Anbieterabhaengig vom Anbieter

Fuer DSGVO-sensitive Workloads (Kamerabilder mit Personen im Bild) ist On-Premise oder ein Rechenzentrum innerhalb der EU oft die pragmatischste Loesung. Wer die Daten vorher anonymisiert (Gesichter blurren, Kennzeichen maskieren), hat mehr Flexibilitaet bei der Standortwahl.

Haeufige Fallstricke

GPU-Speicher-Fragmentierung. Wenn ein Pod abstuerzt und der GPU-Speicher nicht sauber freigegeben wird, kann der naechste Pod keine GPU allozieren. Loesung: nvidia-smi Monitoring und im Notfall Node-Drain.

Zu grosse Container-Images. Ein PyTorch-Image mit CUDA-Runtime wird schnell 8-10GB gross. Das verlaengert Pod-Startzeiten auf Minuten. Nutzen Sie Multi-Stage-Builds und schlanke Base-Images (z.B. nvcr.io/nvidia/pytorch statt vollem Ubuntu).

Kein Batching. Einzelne Bilder durch das Modell zu schicken ist ineffizient. Batching (mehrere Bilder gleichzeitig) verbessert den GPU-Durchsatz um den Faktor 3-5x. Triton Inference Server bringt Batching out-of-the-box mit.

Model-Serving im Applikationscode. Das Modell direkt in der FastAPI-App zu laden funktioniert, skaliert aber schlecht. Fuer Produktionssysteme ist ein dedizierter Model-Serving-Layer (Triton, TorchServe) die bessere Wahl, weil er Modellversionierung, Batching und Multi-Model-Support mitbringt.

Fazit und naechste Schritte

YOLO auf Kubernetes zu betreiben ist keine Raketenwissenschaft, erfordert aber ein paar Entscheidungen, die man frueh richtig treffen sollte: GPU-Node-Konfiguration, Model-Serving-Ansatz, Autoscaling-Strategie und Storage-Architektur.

Der empfohlene Einstieg: Ein einzelnes YOLO-Modell als Deployment mit einem GPU-Pod, MinIO als Model Store, Prometheus fuer Metriken. Wenn das stabil laeuft, kommen HPA, Canary Deployments und ein dedizierter Model-Serving-Layer dazu.

Fuer eine gemeinsame Evaluation Ihres Use Cases oder Unterstuetzung beim Setup koennen Sie uns ueber die Kontaktseite erreichen.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen