Resource requests, limits and autoscaling
Declare what a container needs, understand QoS and eviction, then scale pods horizontally, vertically and the cluster itself.
Requests, limits and QoS
A request is what the scheduler reserves on a node and what the autoscaler measures against. A limit is the ceiling the kernel enforces at runtime. They are different numbers answering different questions, and leaving them out is the most common cause of instability.
spec:
containers:
- name: api
image: ghcr.io/example/api:2.1.0
resources:
requests:
cpu: 250m # 0.25 of a core, reserved on the node
memory: 256Mi # used for scheduling and eviction ordering
limits:
cpu: "1" # throttled above this, never killed
memory: 512Mi # exceeded means OOMKilled| QoS class | Condition | Behaviour under pressure |
|---|---|---|
| Guaranteed | Requests equal limits for every container and resource | Evicted last; the safest for latency-sensitive work |
| Burstable | Requests set, limits larger or absent | Evicted after BestEffort; the normal choice for services |
| BestEffort | No requests and no limits anywhere in the pod | First to be evicted; only acceptable for scratch workloads |
- CPU is compressible: exceeding the limit means throttling, which shows up as latency, not crashes.
- Memory is not compressible: exceeding the limit means the kernel kills the container, so
OOMKilledis a limit-tuning problem first and a leak investigation second. - No memory request means the scheduler is guessing, and a node can be oversubscribed until something gets evicted.
- A CPU request too high wastes capacity; too low makes the pod starve under contention. Measure with
kubectl topand set requests near the steady-state usage.
kubectl top pods -n storefront
kubectl top nodes
kubectl get pod api-abc -o jsonpath='{.status.qosClass}'
kubectl describe pod api-abc | grep -A3 LimitsHorizontal Pod Autoscaler
The HPA changes the replica count of a Deployment or StatefulSet based on observed metrics. It depends on metrics-server for CPU and memory, and on an adapter for anything custom.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # percent of the REQUEST, not of the node
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "200"
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # do not flap down immediately
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30- Targets expressed as utilisation are a percentage of the container request, so an HPA without requests silently has no metric to compare against.
- The HPA scales up quickly and down slowly on purpose. Capacity that arrives late is an outage; capacity that lingers is a bill.
- Autoscaling cannot fix a slow start-up. Pair it with a readiness probe and a realistic
minReplicas, or new pods will arrive after the traffic spike has passed. - Scaling a StatefulSet scales state too. Make sure the application tolerates replicas appearing and disappearing.
Vertical scaling, cluster scaling and disruption
| Autoscaler | What it changes | Notes |
|---|---|---|
| HPA | Replica count | Needs metrics and requests; the default answer for stateless services |
| VPA | Container requests and limits | Recommends or applies sizes; restarts pods unless in recommendation-only mode |
| Cluster Autoscaler / Karpenter | Number and size of nodes | Adds nodes for Pending pods, removes underused ones |
| PodDisruptionBudget | How many pods may be down | Constrains drains and node termination, not failures |
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api
spec:
minAvailable: 2 # or maxUnavailable: 1
selector:
matchLabels:
app: api- Do not run HPA and VPA on the same metric for the same workload: one changes replicas, the other changes requests, and they fight.
- A PDB with
minAvailableequal to the replica count blocks every drain for ever. Leave at least one pod of slack. - The Cluster Autoscaler only adds nodes when a pod is genuinely unschedulable, which is why a bad request size is also a scaling problem.
- Set a memory limit on everything, but be careful with CPU limits: many teams run without CPU limits and rely on requests plus fair sharing to avoid throttling.
⚠️
An HPA makes a small problem worse as often as it helps. If each new replica opens more database connections and the database is the bottleneck, scaling out will take the database down faster. Scale the bottleneck, not the thing in front of it.
FAQ
My HPA shows unknown metrics. Why?
Either
metrics-server is not running, or the target pods have no resource requests, so there is nothing to compute a percentage against. Check kubectl describe hpa for the exact condition.Should I always set a CPU limit?
No. A CPU limit causes throttling even when the node has spare capacity, and throttling often shows up as mysterious tail latency. Requests guarantee your share; limits cap the burst. Decide per workload and measure.
Related
Scheduling: affinity, taints and tolerations Storage: volumes, PersistentVolumes and StorageClasses
Last refreshed 2026-09-18.