Self-Hosted LLM auf Kubernetes: Produktives vLLM-Deployment

April 5, 2026 · 14 min read · kubernetes, llm, self-hosted, vllm, gpu
Self-Hosted LLM auf Kubernetes: Produktives vLLM-Deployment

Die meisten Teams, die nach Self-Hosted-LLM-Kubernetes-Deployments fragen, sollten dafür gar kein Kubernetes fahren. Die ehrliche Antwort: vLLM auf einer einzelnen GPU-Box, eingepackt in systemd oder Docker Compose, deckt mehr Use Cases ab, als man gerne zugibt. Kubernetes verdient sich seinen Platz erst, wenn Sie es ohnehin betreiben — oder wenn Sie horizontale Skalierung, Multi-Tenancy-Isolation oder saubere Rolling Deploys über einen GPU-Node-Pool brauchen.

Dieser Leitfaden setzt voraus, dass Sie sich für Kubernetes entschieden haben. Ich gehe die Referenzarchitektur durch, die ich beim LLM-Deployment nach k8s nutze, liefere vollständige Manifests für vLLM mit Llama 3.3 70B quantisiert und die operativen Stolpersteine, die jedes Team beim ersten Mal erwischen. Keine plattformspezifische Magie, keine Abstraktion hinter einem Managed Vendor. Nur die YAML, die Sie brauchen, und die Begründung hinter jedem Feld.

Wenn Sie noch zwischen Self-Hosted und API schwanken, deckt der Self-Hosted-LLM-vs.-API-Vergleich diese Entscheidung ab. Dieser Artikel setzt dort ein, wo die Plattform-Wahl dran ist.

Wann Kubernetes richtig ist

Kubernetes für LLM-Serving macht in vier Situationen Sinn — und die sind enger gefasst, als die Kubernetes-Community gern zugibt.

Sie betreiben Kubernetes schon für den Rest Ihrer Workloads. Ein zusätzliches LLM-Deployment in einem bestehenden Cluster ist günstiger als ein separater Stack. Sie nutzen dasselbe RBAC, dasselbe Monitoring, dieselbe CI/CD. Die Grenzkosten eines weiteren Namespaces sind gering.

Sie brauchen horizontale Skalierung von Inferenz-Pods. Ein GPU-Replica reicht nicht. Sie sehen anhaltenden Traffic, der von drei, fünf oder zehn Pods hinter einem Load Balancer profitiert, und die Request-Rate schwankt genug, dass Autoscaling zählt. Darunter ist eine fette Node einfacher.

Sie brauchen Pod-Isolation für Mandantenfähigkeit. Verschiedene Tenants treffen verschiedene Modelle — oder dasselbe Modell mit anderen Rate Limits und Quoten. Namespaces und NetworkPolicies liefern Blast-Radius-Isolation, die ein Einzelserver nicht kann.

Sie wollen kontrollierte Rollouts auf GPU-gestützten Nodes. Rolling Updates, Canary Deploys, Blue-Green für eine neue vLLM-Version — alles ohne Request-Drops. Das ist das Killer-Feature, wenn Sie es wirklich brauchen.

Wann es Overkill ist

Single-Model, Single-Team-Deployment. Ein Produkt, ein Modell, ein Team, das es wartet. Kubernetes bringt hier nichts. vLLM auf eine GPU-VM mit systemd, Reverse Proxy davor — fertig. Das Linux-VPS-KI-Dev-Setup zeigt das Muster.

Keine Kubernetes-Erfahrung. Die Lernkurve für k8s plus GPU plus vLLM ist steil. Wenn Ihr Team nie einen produktiven Cluster gefahren hat, dominiert das operative Risiko jeden theoretischen Vorteil. Docker Compose deckt Dev und Staging trivial ab — der Docker-Compose-KI-Dev-Stack ist mein Startpunkt bei fast jedem Projekt.

Nur Dev und Staging. Kein Skalierungsbedarf. Keine SLOs. Niemand wird gepaged. Compose reicht. Kubernetes ist hier Resume-driven Architecture.

