Veröffentlicht am

GPU Auto-Scaling in Kubernetes mit HPA und KEDA

Teilen:
Authors

TL;DR

  • Standard-HPA skaliert nur auf CPU/Memory -- fuer GPUs braucht ihr Custom Metrics (DCGM Exporter + Prometheus Adapter)
  • KEDA vereinfacht event-basiertes Scaling und unterstuetzt GPU-Metriken, Queue-Laengen und Cron-Schedules
  • Spot/Preemptible Instances sparen 60-70% bei GPU-Kosten, erfordern aber Checkpointing
  • GPU-Nodes immer mit Taints und Tolerations schuetzen, damit nur GPU-Workloads dort landen
  • Time-Slicing und MIG (Multi-Instance GPU) erhoehen die Auslastung bei Inference-Workloads

Das Problem: GPUs sind teuer und schlecht ausgelastet

Eine einzelne NVIDIA A100 kostet bei Cloud-Providern zwischen 2-4 EUR/Stunde. Trotzdem liegen GPU-Auslastungsraten in der Praxis oft unter 30%. Die Gruende:

  • Training-Jobs laufen nur zu bestimmten Zeiten
  • Inference-Services haben stark schwankende Last
  • Standard-Autoscaling reagiert nicht auf GPU-spezifische Metriken
  • Teams provisionieren "auf Verdacht" statt bedarfsgerecht

Die Loesung: GPU-aware Autoscaling, das auf tatsaechliche GPU-Auslastung reagiert.

GPU-Metriken verfuegbar machen

Bevor ihr skalieren koennt, muss Prometheus GPU-Metriken kennen. Dafuer sorgt der NVIDIA DCGM Exporter.

# NVIDIA Device Plugin installieren (falls noch nicht vorhanden)
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.3/nvidia-device-plugin.yml

# DCGM Exporter fuer GPU-Metriken
helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts
helm repo update

helm install dcgm-exporter gpu-helm-charts/dcgm-exporter \
  --namespace gpu-monitoring \
  --create-namespace \
  --set serviceMonitor.enabled=true

Damit stehen Metriken wie DCGM_FI_DEV_GPU_UTIL (GPU-Auslastung), DCGM_FI_DEV_FB_USED (GPU-Memory) und DCGM_FI_DEV_POWER_USAGE (Stromverbrauch) in Prometheus zur Verfuegung.

HPA mit Custom GPU Metrics

Der Horizontal Pod Autoscaler kann ueber den Prometheus Adapter auf beliebige Prometheus-Metriken reagieren -- auch GPU-Metriken.

Prometheus Adapter konfigurieren

# prometheus-adapter-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-adapter-config
  namespace: monitoring
data:
  config.yaml: |
    rules:
      - seriesQuery: 'DCGM_FI_DEV_GPU_UTIL{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: "namespace"}
            pod: {resource: "pod"}
        name:
          matches: "DCGM_FI_DEV_GPU_UTIL"
          as: "gpu_utilization"
        metricsQuery: 'avg(DCGM_FI_DEV_GPU_UTIL{<<.LabelMatchers>>}) by (<<.GroupBy>>)'
      - seriesQuery: 'DCGM_FI_DEV_FB_USED{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: "namespace"}
            pod: {resource: "pod"}
        name:
          matches: "DCGM_FI_DEV_FB_USED"
          as: "gpu_memory_used"
        metricsQuery: 'avg(DCGM_FI_DEV_FB_USED{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

HPA mit GPU-Auslastung

# hpa-gpu-inference.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: inference-service-hpa
  namespace: ml-production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: inference-service
  minReplicas: 1
  maxReplicas: 8
  metrics:
    - type: Pods
      pods:
        metric:
          name: gpu_utilization
        target:
          type: AverageValue
          averageValue: "70"   # Skaliert hoch ab 70% GPU-Auslastung
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120   # Max 2 Pods alle 2 Min
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 300   # Max 1 Pod alle 5 Min runter

Warum das behavior-Feld wichtig ist: GPU-Pods brauchen oft 1-2 Minuten zum Starten (Modell laden, CUDA initialisieren). Zu aggressives Hoch- oder Runterskalieren fuehrt zu Thrashing. Die stabilizationWindowSeconds verhindern das.

KEDA fuer event-basiertes GPU-Scaling

