Published on

Ollama auf Kubernetes: LLM-Cluster mit Autoscaling

Authors

Ollama im Kubernetes-Cluster deployen: GPU-Scheduling und Autoscaling für produktive LLMs

TL;DR

Ollama auf Kubernetes läuft am stabilsten als StatefulSet mit einem PersistentVolume für die Modelldateien, einem nvidia.com/gpu-Resource-Request und einem GPU-Node-Selector. Das offizielle otwld-Helm-Chart erledigt das in wenigen Zeilen values.yaml. Autoscaling über den Standard-HPA funktioniert nur eingeschränkt, weil Ollama GPU-gebunden ist – der Weg führt über KEDA oder GPU-Metriken.


Ein einzelner Ollama-Server auf einer RTX 4090 bedient in der Praxis 15 bis 20 gleichzeitige Nutzer zuverlässig. Sobald mehr Abteilungen KI-Chat, RAG oder Batch-Zusammenfassungen nutzen, wird der Server zum Flaschenhals – und der klassische Ansatz mit Nginx-Loadbalancer vor mehreren VMs skaliert manuell, nicht selbstheilend. Kubernetes löst genau das: deklarative Pods, automatische Neustarts, rollende Updates und – wenn man es richtig baut – GPU-bewusstes Scheduling. Dieser Artikel zeigt die konkreten Manifeste und Helm-Werte für einen produktiven Cluster.

Wenn Sie zuerst das Grundprinzip eines Load-Balancing-Setups verstehen wollen, ist unsere Ollama-Cluster-Anleitung mit Nginx der bessere Startpunkt. Dieser Text setzt voraus, dass Sie bereits einen laufenden Cluster mit GPU-Nodes haben.

Warum StatefulSet statt Deployment

Die kurze Antwort: wegen der Modelldateien. Ein llama3.1:8b belegt rund 4,7 GB, ein qwen2.5:32b über 20 GB. Diese Dateien wollen Sie nicht bei jedem Pod-Neustart neu ziehen – das kostet Minuten und Bandbreite.

Ein Deployment mit einem gemeinsamen ReadWriteMany-Volume klingt verlockend, führt aber bei parallelem Modell-Pull zu Race Conditions auf dem Dateisystem. Ein StatefulSet gibt jedem Pod über volumeClaimTemplates ein eigenes, stabiles PersistentVolume. Jeder Ollama-Pod hält seine eigene Modell-Kopie, der Zustand überlebt Neustarts, und Sie vermeiden korrupte Blobs.

Unsere Empfehlung: StatefulSet für die Modell-Persistenz, davor ein Service vom Typ ClusterIP als internes Load-Balancing über alle Replicas. Für rein zustandslose Inferenz ohne persistente Modelle geht auch ein Deployment – aber das ist selten das, was Sie im Mittelstand wollen.

Hier das minimale StatefulSet-Manifest mit GPU-Anforderung:

# ollama-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: ollama
  namespace: ollama
spec:
  serviceName: ollama
  replicas: 2
  selector:
    matchLabels:
      app: ollama
  template:
    metadata:
      labels:
        app: ollama
    spec:
      # Pods nur auf GPU-Nodes schedulen
      nodeSelector:
        nvidia.com/gpu.present: "true"
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
      containers:
        - name: ollama
          image: ollama/ollama:latest
          ports:
            - containerPort: 11434
          resources:
            requests:
              memory: "12Gi"
              nvidia.com/gpu: "1"   # eine GPU pro Pod
            limits:
              nvidia.com/gpu: "1"
          volumeMounts:
            - name: models
              mountPath: /root/.ollama
  volumeClaimTemplates:
    - metadata:
        name: models
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 50Gi

Der Service dazu, damit andere Pods im Cluster den Cluster unter ollama.ollama.svc erreichen:

# ollama-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: ollama
  namespace: ollama
spec:
  selector:
    app: ollama
  ports:
    - port: 11434
      targetPort: 11434

GPU-Scheduling: ohne NVIDIA Device Plugin geht nichts

