Skip to content

Kubernetes for ML

You don't need Kubernetes to serve a model — a single container on a single GPU VM works fine, and for one model on one GPU that's the right answer. Kubernetes earns its complexity at fleet scale: many GPUs, many models, many requests, where you need something to pack scarce accelerators efficiently, restart what crashes, roll out new versions without downtime, and do it reproducibly. This section builds the platform from its one core idea, then shows precisely what changes when the workload is a model instead of a web app.

The one idea: a reconciliation loop

Strip away the hundreds of objects and Kubernetes is a single pattern repeated everywhere: you declare desired state; controllers continuously drive actual state toward it. You don't tell Kubernetes "start this container" (imperative); you tell it "I want 3 replicas of this server to exist" (declarative), write that to the cluster's database, and a controller notices reality has 2, not 3, and starts one. Crash a pod and the same loop replaces it. This control loop — observe, diff, act, repeat — is the whole philosophy, and it's exactly why Kubernetes is good at keeping expensive, failure-prone GPU workloads running without a human babysitting them.

Declarative is the feature, not the syntax

The payoff of "declare desired state" is that your entire system is a document — versionable, reviewable, reproducible (§ 8.3's whole premise). The cost is a mindset shift: you never act on the cluster directly, you change the desired state and let it converge. Fighting this — kubectl hand-edits, manual pod restarts — is the single most common way teams turn Kubernetes from an asset into a liability.

The moving parts

A cluster is a control plane (the brain) plus worker nodes (the muscle, where your GPUs live).

Control plane:

  • API server — the front door. Everything (you, controllers, the scheduler) reads and writes desired state through it. The only component that talks to the database.
  • etcd — the database. A consistent key-value store holding the entire desired + observed state of the cluster. Lose etcd, lose the cluster's memory.
  • Scheduler — decides which node each new pod runs on, given its resource requests and constraints. For GPU work this component's decisions are the whole ballgame (§ 8.2).
  • Controller manager — runs the reconciliation loops (the "make actual match desired" logic for Deployments, Jobs, and the rest).

Each worker node:

  • kubelet — the node's agent; takes pod specs from the API server and makes the container runtime run them, then reports health back.
  • Container runtime (containerd) — actually pulls images and runs containers.
  • kube-proxy — wires up pod networking so Services resolve.

For GPU nodes, add two more (installed by the GPU Operator, § 8.2): the driver and the device plugin / DRA driver that makes the GPU visible to the scheduler as an allocatable resource.

The objects you'll actually use

Kubernetes has dozens of object types; a model platform leans on a handful. The skill is matching the workload's shape to the right object.

Object What it is Use it for
Pod one or more co-located containers, the atomic unit never directly — it's what the others manage
Deployment a controller keeping N identical, stateless pods alive + rolling updates the model server — replicas behind a Service
ReplicaSet the thing a Deployment uses to hold N pods you don't touch it; Deployment owns it
Service a stable virtual IP/DNS load-balancing across a Deployment's pods exposing the server to clients
Job / CronJob run-to-completion (or scheduled) work batch: quantization (Ch. 5), evals, offline inference
StatefulSet pods with stable identities + per-pod storage rarely — only for sharded/leader-based serving
ConfigMap / Secret non-secret / secret config injected into pods model names, endpoints, API keys, HF tokens
PersistentVolumeClaim a request for durable storage caching model weights across restarts (§ 8.4)

Serving is a Deployment; the pipeline jobs are Jobs

A model server is a stateless replica set that should always be running → Deployment behind a Service. The quantization run from Chapter 5, an eval sweep, or a batch-inference pass are run-to-completionJobs (often gang-scheduled, § 8.2). Reaching for a StatefulSet for ordinary serving is a common over-engineering smell — you want fungible replicas, not stable identities, unless you're sharding a single model's KV/state across pods.

What's actually different about ML workloads

Everything above is generic Kubernetes. Here is where models break the web-app assumptions and force the rest of this chapter:

1. A pod wants a whole accelerator, not a CPU slice. Web pods request fractional CPU ("250m = a quarter core"); the scheduler bin-packs dozens per node. A model pod requests nvidia.com/gpu: 1 — an indivisible, scarce unit — or a carefully configured share of one. This is a different scheduling problem entirely (§ 8.2).

2. Images are enormous. A CUDA + PyTorch + engine image is 5–20 GB. First pull onto a fresh node can take minutes and dominates cold-start time. Mitigations — slim images (Ch. 7), pre-pulled base layers, image streaming, secondary boot disks — are platform concerns, not afterthoughts.

3. The container isn't ready when it starts. A web server accepts traffic in milliseconds. A model server must load tens of GB of weights from storage into HBM first — seconds to minutes during which it's running but useless. Get the readiness probe wrong and Kubernetes routes traffic to a pod still loading, and clients get errors (§ 8.4 fixes this precisely).

4. Nodes are a specific SKU, and topology matters. You don't want "a node," you want "an A100 node in us-central1, on the NVLink island next to its 7 siblings." That's expressed with node labels, taints/tolerations, and affinity (§ 8.2), tying directly back to Chapter 3's interconnect and capacity reasoning.

5. Some jobs need all their GPUs at once or none. A multi-GPU serving group or a distributed job is worthless with 7 of 8 GPUs — the 8th pod waiting strands the other 7 (expensive idle silicon, and possibly deadlock). This is the gang scheduling problem, unique to this world (§ 8.2).

When NOT to use Kubernetes

Mirror of Chapter 5's "more traffic, more techniques pay off." One model on one or two GPUs with steady traffic → a plain container on a GPU VM (or a managed endpoint) is simpler and cheaper to operate; Kubernetes is pure overhead. Reach for it when you have many GPUs to pack, many models/versions to roll, bursty or batch workloads to schedule, or a reproducibility/compliance mandate. The platform is a fixed cost you amortize over fleet scale — below that scale it loses.

Try it: feel the reconciliation loop (free, no GPU)

You can experience the one core idea — declare desired state, controllers converge to it — on a free local cluster in two minutes. Needs Docker + kind:

kind create cluster                                   # a throwaway cluster on your laptop
kubectl create deployment web --image=nginx --replicas=3
kubectl get pods -w &                                 # watch in the background

# Now fight the controller: delete a pod and watch desired(3) beat actual(2)
kubectl delete pod "$(kubectl get pod -l app=web -o name | head -1 | cut -d/ -f2)"
#   → the Deployment immediately recreates it. You declared 3; it keeps 3.

kubectl scale deployment web --replicas=5             # change desired state → it converges
kubectl create job hello --image=busybox -- echo "batch done"   # run-to-completion = Job
kubectl get job hello                                 # COMPLETIONS 1/1

kind delete cluster                                   # clean up

You just watched the loop that keeps GPU servers alive without a babysitter — and saw why a server is a Deployment (kept running) while a quant pass is a Job (run to completion). The skill: never kubectl run a pod directly again — change desired state and let it converge.


You now know the platform's structure and the five ways models stress it. The first and sharpest of those stresses — getting a pod onto a GPU at all, whole or shared or in a gang — is the next section.