Key Takeaways
  • Most Kubernetes clusters run at 30-45% average CPU utilization against requested capacity, meaning teams pay for headroom they never use.
  • Zesty continuously rightsizes both pod resource requests and minimum replica counts together, closing the gap that manual quarterly reviews always leave open.
  • Capacity planning and autoscaling solve different problems, one sets the baseline, the other reacts to it in real time, and conflating them causes most rightsizing failures.
  • A phased 30-60-90 day rollout, not a single sizing pass, is what actually prevents the outages teams fear when they start optimizing resource requests.
  • Rightsizing without a risk framework (buffer zones, canary rollout, rollback plan) is the single biggest cause of capacity-planning projects getting rolled back after one bad incident.

Introduction

Kubernetes capacity planning is the ongoing process of setting CPU and memory resource requests and limits to match real workload demand, rather than a one-time sizing exercise performed once at deployment and left alone. That distinction sounds minor until you look at how most teams actually behave: they either overprovision out of fear of an OOMKill or a throttling incident, or they underprovision and get paged at 2am when a workload runs into a limit nobody double-checked.

Neither response is really a strategy, both are reactions to the same underlying problem: resource requests and limits set once, based on a guess or a copied template, and never systematically rechecked as the workload’s real usage changes. Traffic patterns shift, release cycles introduce new code paths with different memory footprints, and dependencies change what a service actually needs to run reliably. A sizing decision that was accurate on day one is rarely still accurate by day ninety.

Most guides on this topic stop at “measure usage, then set requests accordingly,” which is correct but incomplete. It skips the part that actually determines whether a capacity planning project survives contact with production: a decision tree for when to rightsize versus when to lean on autoscaling instead, a risk framework for what goes wrong when rightsizing gets too aggressive, and a phased rollout that doesn’t require betting the whole cluster on one sizing pass. This guide covers all three, in that order.

The stakes of getting this wrong are asymmetric in a way that shapes how most teams behave, even when they know better. An overprovisioned cluster shows up as a monthly invoice nobody scrutinizes line by line. An underprovisioned one shows up loudly, in an incident channel at 2am with someone’s name attached to the deploy that caused it. Given that asymmetry, it’s not surprising that most teams default to padding: the visible cost of underprovisioning is a page and a postmortem, while the cost of overprovisioning is a bigger bill that’s easy to defer dealing with until someone in finance asks about it directly.

How Kubernetes Capacity Planning Actually Works: Requests, Limits, and the Overprovisioning Gap

Resource requests and limits are the two values that determine everything about how a pod is scheduled and how it behaves once it’s running, and capacity planning is fundamentally the practice of getting both values right, together, rather than treating either in isolation.

A request represents the minimum CPU or memory Kubernetes guarantees a container, and the scheduler uses that value to decide which node can host the pod, a pod won’t be placed unless a node has enough allocatable capacity to satisfy every request it’s asking for. A limit caps the maximum a container is allowed to consume: exceed a CPU limit and the kernel throttles the container, exceed a memory limit and the container is killed. Together, these values also determine a pod’s Quality of Service (QoS) class, Guaranteed, Burstable, or BestEffort, which governs eviction priority under resource pressure. A pod with no requests or limits set defaults to BestEffort, the class evicted first, regardless of how important that workload actually is.

Kubernetes overprovisioning is the default outcome when these values are set defensively instead of from data. It’s rarely the result of one bad decision, it’s closer to an accumulation of small, individually reasonable choices that compound across a growing cluster. A new service launches with padded requests because nobody had usage data yet. A second service copies the first one’s manifest because it looked like a reasonable starting point. By the time a cluster has a hundred services, the padding isn’t one team’s decision anymore, it’s an inherited pattern nobody specifically chose and nobody feels individually responsible for fixing. It happens for a small, repeatable set of reasons:

  • Fear of OOMKills and throttling. Since the downside of setting a value too low is a visible incident and the downside of setting it too high is an invisible line item, teams pad generously and rarely get challenged on it.
  • Lack of historical telemetry. Without weeks of real usage data at deployment time, there’s no accurate number to set requests from, so engineers guess, and guesses skew conservative.
  • Copied templates. A request value copied from a similar service, or from a starter manifest, often has no relationship to what the new workload actually needs.
  • No recurring review cadence. Even accurate initial sizing goes stale, and without a scheduled or automated recheck, nobody catches the drift until a cost report flags it.

The average gap this produces is larger than most teams assume. Average CPU utilization against requested capacity typically runs 30-45% in unoptimized clusters, meaning more than half of requested compute is reserved but never used on an ordinary day. That gap compounds across a cluster into something bigger than a per-pod inefficiency: padded requests fragment nodes, since the scheduler has to find room for capacity that’s reserved but idle, which turns Kubernetes overprovisioning into a bin-packing and node-count problem, not just a line item on any single workload’s bill.