Latenz-sensitives Edge-Deployment. Wenn Ihre Nutzer Sub-100 ms TTFT erwarten und Sie auf General-Purpose-Cloud-k8s fahren, tun Control-Plane-Overhead und Netzwerk-Hops weh. Bare Metal gewinnt.

Die Referenzarchitektur

Das ist die Form dessen, was wir bauen. Alles im selben Namespace für die Einfachheit; in der Praxis splittet man nach Umgebung oder Team.

llm-serving/
  Namespace          llm-serving
  ConfigMap          vllm-config         (serving args, chat templates)
  Secret             hf-secret           (Hugging Face token)
  PersistentVolume   model-cache         (50 to 200 GB, read-many)
  Deployment         vllm-llama3         (2+ replicas, GPU nodeSelector)
  Service            vllm-llama3-svc     (ClusterIP)
  Ingress            vllm-llama3-ing     (NGINX + cert-manager + auth)
  HPA                vllm-llama3-hpa     (scales on queue depth)
  ServiceMonitor     vllm-llama3-metrics (Prometheus scrape)

Die langweilige Form zählt. Jede Ressource soll eine Sache tun. Das Deployment fährt vLLM. Der Service gibt ihm einen stabilen In-Cluster-DNS-Namen. Der Ingress managt TLS und Auth. Der HPA skaliert. Der PVC hält Gewichte, damit Sie nicht bei jedem Pod-Restart 140 GB neu laden. Halten Sie die Belange getrennt, und jedes Teil lässt sich ohne Nebenwirkungen austauschen.

GPU-Node-Setup

Bevor irgendein Manifest greift, muss der Cluster von GPUs wissen.

NVIDIA Device Plugin als DaemonSet über den GPU-Node-Pool installieren. Auf den meisten Managed-Kubernetes-Angeboten ein Helm-One-Liner, auf Bare Metal braucht es den NVIDIA-Treiber vorab im Node-Image. Nach der Installation zeigt kubectl describe node <gpu-node> im Capacity-Block nvidia.com/gpu: 1 (oder mehr). Wenn nicht, funktioniert nichts anderes auf dieser Seite.

Dedizierten GPU-Node-Pool fahren. Mischen tut weh. Cluster-Autoscaler werden verwirrt, wenn GPU-Pods auf Non-GPU-Nodes landen, und Pod-Bin-Packing über heterogener Hardware ist ein verlorener Kampf. Eine GPU-Klasse pro Pool wählen: T4 und L4 für kleine Modelle und Batch-Arbeit, L40S oder A100 für 70B-Klasse mit Quantisierung, H100 für Full-Precision-Serving oder Training. Ich fahre L40S für die meisten 70B-quantisierten Deployments. Preis-Leistung ist für Inferenz schwer zu schlagen.

GPU-Nodes tainten, um reguläre Workloads fernzuhalten: kubectl taint nodes <node> nvidia.com/gpu=present:NoSchedule, dann eine passende Toleration am vLLM-Deployment. GPU-Nodes kosten das Acht- bis Zwanzigfache von CPU-Nodes. Sie wollen keinen Sidecar-Logger darauf hocken haben.

Der Hetzner-vs.-AWS-für-KI-Workloads-Vergleich deckt die Provider-Tradeoffs. Auf AWS EKS erledigt der NVIDIA Operator das meiste davon. Auf Hetzner Bare Metal installieren Sie Treiber und Device-Plugin selbst — und es lohnt sich für die Preisdifferenz.

Zum Schluss: der Cluster-Autoscaler muss GPU-Requests verstehen. Aktuelle Versionen tun das — doppelt prüfen. Ein Pod, der auf nvidia.com/gpu: 1 Pending steht, sollte binnen weniger Minuten eine neue GPU-Node hochziehen. Tut er das nicht, zählt der Autoscaler die GPU-Ressourcen falsch.

Modellgewichte handhaben

