Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Kubernetes Consulting

Right-Sizing Pods: Requests, Limits, Autoscaling and Disruption Budgets

Requests tell the Kubernetes scheduler how much CPU and memory a pod needs, limits cap what it can use, the HPA adds replicas under load, the VPA recommends better sizes, and pod disruption budgets keep enough pods running during maintenance. We set all four from measured usage, not guesses.

Key takeaways

  • Requests drive scheduling and cost, limits drive throttling and OOM kills, so they need different thinking.
  • Set memory requests close to limits, and be cautious with CPU limits, which can throttle healthy pods.
  • HPA needs sensible requests to work, because it scales on percentage of request.
  • Use VPA in recommendation mode alongside HPA rather than letting both fight over the same metric.
  • A pod disruption budget protects availability during drains, but a badly set one can block upgrades entirely.

Why this is the highest-return Kubernetes tuning

Almost every unstable cluster we see has the same root: pods that never told Kubernetes what they need. Without requests, the scheduler packs workloads blindly. Without sensible limits, one leak takes a node down. Without disruption budgets, a routine upgrade becomes an outage. Getting kubernetes resource requests and limits right is cheap, and it improves stability, cost and upgrade safety at once.

This is a core part of our Kubernetes consulting work, and it is one of the first things we check in a production readiness audit.

Requests and limits, in plain terms

A request is a reservation. The scheduler only places a pod on a node with enough unreserved capacity. A limit is a ceiling enforced at runtime, and it behaves differently per resource:

  • Memory over the limit: the container is OOM killed and restarted.
  • CPU over the limit: the container is throttled, which shows up as latency, not a restart.

This asymmetry drives our policy. Memory is not compressible, so we set memory requests equal or close to limits and leave headroom above observed peak. CPU is compressible, so requests reflect typical use, and we think carefully before adding hard CPU limits to latency sensitive services.

Requests also determine the quality of service class. Pods where requests equal limits for every container are Guaranteed and are evicted last. Pods with no requests are BestEffort and are evicted first, which is rarely what you want for production.

resources:
  requests:
    cpu: 250m
    memory: 512Mi
  limits:
    memory: 512Mi

Here memory is pinned and CPU is left unlimited, a common pattern for web services on shared nodes.

How we pick the numbers

Guessing is the failure mode. We query history instead:

# 95th percentile CPU use per container over two weeks
quantile_over_time(0.95,
  rate(container_cpu_usage_seconds_total{container!=""}[5m])[14d:5m])

# Peak working set memory per container
max_over_time(container_memory_working_set_bytes{container!=""}[14d])

We include deploy windows and any known peak events. Requests come from a high percentile of CPU and from peak memory plus margin. The result is reviewed with the owning team, because an engineer knows about the month-end batch job that a two-week window missed. The Prometheus and Grafana setup makes this review repeatable.

Horizontal Pod Autoscaler

The HPA adds or removes replicas based on a metric, most often CPU utilisation as a percentage of requests. That last part matters: with wrong requests, the percentage means nothing.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 65
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300

Points we tune: a minReplicas that survives a zone loss, a target low enough to absorb a spike while new pods start, and a scale-down stabilisation window so replicas do not flap. For queue consumers or event driven work, scaling on queue depth with KEDA often beats CPU.

Vertical Pod Autoscaler

The VPA observes usage and recommends, or sets, requests. Its automatic mode restarts pods to apply changes, which many production teams avoid. We usually run it in recommendation mode, feed its suggestions into pull requests, and keep humans in the loop. Do not point HPA and VPA at the same CPU metric on the same workload.

Pod disruption budgets

A pod disruption budget limits how many pods of a workload can be voluntarily evicted at once, for example during a node drain or cluster upgrade.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: api

It protects availability but does nothing for crashes or node failures. And it has a sharp edge: a workload with one replica and minAvailable: 1 can never be evicted, so upgrades stall. We pair each PDB with a replica count that makes it satisfiable, then test a real drain. This directly affects upgrade planning.

Worked example: sizing one service end to end

Take an API that runs three replicas and was deployed with no resources set. We start by watching it through a normal week and one release. Suppose memory settles in a narrow band with a small spike at startup, while CPU rises and falls with traffic. The memory request and limit are set to the same value, a margin above the observed peak. The CPU request goes at a high percentile of typical use, and we leave out a hard CPU limit.