KEDA (Kubernetes Event-Driven Autoscaling) ist flexibler als der Standard-HPA und unterstuetzt ueber 60 Scaler -- darunter Prometheus, RabbitMQ, Kafka und Cron.

# KEDA installieren
helm repo add kedacore https://kedacore.github.io/charts
helm repo update

helm install keda kedacore/keda \
  --namespace keda \
  --create-namespace

KEDA ScaledObject: Queue-basiertes Scaling

Typischer ML-Use-Case: Inference-Requests landen in einer Queue. KEDA skaliert basierend auf der Queue-Laenge.

# keda-scaledobject-inference.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: inference-queue-scaler
  namespace: ml-production
spec:
  scaleTargetRef:
    name: inference-worker
  minReplicaCount: 0          # Scale-to-Zero moeglich!
  maxReplicaCount: 10
  pollingInterval: 15
  cooldownPeriod: 300
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://monitoring-kube-prometheus-prometheus.monitoring:9090
        metricName: inference_queue_length
        query: 'sum(rabbitmq_queue_messages{queue="inference-requests"})'
        threshold: "5"         # 1 Pod pro 5 Messages in der Queue
    - type: cron
      metadata:
        timezone: Europe/Berlin
        start: "0 8 * * 1-5"  # Mo-Fr 08:00 mindestens 2 Replicas
        end: "0 20 * * 1-5"   # Mo-Fr 20:00 zurueck auf Minimum
        desiredReplicas: "2"

KEDA ScaledObject: GPU-Auslastung

# keda-scaledobject-gpu.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: gpu-utilization-scaler
  namespace: ml-production
spec:
  scaleTargetRef:
    name: training-job-dispatcher
  minReplicaCount: 1
  maxReplicaCount: 6
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://monitoring-kube-prometheus-prometheus.monitoring:9090
        metricName: avg_gpu_utilization
        query: 'avg(DCGM_FI_DEV_GPU_UTIL{namespace="ml-production"})'
        threshold: "80"

Vorteil von KEDA gegenueber Standard-HPA: Scale-to-Zero. Wenn keine Inference-Requests anstehen, laeuft kein einziger GPU-Pod -- das spart erhebliche Kosten.

Kostenoptimierung: Spot Instances fuer GPUs

Spot/Preemptible Instances bieten massive Einsparungen, erfordern aber Vorbereitung.

Kostenvergleich GPU-Instanzen (Stand 2026, eu-central-1)

GPU-TypOn-Demand/StundeSpot/StundeErsparnisGeeignet fuer
NVIDIA T4~0,60 EUR~0,20 EUR~67%Inference, leichtes Training
NVIDIA A10G~1,50 EUR~0,50 EUR~67%Mixed Workloads
NVIDIA A100 (40G)~3,80 EUR~1,30 EUR~66%Grosses Training
NVIDIA H100~8,00 EUR~3,00 EUR~63%LLM-Training, HPC

Spot-Nodes im Cluster einrichten

# gpu-spot-nodepool.yaml (Beispiel fuer Karpenter)
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: gpu-spot-pool
spec:
  template:
    spec:
      requirements:
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["spot"]
        - key: "node.kubernetes.io/instance-type"
          operator: In
          values: ["g5.xlarge", "g5.2xlarge", "g5.4xlarge"]
        - key: "nvidia.com/gpu"
          operator: Exists
      taints:
        - key: nvidia.com/gpu
          value: "true"
          effect: NoSchedule
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h
  limits:
    cpu: "128"
    memory: 512Gi
    nvidia.com/gpu: "16"

GPU-Workload mit Spot-Tolerierung und Checkpointing

# training-job-spot.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: model-training
  namespace: ml-training
spec:
  backoffLimit: 3
  template:
    spec:
      tolerations:
        - key: nvidia.com/gpu
          operator: Equal
          value: "true"
          effect: NoSchedule
      nodeSelector:
        karpenter.sh/capacity-type: spot
      containers:
        - name: trainer
          image: registry.example.com/ml-trainer:v2.1
          resources:
            requests:
              nvidia.com/gpu: 1
              memory: "16Gi"
              cpu: "4"
            limits:
              nvidia.com/gpu: 1
              memory: "32Gi"
          env:
            - name: CHECKPOINT_DIR
              value: "/checkpoints"
            - name: CHECKPOINT_INTERVAL
              value: "300"     # Alle 5 Min speichern
          volumeMounts:
            - name: checkpoints
              mountPath: /checkpoints
      volumes:
        - name: checkpoints
          persistentVolumeClaim:
            claimName: training-checkpoints   # Ueberlebt Pod-Eviction
      terminationGracePeriodSeconds: 120
      restartPolicy: OnFailure