Llama 3.3 70B sind 140 GB in Full Precision, rund 40 GB AWQ-quantisiert. Das wollen Sie nicht bei jedem Pod-Start herunterladen. Drei Strategien, jede mit realen Tradeoffs.

Init-Container, der von Hugging Face lädt. Einfach, keine Storage-Infrastruktur, funktioniert sofort. Cold Start ist langsam (5 bis 30 Minuten je nach Modellgröße und Bandbreite), und wenn HF Sie bei einem Scale-up rate-limited, fallen die Hälfte der Pods aus der Readiness. Gut für Experimente und frühes Staging, schmerzhaft in Produktion.

PersistentVolume, Read-only über Pods geteilt. Gewichte leben auf einem PVC, Pods mounten es, der Start fällt auf die Zeit, die vLLM zum Laden der Gewichte in den GPU-Speicher braucht (30 bis 120 Sekunden). Der Haken: Storage-Class-Support. ReadWriteMany oder ReadOnlyMany-Accessmodes sind Pflicht, und nicht jedes Backend kann das. AWS: EFS geht, bringt aber Latenz auf First Read. Hetzner oder Bare Metal: NFS oder Ceph handhaben es sauber. Ein PVC, das einmal durch einen One-Shot-Job befüllt wird, ist das kanonische Muster.

Ins Container-Image gebacken. Modellgewichte im Image selbst. Cold Start ist so schnell wie möglich. Das Image ist 40 bis 150 GB. Jeder Pull belastet die Registry hart, jeder Base-Image-Bump erzwingt einen Full-Rebuild, und die meisten Registries verrechnen Per-GB-Egress. Valide für air-gapped Deployments mit Compliance-Gründen — sonst überall schmerzhaft.

Meine Empfehlung: PersistentVolume für Produktion, Init-Container für Experimente, gebackenes Image nur wenn Compliance es erzwingt. Hier der Job, der das PVC einmal befüllt, bevor das Deployment überhaupt läuft:

apiVersion: batch/v1
kind: Job
metadata:
  name: model-cache-populate
  namespace: llm-serving
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: downloader
          image: python:3.12-slim
          command:
            - /bin/sh
            - -c
            - |
              pip install huggingface_hub
              huggingface-cli download meta-llama/Meta-Llama-3.3-70B-Instruct \
                --local-dir /cache/Meta-Llama-3.3-70B-Instruct
          env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: token
          volumeMounts:
            - name: model-cache
              mountPath: /cache
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: model-cache-pvc

Einmal laufen lassen, prüfen dass das PVC die Gewichte hat, dann die Deployment-Pods es Read-only mounten lassen.

Das vollständige Deployment-Manifest

Das ist das zentrale Artefakt. vLLM mit Llama 3.3 70B quantisiert, zwei Replicas, L40S-Nodes, PVC-basierter Weight-Cache, Prometheus-ready, mit einer Readiness-Probe, die vLLMs langsames erstes Laden berücksichtigt.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama3
  namespace: llm-serving
  labels:
    app: vllm-llama3
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm-llama3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: vllm-llama3
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      nodeSelector:
        nvidia.com/gpu.product: "NVIDIA-L40S"
      tolerations:
        - key: nvidia.com/gpu
          operator: Equal
          value: present
          effect: NoSchedule
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.7.3
          imagePullPolicy: IfNotPresent
          args:
            - --model
            - meta-llama/Meta-Llama-3.3-70B-Instruct
            - --quantization
            - awq_marlin
            - --gpu-memory-utilization
            - "0.9"
            - --max-model-len
            - "32768"
            - --max-num-seqs
            - "64"
            - --disable-log-requests
          ports:
            - name: http
              containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: 1
              memory: 64Gi
              cpu: "8"
            requests:
              nvidia.com/gpu: 1
              memory: 48Gi
              cpu: "4"
          volumeMounts:
            - name: model-cache
              mountPath: /root/.cache/huggingface
              readOnly: true
            - name: shm
              mountPath: /dev/shm
          env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: token
            - name: HF_HUB_OFFLINE
              value: "1"
            - name: VLLM_WORKER_MULTIPROC_METHOD
              value: spawn
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 180
            periodSeconds: 10
            failureThreshold: 30
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 600
            periodSeconds: 30
            failureThreshold: 5
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: model-cache-pvc
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: 8Gi
      terminationGracePeriodSeconds: 120