This is where a lot of capacity planning advice stops short of the real problem: it treats overprovisioning as a per-workload tuning exercise, something to fix one deployment at a time, when the actual cost shows up at the cluster level, in how many nodes are running and how full they are, not in any single pod’s resource block. Ten nodes each running at 60% utilization aren’t paying for six nodes’ worth of work spread thin, they’re paying for ten nodes while getting the output of six, and no amount of per-pod tuning fixes that gap unless the freed-up capacity actually gets consolidated onto fewer nodes afterward. Rightsizing that never converts into fewer nodes is a partial fix, one that improves a dashboard metric without necessarily moving the invoice by nearly as much as the improved utilization number would suggest.

Zesty continuously rightsizes both pod resource requests and minimum replica counts together, closing the gap that manual quarterly reviews always leave open between one review and the next.

The Capacity Planning Decision Tree: Rightsize, Autoscale, or Both?

Capacity planning and Kubernetes autoscaling solve two different problems, and confusing them is a common source of rightsizing failures. Capacity planning sets the baseline, the request and limit values a workload starts from. Autoscaling reacts to real-time demand on top of that baseline, adding replicas through the Horizontal Pod Autoscaler (HPA) or adjusting resource values through the Vertical Pod Autoscaler (VPA). A workload needs both concepts addressed, but not always both tools, and which one to lean on depends on the workload’s traffic pattern.

Workload TypeRecommended ApproachWhy
Stable, predictable (steady traffic, few spikes)Rightsize once, reassess quarterlyBaseline rarely shifts enough to need real-time reaction
Variable, bursty (regular but uneven traffic)Pair rightsizing with autoscalingA fixed baseline alone under- or over-shoots depending on the hour
Unpredictable, spiky (irregular, hard-to-forecast demand)Autoscaling-first, with wide buffer zonesNo baseline stays accurate long enough to rightsize tightly against it

A batch processing job that runs on a predictable weekly schedule is a clean fit for the first branch: rightsize it once against its known usage pattern, and check back quarterly to confirm nothing’s changed. A checkout service with regular but uneven daily traffic fits the second branch, since a static baseline would either overshoot during the quiet hours or undershoot during the peak ones, but the underlying pattern is regular enough that rightsizing plus autoscaling on top of it works well. A workload exposed to unpredictable viral traffic or unscheduled batch spikes belongs in the third branch, where the baseline matters less than having enough autoscaling headroom to absorb whatever arrives without warning.

The failure mode worth naming directly: HPA and VPA can fight each other when misconfigured together. Both can react to the same CPU signal on the same workload, VPA adjusting resource requests while HPA adjusts replica count, and without coordination, one system’s correction becomes the input that triggers the other system’s next correction, producing oscillation instead of the stable response either tool was meant to deliver on its own.

The usual workaround is to keep VPA and HPA scoped to different metrics for the same workload, VPA reacting to memory while HPA reacts to a custom request-latency metric, for example, so the two never correct against the same signal at the same time. That workaround is real and it works, but it’s also a manual configuration decision that has to be made correctly for every workload running both tools, and rechecked any time a workload’s metrics change enough that the original split no longer makes sense. It solves the conflict; it doesn’t remove the ongoing attention the conflict requires.

Zesty rightsizes both the vertical dimension, pod CPU and memory requests, and the horizontal dimension, minimum replica counts, as one coordinated decision instead of two independently tuned ones, which is what avoids the oscillation that comes from running two uncoordinated controllers against the same signal.

A Risk-Aware Framework: What Goes Wrong When You Rightsize Too Aggressively

This is the section most competing guides skip, and it’s the part that determines whether a capacity planning project survives its first production incident or gets rolled back after one.

Pod rightsizing done carelessly introduces real, specific risks, not vague ones, and each has a concrete mitigation.

Failure ModeCauseMitigation
OOMKillsMemory limits set too low relative to real peak usageBuffer zone: 10-20% headroom above measured p95 usage
ThrottlingCPU limits set too low, kernel restricts execution under loadSame buffer zone approach, or skip CPU limits where a hard cap isn’t required
Cascading pod evictionsNode scale-down evicts multiple pods at once when resource limits shrink density assumptionsCanary rollout: change one namespace or team’s workloads first, not the whole cluster
“Big bang” rollout failureEvery workload’s requests changed at once, with no isolated blast radius if something’s wrongRollback plan: keep previous request and limit values versioned, ready to revert without a rebuild