Kubernetes weiß von sich aus nicht, dass ein Node eine GPU hat. nvidia.com/gpu als Resource existiert erst, wenn das NVIDIA Device Plugin läuft – meist über den NVIDIA GPU Operator installiert. Der Operator kümmert sich um Treiber, Container-Toolkit und das Device Plugin in einem Rutsch, was besonders bei EKS- oder GKE-Clustern die Reibung stark reduziert. Auf On-Premises-Clustern müssen Sie den NVIDIA-Container-Toolkit-Layer teils vorinstallieren.

Prüfen Sie zuerst, ob Ihre Nodes GPUs melden:

# Zeigt allocatable GPUs pro Node
kubectl get nodes -o custom-columns=\
NAME:.metadata.name,GPU:.status.allocatable.'nvidia\.com/gpu'

Steht dort eine Zahl statt <none>, ist das Device Plugin aktiv. Falls nicht, installieren Sie den Operator per Helm:

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install --wait gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator --create-namespace

Ein häufiger Fehler: Der Pod bleibt in Pending mit der Meldung Insufficient nvidia.com/gpu. Das bedeutet fast immer, dass alle GPUs bereits belegt sind oder das Device Plugin nicht auf dem Node läuft. Jeder Pod mit nvidia.com/gpu: "1" belegt eine ganze GPU exklusiv – zwei Replicas brauchen also zwei physische GPUs.

Wenn Ihnen das zu teuer ist, teilen Sie eine GPU auf mehrere Pods. Über NVIDIA MIG (Multi-Instance GPU) partitioniert eine A100 oder H100 in bis zu sieben isolierte Instanzen. Wie Sie mehrere Modelle GPU-schonend auf Kubernetes betreiben, zeigt unser GPU-Cluster-Skalierungsguide.

Der schnelle Weg: das otwld-Helm-Chart

Für die meisten Teams ist das Community-Chart von otwld der pragmatischste Einstieg. Es kapselt StatefulSet, Service, GPU-Konfiguration und optionalen Modell-Pull. Installation:

helm repo add otwld https://helm.otwld.com/
helm repo update
helm install ollama otwld/ollama \
  --namespace ollama --create-namespace \
  --values values.yaml

Die zugehörige values.yaml für einen GPU-Cluster, der beim Start zwei Modelle vorlädt:

# values.yaml
ollama:
  gpu:
    enabled: true
    type: 'nvidia'
    number: 1
  models:
    pull:
      - llama3.1:8b
      - qwen2.5:14b

persistentVolume:
  enabled: true
  size: '50Gi'
  accessModes:
    - ReadWriteOnce

# GPU-Nodes gezielt ansteuern
nodeSelector:
  nvidia.com/gpu.present: 'true'

Der Vorteil: Bei einem helm upgrade mit geänderter Modell-Liste startet das Chart einen neuen Bootstrap-Job und lädt die neuen Modelle nach, ohne dass Sie manuell in Pods springen. Der Nachteil: Die eingebaute Autoscaling-Sektion basiert auf CPU-Utilization – und die ist bei Ollama praktisch nutzlos. Dazu gleich mehr.

Ob Sie lieber das Chart oder eigene Manifeste nehmen, hängt vom Team ab:

KriteriumEigene Manifesteotwld-Helm-Chart
Einrichtungszeit1-2 Stunden15 Minuten
Kontrolle über Detailsvollständigüber values.yaml
Modell-Pull automatischselbst baueneingebaut
Upgradesmanuell versionierthelm upgrade
Ideal fürGitOps mit Kustomizeschneller Produktivstart

Unsere Empfehlung: Fangen Sie mit dem Chart an, exportieren Sie das gerenderte YAML per helm template und übernehmen Sie es in Ihr GitOps-Repo, sobald das Setup steht.

Autoscaling: warum der Standard-HPA scheitert

Hier liegt die häufigste Fehlannahme. Der Horizontal Pod Autoscaler skaliert per Default nach CPU- oder Memory-Auslastung. Ollama rechnet aber auf der GPU – die CPU-Last bleibt selbst unter Volllast oft bei 5 bis 10 Prozent. Ein CPU-basierter HPA würde also nie hochskalieren, obwohl die GPU längst am Anschlag ist.