Ein paar Felder sind tragend:

nodeSelector: nvidia.com/gpu.product: "NVIDIA-L40S" pinnt auf ein konkretes GPU-Modell. Ohne das landen Sie vielleicht auf einer T4 und haben OOM, bevor die Gewichte geladen sind. Das NVIDIA-GPU-Feature-Discovery-DaemonSet setzt das Label automatisch.

--quantization awq_marlin ist, wie 70B auf eine einzelne 48-GB-L40S passt. Ohne das brauchen Sie zwei GPUs und Tensor-Parallelität, was die Kosten verdoppelt. AWQ mit Marlin-Kernel ist der aktuelle Sweet Spot für Qualität vs. Durchsatz auf L40S.

--gpu-memory-utilization 0.9 reserviert 90 % des GPU-Speichers für vLLMs KV-Cache. Etwas Headroom für CUDA-Kernels lassen. Auf 0,95 hochschieben funktioniert manchmal, fällt dann unter Last auf die Nase.

--max-model-len 32768 cappt Kontext bei 32k Tokens. Llama 3.3 kann 128k, aber größerer Kontext frisst KV-Cache und kostet Durchsatz. An Ihren tatsächlich längsten Prompt anpassen.

HF_HUB_OFFLINE: "1" zwingt vLLM, ausschließlich den PVC-Cache zu nutzen — keine Netzwerk-Calls zu Hugging Face zur Laufzeit. Verhindert das Albtraum-Szenario: HF ist down und Ihre Pods starten nicht.

emptyDir: medium: Memory für /dev/shm gibt vLLMs Multiproc-Workern genug Shared Memory. Default-shm ist 64 MB. vLLM segfaultet bei allem Nicht-Trivialem ohne das.

readinessProbe.initialDelaySeconds: 180 plus failureThreshold: 30 gibt dem Pod 180 Sekunden, bevor das Probing startet, dann bis zu 5 Minuten Probe-Failures. Weight-Loading ist selbst vom PVC langsam.

terminationGracePeriodSeconds: 120 lässt In-Flight-Requests bei Rolling Updates auslaufen. Default 30 s schneidet Requests ab.

Das Deployment paart mit einem passenden PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: model-cache-pvc
  namespace: llm-serving
spec:
  accessModes:
    - ReadOnlyMany
  resources:
    requests:
      storage: 200Gi
  storageClassName: nfs-client

Storage-Class zählt. Ein Backend nutzen, das ReadOnlyMany oder ReadWriteMany unterstützt — EFS, NFS, Ceph oder CSI-Driver mit Multi-Attach. AWS Default gp3 funktioniert hier nicht, das ist RWO-only.

Service, Ingress und Auth

Interner Traffic trifft einen ClusterIP. Externer Traffic geht durch einen Ingress mit TLS und Authentifizierung. vLLMs OpenAI-kompatible API nie roh ins Internet hängen.

apiVersion: v1
kind: Service
metadata:
  name: vllm-llama3-svc
  namespace: llm-serving
spec:
  type: ClusterIP
  selector:
    app: vllm-llama3
  ports:
    - name: http
      port: 80
      targetPort: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: vllm-llama3-ing
  namespace: llm-serving
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    nginx.ingress.kubernetes.io/auth-url: "http://auth-proxy.llm-serving.svc.cluster.local/verify"
    nginx.ingress.kubernetes.io/auth-response-headers: "X-Tenant-Id"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - llm.example.com
      secretName: vllm-tls
  rules:
    - host: llm.example.com
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: vllm-llama3-svc
                port:
                  number: 80