The canary approach deserves more than a line in a table. Rightsizing one namespace or one team’s workloads first, rather than the whole cluster at once, does two things: it limits the blast radius of a bad call to a small, recoverable slice of the environment, and it produces real validation data, actual OOMKill rates, actual throttling percentages, before the same change gets applied anywhere that matters more. A rollback plan with versioned request and limit values turns a bad rightsizing pass into a quick revert instead of an incident retrospective, since the previous, known-working configuration is one command away rather than something that has to be reconstructed from memory.

The “big bang” mistake deserves a closer look too, since it’s less about any single bad value and more about scale. Changing every workload’s requests in one pass means a mistake in a single service’s sizing doesn’t stay contained to that service, it lands at the same time as every other change, making it far harder to isolate which specific adjustment caused a given incident once something goes wrong. A team that rightsizes 200 workloads in one afternoon and sees three unrelated incidents the next day is left debugging three separate problems simultaneously, with no clean way to tell which change caused which failure. Staging the rollout, one namespace, one team, one risk tier at a time, turns that same set of changes into 200 small, individually attributable experiments instead of one large, unattributable one.

Zesty applies changes gradually and continuously, rather than in a single risky pass, which is the automated equivalent of the canary approach above, just running as the default behavior instead of a manual process someone has to remember to follow.

The 30-60-90 Day Capacity Planning Roadmap

A phased rollout, not a single sizing pass, is what prevents the outages teams fear when they start a capacity planning project. Here’s what belongs in each phase.

Days 1-30: Instrument, don’t change anything yet.

  • Enable cost and usage attribution at the namespace and workload level, since none of the later phases produce trustworthy results without this in place first.
  • Collect at least 2-3 weeks of historical telemetry before touching a single resource value. Shorter windows miss weekly patterns, and a single quiet week can look like a permanent baseline when it’s really just a slow one.
  • Identify the workloads with the widest gap between requested and actual usage, these are your highest-leverage targets later, since fixing the biggest gaps first produces the most convincing early results.
  • Flag any workloads already showing OOMKills or throttling under current settings, since those need attention regardless of cost, and shouldn’t wait for the general rightsizing pass to get addressed.
  • Resist the urge to start adjusting values during this phase even when a gap looks obvious. A month of clean baseline data is worth more than a week saved by skipping ahead.

Days 31-60: Rightsize where the risk is lowest first.

  • Start with non-production environments: dev, staging, and test clusters commonly account for 50-70% of non-production cloud waste, and a misstep there costs a Slack thread, not an incident.
  • Apply the buffer-zone approach (10-20% above p95 usage) to non-prod workloads and monitor for a full week before drawing conclusions.
  • Once non-prod is stable, begin canary rightsizing in production: pick one or two low-risk namespaces, not the whole cluster.
  • This is also where Kubernetes cost optimization starts showing measurable results, since non-prod savings land fast and provide the proof point that makes the production phase easier to justify internally.
  • Document what changed and why for every canary namespace, not just the new values but the reasoning behind them, since that record is what makes Days 61-90’s cluster-wide expansion a repeatable process instead of a one-off experiment nobody can reproduce at scale.

Days 61-90: Expand and establish a cadence.

  • Extend rightsizing cluster-wide, moving from the canary namespaces to the rest of the environment in stages, not all at once. Group the remaining workloads by risk tier and expand into the lowest-risk tier first, same logic as the canary phase, just applied to the rest of the cluster.
  • Introduce HPA and VPA policies on workloads identified as variable or bursty back in the decision tree, pairing autoscaling with the now-accurate baseline rather than layering it onto stale requests.
  • This phase is also where cluster autoscaling and node-level consolidation start to matter more, since a cluster full of rightsized pods creates real bin-packing opportunities that a padded cluster never had, opportunities that weren’t there to capture before the rightsizing pass ran.
  • Establish a recurring review cadence, monthly at minimum, ideally continuous, rather than treating the 90-day rollout as a finished project. Usage keeps shifting after day 90 exactly as it did before it, and a program that stops at day 90 starts drifting back toward the original baseline almost immediately.

Zesty replaces this entire manual cadence with continuous, automatic rightsizing, collapsing the 30-60-90 day timeline into an ongoing process that doesn’t require a person to schedule the next review.

Manual Tools vs. Automated Platforms: Where Each Approach Falls Short

The manual toolchain for Kubernetes resource optimization is real and it works, up to a point. Kubecost or OpenCost provide visibility into where spend is going. HPA and VPA provide reactive scaling. Karpenter handles node provisioning. Spreadsheets and dashboards track it all so a person can act on it. Each piece does its job, and the combination still runs into the same wall: every one of these tools solves one piece of the problem, someone still has to interpret the data and make the sizing decision, and the whole process drifts out of date the moment workloads change, which is constantly.

