Production vLLM on Kubernetes: a deployment guide

Getting vLLM to serve one model on one GPU is a one-liner. Getting it to survive real traffic, restarts, node failures and a finance review is the actual job. This guide walks the production concerns in the order they bite: the OpenAI-compatible server, GPU scheduling, the throughput knobs, the KV-cache, tensor parallelism, probes, autoscaling and model loading.

๐Ÿ’ก Before you tune anything, know what idle serving is costing you. The free GPU Idle-Cost Calculator turns fleet size and utilization into an annual waste figure in two minutes, no signup.

The OpenAI-compatible server

vLLM ships an OpenAI-compatible HTTP server, so your existing OpenAI SDK clients point at it with a base URL change and nothing else. Launch it with vllm serve (equivalently python -m vllm.entrypoints.openai.api_server):

vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --served-model-name llama3-8b \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --max-num-seqs 256 \
  --port 8000

It exposes /v1/chat/completions, /v1/completions and /v1/models, a /health endpoint that returns 200 when the engine is up, and a Prometheus /metrics endpoint. The --served-model-name flag decouples the public model id your clients send from the on-disk weights path โ€” worth setting so you can swap the underlying checkpoint without breaking callers.

Requesting the GPU (integer-only, request == limit)

GPUs are exposed to Kubernetes as the nvidia.com/gpu extended resource, and extended resources have two hard rules: they are integer-only and non-overcommittable. You cannot request 0.5 of a physical card through this resource, and you set it under limits โ€” Kubernetes copies that value into requests, so request always equals limit. Fractional sharing is a separate mechanism (MIG or time-slicing); the plain resource is whole cards.

resources:
  limits:
    nvidia.com/gpu: 1      # request is set equal automatically

One more thing bites tensor-parallel and multi-GPU pods: vLLM uses shared memory for inter-process/NCCL communication, and the container default /dev/shm is tiny. Mount a memory-backed volume:

volumes:
- name: dshm
  emptyDir: {medium: Memory, sizeLimit: 8Gi}
# ... in the container:
volumeMounts:
- {name: dshm, mountPath: /dev/shm}

Continuous batching and --max-num-seqs

vLLM's headline feature is continuous (in-flight) batching: it admits new requests into the running batch as sequences finish, instead of waiting for a static batch to complete, which keeps the GPU saturated across concurrent users. You don't turn it on โ€” it is the scheduler. What you tune is --max-num-seqs, the ceiling on sequences running concurrently, and --max-num-batched-tokens, the token budget per scheduler step. Raise --max-num-seqs for more concurrency and throughput; lower it if you're seeing KV-cache preemption (vLLM logging that it is recomputing or swapping sequences because the cache filled). These two flags, plus memory utilization below, are the throughput levers that matter most.

KV-cache and --gpu-memory-utilization

After weights load, vLLM allocates the remaining GPU memory to the KV-cache โ€” the per-token attention state that actually determines how many sequences fit at once. --gpu-memory-utilization (default 0.90) is the fraction of each card vLLM is allowed to claim. Push it toward 0.95 and you get more KV-cache blocks and higher concurrency; push too far and you leave no headroom for fragmentation spikes and risk CUDA OOM at load. --max-model-len caps context length, which directly caps worst-case KV-cache per request โ€” set it to what you actually serve, not the model's theoretical maximum, or a handful of huge-context requests will starve everyone else. If your workload shares a long system prompt across requests, --enable-prefix-caching reuses that prefix's KV blocks instead of recomputing them.

Tensor parallelism across GPUs

When a model doesn't fit on one card, shard it with --tensor-parallel-size N (across GPUs in one node) and, for very large models across nodes, --pipeline-parallel-size. Match the pod's GPU limit to the tensor-parallel degree โ€” --tensor-parallel-size 4 needs nvidia.com/gpu: 4 โ€” and keep those GPUs on the same node with fast interconnect (NVLink/NVSwitch where available), because tensor parallelism is communication-heavy and splitting across a slow link tanks throughput. Don't reach for parallelism as a default: a model that fits on one GPU serves cheaper on one GPU than sharded across four, because you pay the communication overhead for nothing.