Zwei kleine Fallen. Die Proxy-Timeouts müssen lang genug für Streaming-Responses sein, sonst killt NGINX die Connection mitten in der Generation. 300 Sekunden reichen meist. Und die auth-url-Annotation verdrahtet einen separaten Auth-Service, der API-Tokens oder OIDC validiert und 200 oder 401 zurückgibt. Für interne Nutzung: selbst schreiben ist in Ordnung. Für kundenseitige Workloads deckt ein OIDC-Proxy wie oauth2-proxy die Edge-Cases ab.

Wenn Ihr Cluster hinter einem Cloud-LoadBalancer sitzt, auf Idle-Connection-Timeouts achten. AWS ALB steht default bei 60 Sekunden, was lange Generationen killt. Auf 300+ anheben. GCP ähnlich. Beim Hetzner Load Balancer ist der Default ok.

Horizontal Pod Autoscaling

Der naive Ansatz ist Autoscaling auf CPU. Es funktioniert so lala, weil vLLM Host-CPU proportional zur Request-Last verbrennt, aber es ist ein nachlaufender Indikator, und Sie skalieren zu spät. Das bessere Signal ist Queue-Depth.

vLLM stellt vllm:num_requests_waiting unter /metrics bereit. Prometheus scraped es. Ein Prometheus Adapter exponiert es als Custom Metric zum HPA.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vllm-llama3-hpa
  namespace: llm-serving
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-llama3
  minReplicas: 2
  maxReplicas: 8
  metrics:
    - type: Pods
      pods:
        metric:
          name: vllm_num_requests_waiting
        target:
          type: AverageValue
          averageValue: "5"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 2
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 600
      policies:
        - type: Pods
          value: 1
          periodSeconds: 300

Scale-up ist aggressiv, weil neue GPU-Pods Minuten zum Hochkommen brauchen — schnell reagieren. Scale-down ist langsam, weil Sie teure GPU-Nodes nicht churnen wollen. Zehn Minuten anhaltend niedriger Queue-Depth, bevor ein Replica abgeworfen wird, ist ein vernünftiger Startpunkt.

Wenn Sie keinen Custom-Metrics-Adapter aufsetzen können, reicht CPU-basierter HPA als grober Ersatz. Ziel 70 % CPU, akzeptieren Sie, dass Sie leicht zu spät skalieren, weitermachen.

vLLM-Monitoring

vLLM stellt Prometheus-Metriken unter /metrics out of the box bereit. Die, die ich beobachte:

  • vllm:e2e_request_latency_seconds-Histogramm. p95 und p99 sind die SLO-Metriken.
  • vllm:time_to_first_token_seconds. Streaming-UX hängt davon ab.
  • vllm:generation_tokens_total-Rate. Tokens pro Sekunde über alle Pods.
  • vllm:num_requests_running und vllm:num_requests_waiting. Queue-Gesundheit.
  • vllm:gpu_cache_usage_perc. KV-Cache-Druck. Nähert sich unter Last 100 %, bleibt dort, wenn Sie overcommit haben.
  • DCGM_FI_DEV_GPU_UTIL vom NVIDIA-DCGM-Exporter. Tatsächliche GPU-Auslastung.

Grafanas vLLM-Dashboard (ID auf grafana.com) lässt sich als Template importieren. Dort starten, dann Panels auf Ihre SLOs tunen. Der Artikel systemd-Services für KI-Server hat das Metrik-Playbook für die Nicht-Kubernetes-Variante — das meiste portiert.

Alerts, die ich fahre:

  • p95-Latenz über SLO für 5 Minuten.
  • num_requests_waiting über 20 für 3 Minuten (HPA ist zu langsam).
  • GPU-Auslastung anhaltend über 95 % (heiß, muss skalieren).
  • Pod-Restart-Rate über 1 pro Stunde (etwas crasht).
  • PVC nähert sich Kapazität (Modell-Cache wächst unerwartet).

Multi-Model-Strategien

Mehr als ein Modell verkompliziert das Bild.