That wall shows up in a specific, recurring way: a team builds a dashboard, gets a clear picture of where the waste is, and then discovers that turning that picture into action means someone has to sit down, review the numbers namespace by namespace, decide on new values, and apply them, on a schedule that has to compete with everything else on a platform engineer’s plate. The tooling accelerates the analysis step. It does nothing for the application step, which is where most manual programs actually stall out after the initial rightsizing pass produces its first round of savings.

CapabilityManual ToolchainZesty
Continuous rightsizingPeriodic, requires a person to run the reviewContinuous, runs without a scheduled pass
Coordination between replica count and pod sizeHPA and VPA can conflict if configured without careBoth dimensions rightsized together as one coordinated decision
Non-production automationManual scheduling and manual sizingAutomated across environments without a separate workflow
Time to first recommendationDays to weeks, depending on telemetry collectionSizing recommendations available within 24 hours of activation
Ongoing maintenance burdenRecurring, scales with cluster size and team bandwidthMinimal, runs as a standing system rather than a recurring project

Zesty’s multi-dimensional autoscaling is what closes that gap specifically: it continuously rightsizes both pod requests (vertical) and minimum replica counts (horizontal) together, as one coordinated system rather than two independently reacting controllers, so the two dimensions move in the same direction instead of working against each other. That coordination has shown up as over 40% optimization in cluster size and a measured EKS cluster size reduction of 10% in real deployments, with measurable savings achievable in under an hour after activation, numbers that reflect what continuous coordination looks like once it’s actually running, rather than a projected estimate.

None of this makes the manual toolchain worthless, and it’s worth being direct about that rather than dismissing it outright. Visibility tools remain useful even alongside automation, since understanding where spend is going is valuable independent of who or what acts on it, and node provisioning tools like Karpenter still need to run underneath any rightsizing approach, manual or automated, since something has to actually launch and terminate the nodes being consolidated. What the manual toolchain doesn’t do on its own is close the loop between seeing the problem and continuously fixing it, and that gap is exactly where the time-to-recommendation and maintenance-burden rows in the table above come from.

Conclusion: Automate the Roadmap With Zesty

The 30-60-90 day roadmap above works. Instrument first, rightsize non-production and canary namespaces next, then expand cluster-wide and settle into a recurring cadence. It’s also, by its own design, a three-month project that has to happen again every time workloads shift meaningfully, which is often.

Zesty’s approach to multi-dimensional autoscaling compresses that roadmap into a continuous process instead of a quarterly one: it monitors real workload usage on an ongoing basis and automatically tunes both pod resource requests (vertical) and minimum replica counts (horizontal), with the two dimensions coordinated so they move together instead of drifting apart the way two independently managed controllers can. That coordination is exactly what Section 3’s biggest risks trace back to when it’s missing: OOMKills, throttling, and cascading evictions all get worse when vertical and horizontal decisions aren’t made together. That’s translated into over 40% optimization in cluster size, sizing recommendations within 24 hours of activation, and measurable savings within an hour of turning it on, replacing a 90-day manual rollout with automated rightsizing that never stops running. Kubernetes capacity planning stops being a recurring quarterly project once the coordination between both dimensions is handled continuously instead of by hand.

The roadmap above is the correct sequence whether or not it ends in automation, since instrumenting first, derisking with canaries, and expanding in stages is sound practice regardless of what tool eventually runs it. What changes with automation isn’t the sequence, it’s whether that sequence has to be re-run by a person every quarter for the rest of the cluster’s life, or whether it becomes the standing, default behavior of the environment itself.

Book a Demo with Zesty to see automated rightsizing applied to your own cluster’s usage patterns.

FAQs

What is Kubernetes capacity planning, and how is it different from autoscaling?

Kubernetes capacity planning is the practice of setting the baseline CPU and memory resource requests and limits a workload starts from. Autoscaling reacts to real-time demand on top of that baseline, through the Horizontal Pod Autoscaler (HPA) adding replicas or the Vertical Pod Autoscaler (VPA) adjusting resource values. Capacity planning sets where you start; autoscaling handles what happens after that, and a workload that’s been autoscaling around a badly set baseline for months is usually still wasting money even while the autoscaler is doing its job correctly.

How do you calculate the right CPU and memory requests for a Kubernetes pod?

What are the biggest risks of rightsizing Kubernetes workloads too aggressively?

How long does it take to implement a full Kubernetes capacity planning program?

How does Zesty automate Kubernetes capacity planning and rightsizing?