Es gibt drei realistische Wege:

  1. HPA auf Memory-Basis – ein grober Behelf. Wenn Modelle in den RAM geladen werden, korreliert Speicherdruck teils mit Last. Ungenau, aber besser als CPU.
  2. HPA auf GPU-Custom-Metrics – über den DCGM-Exporter von NVIDIA und Prometheus Adapter exponieren Sie DCGM_FI_DEV_GPU_UTIL als Custom Metric. Der HPA skaliert dann bei über 70 Prozent GPU-Auslastung.
  3. KEDA mit Queue-Länge – der sauberste Ansatz für Batch-Workloads. KEDA skaliert nach der Länge einer Anfrage-Queue oder Prometheus-Query, inklusive Scale-to-Zero.

Ein pragmatischer HPA, der auf GPU-Auslastung als Custom Metric reagiert:

# ollama-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ollama
  namespace: ollama
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: ollama
  minReplicas: 1
  maxReplicas: 4
  metrics:
    - type: Pods
      pods:
        metric:
          name: DCGM_FI_DEV_GPU_UTIL   # aus dem DCGM-Exporter
        target:
          type: AverageValue
          averageValue: "70"

Ein Wort zur Realität: Autoscaling für LLM-Inferenz ist teurer und langsamer als bei Web-Diensten. Ein neuer Pod muss das Modell erst in den GPU-Speicher laden – bei einem 14B-Modell sind das je nach Storage 20 bis 60 Sekunden. Wer Antwortzeiten unter einer Sekunde braucht, sollte eher fixe Replicas plus Cluster Autoscaler auf Node-Ebene fahren, statt reaktiv Pods hochzuziehen. Für schwankende Batch-Last dagegen ist KEDA mit Scale-to-Zero Gold wert, weil GPU-Nodes nachts komplett abgeschaltet werden können.

Modelle persistent halten und vorladen

Der zweite große Praxis-Fehler nach dem CPU-HPA: Modelle bei jedem Pod-Start neu ziehen. Bei einem Node-Ausfall oder Rolling Update lädt der neue Pod sonst minutenlang, bevor er Traffic annimmt.

Mit dem StatefulSet-volumeClaimTemplate von oben bleibt das Modell über Neustarts erhalten. Zusätzlich sollten Sie eine Readiness-Probe setzen, damit der Service erst Traffic schickt, wenn das Modell geladen ist:

          readinessProbe:
            httpGet:
              path: /api/tags
              port: 11434
            initialDelaySeconds: 30
            periodSeconds: 10

Zum Vorladen eines Modells nach dem ersten Deploy reicht ein einmaliger Befehl in den laufenden Pod:

# Modell in Pod ollama-0 ziehen, damit es im PV liegt
kubectl exec -it ollama-0 -n ollama -- ollama pull llama3.1:8b

Für Produktion gehört dieser Schritt in einen Init-Container oder – beim Helm-Chart – in die models.pull-Liste, damit er reproduzierbar bleibt.

Was das im Betrieb bedeutet

Ein Maschinenbauer, mit dem wir gesprochen haben, betrieb drei einzelne Ollama-VMs hinter Nginx. Bei jedem Treiber-Update oder Modellwechsel war Handarbeit auf drei Servern nötig, und ein Node-Ausfall bedeutete manuelles Failover. Nach dem Umzug auf einen Kubernetes-Cluster mit StatefulSet und drei GPU-Nodes lief das Failover automatisch, Modell-Updates gingen per helm upgrade in einem Befehl über alle Replicas.

Der ehrliche Gegenpunkt: Kubernetes lohnt sich nicht für jeden. Wenn Sie einen einzelnen GPU-Server für 30 Nutzer betreiben, ist die K8s-Komplexität Overhead ohne Nutzen. Die Nginx-Cluster-Variante ist dann schlicht günstiger im Betrieb. Der Bruch kommt typischerweise, wenn Sie mehr als drei Nodes fahren, mehrere Modelle parallel brauchen oder rollende Updates ohne Downtime erwarten. Ab da spielt Kubernetes seine Stärken aus.

Wer generell zwischen Cloud und Eigenbetrieb abwägt, findet im Self-Hosted-LLM-Kostenguide die TCO-Rechnung für Ollama und vLLM.