Ein Deployment pro Modell. Am saubersten. Jedes Modell bekommt eigene Replicas, eigene Skalierungskurve, eigenen Ressourcen-Envelope. Verschwenderisch, wenn einige Modelle 95 % des Traffics übernehmen und andere zweimal am Tag gefragt werden. Super, wenn die Nutzung balanced ist.

Router-Layer mit LiteLLM oder Ähnlichem. Ein externer Endpoint, LiteLLM routet basierend auf Modellnamen im Request an das richtige Backend. Fügt einen Hop und einen Failure-Mode hinzu, lässt Sie aber eine saubere OpenAI-kompatible API präsentieren, die beliebig viele interne Deployments fronted. Ich nutze dieses Muster, sobald es mehr als zwei Modelle gibt.

Dynamic Model Loading. vLLM hot-swapt keine Modelle. SGLang und manche neueren Serving-Stacks tun das mit realen Tradeoffs bei First-Request-Latency. Einen Blick wert, wenn Sie einen Long Tail selten genutzter Modelle haben und dedizierte Replicas nicht rechtfertigen können.

In der Praxis mische ich die ersten beiden. Deployments für High-Traffic-Modelle, LiteLLM-Router davor, Fallback auf Managed Inference (Fireworks, Together, Replicate) für den Tail. Der Produktive-KI-Agent-Architektur-Leitfaden geht die Routing-Logik im Detail durch.

Kostenmodell auf k8s-Ebene

Grobe Zahlen für zwei L40S-Nodes, die 24/7 laufen:

KomponenteMonatliche Kosten
2× L40S-Instanzen (Cloud)1.400 $ bis 1.800 $
Cluster-Control-Plane75 $ (EKS, GKE) oder 0 $ (Bare Metal)
Storage (PVC, 200 GB)20 $ bis 40 $
LoadBalancer20 $ bis 30 $
Egressstark variabel
Baseline-Summe1.500 $ bis 1.900 $

Vergleichen Sie das mit API-Ausgaben bei gleichem Durchsatz. Eine einzelne L40S mit Llama 3.3 70B AWQ schafft je nach Prompt-Form etwa 800 bis 1.500 Tokens pro Sekunde anhaltend. Zwei Replicas bei 60 % Auslastung sind vielleicht 100 Millionen Tokens pro Tag. Bei 0,60 $ pro Million Input plus 2 $ pro Million Output blended für eine Mid-Tier-API sind das 60 $ bis 200 $ pro Tag, also 3.000 $ bis 6.000 $ im Monat.

Der Crossover liegt meist bei rund 30 % Auslastung. Darunter ist eine API günstiger. Oberhalb 60 % gewinnt Self-Hosted deutlich — angenommen, Sie brauchen 70B-Qualität wirklich. Bei 100 % Auslastung auf günstigerer Hardware reden wir von 5× bis 10× Kostenreduktion gegenüber API-Ausgaben.

Die Zahlen bewegen sich ständig. Der Hetzner-vs.-AWS-für-KI-Workloads-Breakdown geht tiefer auf Provider-Pricing, und der GPU-Cloud-Vergleich deckt dedizierte GPU-Mieten ab, bei denen die Rechnung sich erneut verschiebt.

Managed Inference (Fireworks, Together, Replicate) ist die Mitteloption. Sie bezahlen pro Token wie bei einer API, aber auf offenen Modellen. Wenn Ihre Open-Model-Wahl fix ist und Sie den Betrieb nicht wollen, überspringt das Kubernetes komplett. Oft die richtige Antwort für Teams von einem oder zwei Engineers.

Stolpersteine

Erster Pod-Start dauert 10 bis 30 Minuten. Readiness-Probe initialDelaySeconds und failureThreshold müssen das abdecken. Pod-Logs beim ersten Deploy beobachten. Wenn vLLM noch lädt, während Kubernetes aufgibt, Failure-Threshold erhöhen.

