Kubernetes Resource Requests and Limits: A Practical Right-Sizing Guide

Almost every Kubernetes resource spec in production was written the same way: someone guessed a number that seemed reasonable, deployed it, and never looked at it again unless something broke. That’s not a criticism — there’s rarely a clear signal telling you the guess was wrong, in either direction. Over-provisioned workloads don’t crash; they just quietly cost more than they need to. Under-provisioned ones don’t fail immediately either, until a traffic spike turns a tight limit into a throttled or evicted pod at the worst possible moment.
What requests and limits actually control
It’s worth being precise about this, because the two numbers do genuinely different jobs and mixing them up is the source of most resource-spec mistakes.
Requests are what the scheduler uses to decide which node a pod can fit on. If a node has 4 CPU cores free and your pod requests 2, the scheduler considers it a fit — regardless of what the pod actually ends up using once running. Requests are a scheduling-time promise, not a runtime cap.
Limits are enforced by the kernel at runtime. A CPU limit gets throttled if exceeded — the container keeps running, just slower. A memory limit gets the container killed (OOMKilled) if exceeded — there’s no “throttling” equivalent for memory, because you can’t partially use RAM the way you can partially use CPU cycles. This asymmetry is why memory limits deserve more caution than CPU limits: getting memory wrong causes restarts, not just slowness.
The three failure patterns, and how to spot each one
Requests set too high. The scheduler reserves capacity the workload never uses, so the node looks “full” long before it actually is, and you end up paying for nodes that are mostly idle. This is invisible unless you specifically look for it — check actual usage against requested amounts with kubectl top pod against your manifest values, not just the dashboard’s aggregate cluster utilization number, which can look healthy even when individual workloads are wildly over-requested.
Requests set too low, limits absent or too high. The scheduler under-reserves, packs more pods onto a node than it can actually sustain under real load, and the node hits memory pressure — at which point the kubelet starts evicting pods, often ones that had no limits set at all (BestEffort QoS pods are evicted first). This is the scenario behind most “why did an unrelated pod just get killed” incidents.
Limits set too tight relative to real peak usage. The workload gets OOMKilled or CPU-throttled during legitimate load, not because anything is actually wrong, just because the ceiling was set based on a guess instead of observed peak. See our OOMKilled root cause guide for how to tell this apart from an actual memory leak — the fix is completely different depending on which one you’re looking at.
A right-sizing workflow that doesn’t require a PhD
- Pull actual usage over a real time window — at least one full traffic cycle, not a quiet afternoon. A workload that looks fine at 3pm on a Tuesday can look completely different during your actual peak. If your traffic has a weekly pattern (batch jobs, Monday-morning login spikes), your sampling window needs to cover it.
- Set requests near your p50 (typical) usage, not your peak. Requests are about efficient scheduling, not headroom — the scheduler will still let the pod burst above its request when the node has spare capacity.
- Set memory limits with real headroom above your observed peak — commonly 20-30% above the highest sustained value you’ve seen, not your absolute peak spike. Memory limits should rarely be hit under normal operation; hitting them should mean something is actually wrong, not that a normal Tuesday afternoon crossed an arbitrary line.
- Consider leaving CPU limits off entirely for latency-sensitive services. CPU throttling under contention is confusing to diagnose and often worse for user experience than letting a workload briefly use more CPU than requested when the node has spare capacity. Memory limits still matter — CPU limits are the more debatable one.
- Re-run this quarterly, not once. Traffic patterns shift, code changes memory profiles, and a right-sizing pass from six months ago is a snapshot of a cluster that no longer exists.
Why this is hard to do by hand at more than a few services
The workflow above is straightforward for one workload. It gets tedious fast once you’re doing it for thirty services across three environments, which is exactly why it usually doesn’t happen — not because teams don’t know they should right-size, but because pulling usage history, comparing it against manifest values, and flagging the mismatches is manual work nobody has a free afternoon for.
This is squarely what cost and utilization intelligence is for: mapping actual node and pod utilization against what’s requested, so the mismatch is visible per namespace and workload instead of requiring a spreadsheet exercise every quarter. It doesn’t replace the judgment call in step 3 above — how much headroom is right for your workload is still a decision that depends on how spiky your traffic actually is — but it removes the data-gathering step that usually stops the exercise before it starts.
Frequently asked questions
Should requests and limits ever be set to the same value? That’s called “Guaranteed” QoS, and it’s appropriate for workloads where you want maximum scheduling priority and zero throttling risk — typically stateful or latency-critical services. For most stateless web services, some gap between request and limit is fine and gives the scheduler more flexibility.
How often should resource specs actually be revisited? Quarterly is a reasonable default for most teams, or immediately after any change that plausibly shifts memory or CPU profile — a new dependency, a caching change, a traffic pattern shift.
What’s a safe way to test a new limit without risking an outage? Roll it out to a canary or staging environment first under realistic load, and change memory limits upward before downward — it’s much cheaper to over-provision temporarily than to get an OOMKill in production while validating a tighter number.
Does autoscaling remove the need to right-size individual pods? No — Horizontal Pod Autoscaling changes how many replicas you run, not how much each one is allowed to use. Both matter, and getting one right doesn’t compensate for getting the other wrong. See our HPA-specific guide for that side of the picture.
See real request-vs-usage mismatches across your own fleet — start free with one cluster, no card required.