Next we check the HPA. With the CPU request now meaningful, a utilisation target does what it says, and minimum replicas are set so that losing one zone still leaves the service able to carry load. Finally the PDB is written to allow one pod to be evicted at a time, and we drain a node in a quiet window to watch it hold. Each step is a small, reviewable pull request, so a mistake is easy to revert.

Signals that sizing has drifted

Sizes are not set once and forgotten. Traffic grows, code changes and runtimes update. We watch for restarts with an OOM reason, sustained throttling, pods whose usage sits far below their request, and HPAs that sit at their maximum for long periods. Any of these is a prompt to revisit that workload.

Trade-offs and when not to automate

  • Tiny, steady workloads: a fixed replica count and a modest request may beat an HPA you must now tune.
  • Spiky traffic with slow startup: HPA reacts after load arrives. Pre-scaling on a schedule, or faster startup, matters more than clever targets.
  • Node layer: pod autoscaling needs capacity. Pair it with Karpenter or the Cluster Autoscaler, otherwise scaling pods just produces pending pods.
  • Stateful workloads: scaling databases is not an HPA problem, and PDBs for them need special care.

Common failure modes

  • Requests copied from a blog post and never revisited.
  • Memory limits below the language runtime’s real needs, causing repeated restarts under load.
  • HPA max set so high it exhausts a database connection pool before it helps.
  • PDBs on every workload with identical values, regardless of replicas.
  • Throttling mistaken for application slowness. Check container_cpu_cfs_throttled_periods_total before tuning code.

Delivery and measurement

We deliver tuned manifests as pull requests, a dashboard that shows requested versus used resources per workload, and a short guide for setting values on new services. You will know it works when OOM kills and throttling alerts fall, node utilisation rises without hurting latency, and a node drain completes without user visible errors. Cost impact follows from better packing, which our EKS cost calculator can help you estimate. For a review of your own settings, talk to us.

References

How we deliver it

  1. 01

    Measure real usage

    We read CPU and memory history from Prometheus across normal, peak and deploy periods.

  2. 02

    Set requests and limits

    Values are derived from percentiles of observed use, with memory headroom and a deliberate CPU policy.

  3. 03

    Configure autoscaling

    HPA targets, min and max replicas and scale behaviour are tuned per workload type.

  4. 04

    Add VPA recommendations

    VPA runs in recommendation mode so sizes keep improving without surprise restarts.

  5. 05

    Protect with disruption budgets

    PDBs are written for each critical workload and tested against a real node drain.

  6. 06

    Load test and review

    We confirm scaling behaviour under load and set a review cadence for drift.

Tools and technologies

PrometheusGrafanaHorizontal Pod AutoscalerVertical Pod AutoscalerKEDAmetrics-serverKarpenterCluster Autoscalerk6kubectl

Frequently asked questions

What is the difference between requests and limits?
A request is the amount the scheduler reserves for a pod when placing it on a node, and a limit is the maximum it may use at runtime. Exceeding a memory limit gets the container OOM killed. Exceeding a CPU limit gets it throttled. Requests affect cost and placement, limits affect stability.
Should we set CPU limits?
It is a debated point. CPU limits cause throttling even when the node has spare capacity, which can hurt latency sensitive services. Many teams set CPU requests but omit limits for those workloads, while keeping memory limits. Multi-tenant clusters may still want CPU limits for fairness. We decide per workload.
Can HPA and VPA run together?
Not safely on the same metric. If the HPA scales on CPU utilisation and the VPA changes CPU requests, they interfere. A common pattern is HPA on CPU or custom metrics and VPA in recommendation only mode for sizing, or VPA on memory while HPA uses a different signal.
What happens if a pod disruption budget is too strict?
Voluntary disruptions such as node drains and cluster upgrades are blocked, because Kubernetes refuses to evict pods that would break the budget. A single replica workload with minAvailable of 1 can stall an upgrade forever. We match PDBs to replica counts and test a drain.
Why is my pod OOMKilled?
The container used more memory than its limit, so the kernel killed it. Causes include a limit set too low, a memory leak, or runtime settings that ignore container limits, such as JVM heap sizing. Check usage history and restart reasons first, then fix the cause rather than only raising the limit.
Does autoscaling save money?
It can, but only if requests are accurate and the node layer scales too. Inflated requests make nodes look full when they are mostly idle. Pod level autoscaling without node autoscaling just leaves pending pods. Both layers must work together for cost to follow demand.

Talk to an engineer about kubernetes resource requests and limits

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.