OOM beim Laden von 70B ohne Quantisierung. Einzelne L40S hat 48 GB. 70B fp16 sind 140 GB. --quantization awq_marlin oder --quantization gptq nutzen. Wenn Sie auf Full Precision bestehen: Tensor-parallel über zwei GPUs mit --tensor-parallel-size 2 und passendem nvidia.com/gpu: 2 in den Resources.

vLLM liefert Breaking Changes. Image pinnen. vllm/vllm-openai:latest in Produktion ist ein 3-Uhr-Page, wenn ein neues Release landet. Auf einen Tag wie v0.7.3 pinnen und bewusst upgraden.

ReadWriteMany ist nicht universell. Default-Cloud-Block-Storage ist RWO. Sie brauchen EFS, NFS, Ceph oder einen CSI-Driver, der Multi-Attach ausweist. Storage-Class vor Shared-PVC-Design prüfen.

LoadBalancer Long-Connection-Timeouts. AWS ALB und GCP LB killen Idle-Connections default bei 60 Sekunden. Streaming-Responses sehen aus LB-Sicht idle aus. Idle-Timeout auf 300+ anheben oder NGINX Ingress mit passendem proxy-read-timeout nutzen.

HF-Rate-Limits bei Scale-Events. Bei Init-Container-Downloads und Scale von 2 auf 8 Pods treffen 6 neue Pods gleichzeitig HF. 429. PVC-basierter Cache löst das. Init-Container-Pattern nur für Dev.

KV-Cache-Druck unter burst-Last. Wenn vllm:gpu_cache_usage_perc 100 % trifft, fängt vLLM an, Requests zu preempten. Latenz schießt hart hoch. Sehen Sie das regelmäßig: horizontal skalieren oder --max-model-len senken.

Node-Autoscaler-Verwirrung bei GPU-Requests. Ältere Cluster-Autoscaler-Versionen zählen nvidia.com/gpu nicht korrekt. Pods bleiben ewig pending. Autoscaler-Version und Logs prüfen — Scale-up von Null-GPU-Nodes testen, bevor Sie sich darauf verlassen.

Wann Sie Self-Hosted aufgeben sollten

Es gibt eine klare Schwelle, und sie gehört benannt.

Ihre Kostenanalyse zeigt, dass eine API bei Ihrem tatsächlichen Volumen günstiger ist. Wenn Sie unter 30 % GPU-Auslastung ohne absehbares Wachstum sind, stoppen Sie. Wechsel zu API oder Managed Inference. Die Engineering-Zeit, die Sie in k8s-Tuning stecken, ist mehr wert als die Ersparnis.

Open-Model-Qualität erfüllt Ihren Anspruch nicht mehr. Wenn Claude Sonnet 4.6 oder GPT-5 Ihre Aufgabe lösen und Llama 3.3 70B nicht, gewinnt die Qualitätslücke. API bezahlen, auf Prompts und Evals fokussieren, Self-Hosting wieder prüfen, wenn Open Models die Lücke schließen.

Ops-Kosten übersteigen die Ersparnis. Ein Vollzeit-Engineer, der 20 % seiner Zeit auf vLLM-Ops verbringt, sind 5.000 bis 8.000 $ pro Monat an Vollkosten. Spart Self-Hosting weniger als das, nicht self-hosten.

Regulatorische Anforderungen ändern sich. Manchmal starten Sie Self-Hosted wegen Datensouveränität, dann freigegeben Ihr Compliance-Team einen Vendor mit den richtigen Zertifikaten und Regionen. Umsteigen. Es gibt keinen Pokal für den eigenen GPU-Cluster.

Das ehrliche Muster: mit API starten, zu Managed Inference wechseln, wenn Sie offene Modelle brauchen, zu Self-Hosted Kubernetes erst wechseln, wenn Skalierung und Ökonomie beides rechtfertigen. Die meisten Teams brauchen den dritten Schritt nie — und die, die ihn brauchen, wissen das lange bevor sie das erste Manifest schreiben.

Weiterführendes

KI-Automatisierungs-Checkliste (PDF) herunterladen