Serving in production and want the battle-tested manifests? The Production vLLM on Kubernetes pack ships the Deployment, probes, PVC model-cache, HPA/KEDA scaler and the tuning defaults from this guide โ€” drop-in and validated.

And to size the fleet first, the free GPU Idle-Cost Calculator โ†’

Readiness and liveness probes

The single most common vLLM-on-K8s outage is traffic arriving before the model finishes loading. Loading tens of GB of weights takes real time, so your probes must tolerate it. Point readiness at /health and give liveness a generous startup grace (or use a startupProbe) so the kubelet doesn't kill the pod mid-load:

startupProbe:
  httpGet: {path: /health, port: 8000}
  failureThreshold: 60
  periodSeconds: 10          # allow up to ~10 min to load
readinessProbe:
  httpGet: {path: /health, port: 8000}
  periodSeconds: 10
livenessProbe:
  httpGet: {path: /health, port: 8000}
  periodSeconds: 20
  failureThreshold: 3

Readiness gates the Service endpoint, so a still-loading replica never receives requests; the startup probe protects the slow first minutes; liveness restarts a genuinely wedged engine.

Autoscaling on queue depth, not CPU

CPU and memory are near-useless signals for LLM serving โ€” a GPU-bound pod can be maxed out at low CPU. Scale on vLLM's own Prometheus metrics instead: vllm:num_requests_waiting (queue depth) and vllm:num_requests_running, with vllm:gpu_cache_usage_perc as a saturation indicator. KEDA's Prometheus scaler is the clean path โ€” scale up when average waiting requests per replica crosses a threshold. Two caveats keep it safe: cold starts are slow, so keep a warm floor of replicas and scale up fast, down gently; and scale-to-zero is only viable for genuinely intermittent models where an accepted first-request delay is fine. This is the same request-aware pattern covered in reducing LLM inference cost.

Model loading: PVC vs object store

Where the weights come from decides your startup time and your blast radius. Three common patterns:

Whichever you pick, mount the cache read-only and set HF_HOME/the model path explicitly so vLLM reads from it rather than re-fetching.

Multi-model routing

A vLLM server hosts one model. To serve several, run one Deployment per model โ€” each independently sized, scaled and probed โ€” and put a router in front that reads the OpenAI model field and forwards to the right backend Service (LiteLLM, an API gateway, or the vLLM production-stack router all do this). This keeps a heavy model's traffic from starving a light one, lets you scale and roll each independently, and isolates a bad deploy to a single model. Consolidate genuinely low-traffic models onto shared GPUs via MIG or time-slicing rather than giving each its own idle card โ€” see time-slicing vs MIG.

The short version

ConcernThe leverWatch out for
GPU requestnvidia.com/gpu integer, in limitsNo fractional cards; request==limit
ThroughputContinuous batching + --max-num-seqsKV-cache preemption if too high
Concurrency--gpu-memory-utilization, --max-model-lenOOM if utilization too aggressive
Big models--tensor-parallel-sizeMatch GPU limit; keep on one node
StartupstartupProbe on /healthLiveness killing slow loads
ScalingKEDA on vllm:num_requests_waitingCold starts; keep a warm floor

Get these right and vLLM is a genuinely boring, high-utilization serving layer. Get the probes and autoscaling wrong and you get 3 a.m. pages and a GPU bill for capacity you never used.

When you want a human to pressure-test your vLLM setup โ€” the probes, the memory headroom, the scaler that never fires โ€” book a GPU Cost & Reliability Audit โ†’
Get the next field note. Practical, occasional notes on GPU/K8s serving and cost โ€” the traps and the numbers. No spam.

Related reading

Serve efficiently, then watch it: reduce LLM inference cost, set up DCGM + Prometheus + Grafana monitoring, right-size GPU requests, and choose time-slicing vs MIG.