Checkpointing ist Pflicht bei Spot: Spot-Instances koennen jederzeit entzogen werden. Ohne regelmaessige Checkpoints verliert ihr den gesamten Trainingsfortschritt.

GPU-Auslastung verbessern: Time-Slicing und MIG

Fuer Inference-Workloads, die keine volle GPU benoetigen, gibt es zwei Ansaetze:

NVIDIA Time-Slicing

Mehrere Pods teilen sich eine GPU ueber zeitliches Multiplexing. Einfach einzurichten, aber ohne Memory-Isolation.

# time-slicing-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nvidia-device-plugin-config
  namespace: kube-system
data:
  config.yaml: |
    version: v1
    sharing:
      timeSlicing:
        resources:
          - name: nvidia.com/gpu
            replicas: 4        # 4 Pods pro GPU

NVIDIA MIG (Multi-Instance GPU, nur A100/H100)

Partitioniert die GPU physisch -- mit echter Memory- und Compute-Isolation.

FeatureTime-SlicingMIG
GPU-SupportAlle NVIDIA GPUsA100, H100
Memory-IsolationNeinJa
Compute-IsolationNeinJa
Max. PartitionenBeliebig (Software)7 (Hardware)
Geeignet fuerDev/Test, leichte InferenceProduction Inference, Multi-Tenant

Taints, Tolerations und Node Affinity

GPU-Nodes sollten immer mit einem Taint geschuetzt werden, damit regulaere Pods nicht dort scheduled werden.

# gpu-deployment-with-affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inference-service
  namespace: ml-production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: inference-service
  template:
    metadata:
      labels:
        app: inference-service
    spec:
      tolerations:
        - key: nvidia.com/gpu
          operator: Equal
          value: "true"
          effect: NoSchedule
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: nvidia.com/gpu.product
                    operator: In
                    values:
                      - NVIDIA-A10G
                      - NVIDIA-T4
      containers:
        - name: inference
          image: registry.example.com/inference:v1.5
          resources:
            requests:
              nvidia.com/gpu: 1
            limits:
              nvidia.com/gpu: 1
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 9090
              name: metrics

Monitoring: GPU-spezifische Dashboards

Ergaenzend zu eurem Kubernetes Monitoring Stack solltet ihr GPU-spezifische Metriken ueberwachen:

# gpu-prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: gpu-alerts
  namespace: monitoring
spec:
  groups:
    - name: gpu-alerts
      rules:
        - alert: GPUHighTemperature
          expr: DCGM_FI_DEV_GPU_TEMP > 85
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "GPU Temperatur ueber 85C auf {{ $labels.instance }}"

        - alert: GPUMemoryNearFull
          expr: (DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE) > 0.9
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "GPU Memory fast voll auf {{ $labels.instance }}"

        - alert: GPULowUtilization
          expr: avg_over_time(DCGM_FI_DEV_GPU_UTIL[1h]) < 10
          for: 2h
          labels:
            severity: info
          annotations:
            summary: "GPU auf {{ $labels.instance }} ist seit 2h kaum ausgelastet -- Kosten pruefen"

Checkliste: GPU-Scaling produktionsreif

  • NVIDIA Device Plugin und DCGM Exporter deployed
  • GPU-Metriken in Prometheus verfuegbar
  • Taints auf GPU-Nodes gesetzt
  • HPA oder KEDA mit GPU-Metriken konfiguriert
  • Spot-Instances evaluiert und Checkpointing implementiert
  • Time-Slicing oder MIG fuer Inference-Workloads geprueft
  • GPU-spezifische Alerts eingerichtet
  • Kosten-Monitoring (z.B. Kubecost) fuer GPU-Workloads aktiv

Weitergehende Ressourcen


GPU-Workloads optimieren? Wir helfen Teams dabei, ML-Infrastruktur auf Kubernetes kosteneffizient und skalierbar aufzubauen -- von der GPU-Node-Strategie bis zur Autoscaling-Konfiguration. Kontakt aufnehmen fuer ein unverbindliches Erstgespraech.

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