Checkliste vor dem Produktivgang

  • GPU-Nodes melden nvidia.com/gpu als allocatable Resource
  • StatefulSet mit volumeClaimTemplates statt geteiltem RWX-Volume
  • nodeSelector und Tolerations auf GPU-Nodes gesetzt
  • Readiness-Probe auf /api/tags, damit kein Traffic auf ungeladene Pods geht
  • Autoscaling über GPU-Metriken oder KEDA, nicht über den CPU-HPA
  • Modelle in models.pull oder Init-Container, nicht manuell per exec

Häufig gestellte Fragen

Brauche ich für Ollama auf Kubernetes zwingend eine GPU?

Nein, Ollama läuft auch CPU-only im Cluster – dann lassen Sie nodeSelector, Tolerations und den nvidia.com/gpu-Request weg. Praktikabel ist das aber nur für kleine Modelle bis etwa 3B Parameter. Für 7B aufwärts sind die Latenzen ohne GPU im zweistelligen Sekundenbereich, was für interaktive Nutzung zu langsam ist.

Warum bleibt mein Ollama-Pod in Pending hängen?

In den allermeisten Fällen lautet die Ursache Insufficient nvidia.com/gpu. Entweder sind alle GPUs im Cluster belegt (jeder Pod belegt eine ganze GPU exklusiv), oder das NVIDIA Device Plugin läuft nicht auf dem Node. Prüfen Sie mit kubectl describe pod die Events und mit kubectl get nodes -o custom-columns die allocatable GPUs.

Sollte ich das Helm-Chart oder eigene Manifeste nehmen?

Für den schnellen Produktivstart das otwld-Helm-Chart – es bringt Modell-Pull und GPU-Konfiguration mit. Wenn Sie GitOps mit Kustomize oder ArgoCD fahren und volle Kontrolle über jedes Feld wollen, sind eigene Manifeste sauberer. Ein guter Kompromiss ist, das Chart einmal per helm template zu rendern und das Ergebnis versioniert abzulegen.

Wie viele Nutzer bedient ein Ollama-Cluster?

Als Faustregel bedient eine RTX-4090-GPU 15 bis 20 gleichzeitige Nutzer bei akzeptabler Latenz. Ein Cluster skaliert linear mit der Zahl der GPU-Pods: drei GPUs bedienen also grob 45 bis 60 Nutzer. Die genaue Zahl hängt stark von Modellgröße und durchschnittlicher Prompt-Länge ab – 32B-Modelle brauchen mehr GPU-Speicher und liefern weniger parallele Streams.

Funktioniert horizontales Autoscaling für LLMs überhaupt sinnvoll?

Bedingt. Der CPU-basierte Standard-HPA ist für Ollama unbrauchbar, weil die Last auf der GPU liegt. Sinnvoll wird es mit GPU-Custom-Metrics über den DCGM-Exporter oder mit KEDA nach Queue-Länge. Rechnen Sie aber mit 20 bis 60 Sekunden Modell-Ladezeit pro neuem Pod – für latenzkritische Dienste sind fixe Replicas oft die bessere Wahl.

Fazit und nächster Schritt

Ollama auf Kubernetes zu deployen ist kein Hexenwerk, sobald das GPU-Scheduling steht: StatefulSet für persistente Modelle, NVIDIA Device Plugin für die GPU-Resource, Helm-Chart für den schnellen Start und GPU-Metriken statt CPU-HPA fürs Autoscaling. Der Aufwand lohnt sich ab drei GPU-Nodes und bei Anspruch auf Zero-Downtime-Updates.

Wenn Sie unsicher sind, ob sich ein eigener LLM-Cluster gegenüber Cloud-APIs rechnet, kalkulieren Sie es mit unserem KI-Kosten-Vergleich durch, bevor Sie in Hardware investieren.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen

Bereit für KI im Mittelstand?

Nutzen Sie unsere 10 kostenlosen KI-Tools und Praxis-Guides – oder sprechen Sie direkt mit unseren Experten.

Pexon Consulting – KI-Beratung für den Mittelstand | Scaly Academy – Geförderte KI-Weiterbildung (KI-Spezialist, KI-Experte, Workflow-Automatisierung)