Kubernetes Resource Optimization Strategies: A Production Guide for 2027
Every CPU request in your manifests is a bet on how a workload behaves, and you improve the odds by checking it against actual usage. You can reclaim unused CPU and memory capacity when your requests exceed what your workloads use. When you revisit the padding you added to avoid throttling and out-of-memory (OOM) kills, you’re ready to right-size workloads without relying on old deployment assumptions.
This guide covers request calibration, autoscaling, and continuous monitoring so you can build each layer on calibrated requests, with config values that hold up under load.
Why Kubernetes Resources Are Hard to Right-Size
The scheduler reserves node capacity by request rather than usage, so you pay for every padded request in full whether the container touches it or not. Teams pad because the alternatives hurt: the kernel OOM-kills a container that exceeds its memory limit, and Completely Fair Scheduler (CFS) throttles one that exceeds its CPU limit. Noisy neighbors on over-committed nodes push requests higher after every incident. New services inherit their numbers from older manifests, and those values stay frozen while traffic patterns move on. Those forces are why Kubernetes cost optimization regresses when a team treats it as a one-time audit.
Kubernetes sorts every pod into one of three quality of service (QoS) classes from its requests and limits:
- Guaranteed: Pods set requests equal to limits for every container.
- Burstable: Pods set at least one request or limit without qualifying as Guaranteed.
- BestEffort: Pods set nothing.
QoS predicts the likely eviction order under memory pressure: BestEffort first, then Burstable, then Guaranteed. The kubelet won’t evict a Guaranteed pod unless it exceeds its own limits or no lower-priority pods can be preempted.
Set Resource Requests and Limits Correctly
The scheduler places pods by request, and Karpenter launches nodes to fit pending pods’ requests. The Horizontal Pod Autoscaler (HPA) computes utilization as a percentage of request.
How Requests and Limits Differ
A request is what the scheduler reserves for a container on a node; a limit is what the kubelet enforces at runtime. The kernel enforces CPU limits through CFS throttling: the container gets a slice of each scheduling period and waits out the remainder, so a multi-threaded service can burn its slice early and stall until the next period. Memory can’t be throttled, which is why exceeding a memory limit means an OOM kill. The gap between request and real usage represents wasted capacity, since a 2000m request serving 200m of load still bills for the 2000m.
Best Practices for Production Configs
The Vertical Pod Autoscaler (VPA) recommender targets the 90th percentile of observed usage with a 95th-percentile upper bound and a 15 percent safety margin. For slightly higher requests, use CPU near the 95th percentile (P95) and memory near weekly peak plus a margin. Starting CPU requests at the 50th percentile (P50) has thin evidence behind it and under-reserves any service with a daily peak. Set memory limit equal to request and never set a CPU limit, because throttling shows up as tail latency before it shows on a CPU graph. The exceptions are multi-tenant nodes where tenants fight for cores and workloads that need Guaranteed QoS, which requires limits equal to requests.
Guard Namespaces with LimitRange and ResourceQuota
Namespace guardrails stop one team from undoing that calibration. The LimitRanger admission controller injects defaults from a LimitRange into any container that ships without requests or limits. A ResourceQuota sets a hard ceiling on a namespace’s total requests and limits and rejects any pod that would push past it. The canonical example from the Kubernetes docs for namespace memory defaults is below.
apiVersion: v1
kind: LimitRange
metadata: {name: mem-limit-range}
spec:
limits:
- default: {memory: 512Mi}
defaultRequest: {memory: 256Mi}
type: Container
A pod with no resources set gets a 256Mi request and a 512Mi limit.
Right-Size Workloads with Vertical Pod Autoscaler
VPA turns request calibration from a quarterly spreadsheet into a controller loop for container rightsizing. Its recommender builds a decaying histogram of usage per container that weights recent samples more heavily. With the VPA update modes, Off only writes recommendations; Initial applies recommendations at pod creation. Recreate evicts pods whose requests drift too far and is the default. The project deprecated Auto as an alias for Recreate.
Start with VPA in Off in production long enough to compare recommendations against current requests. The restart penalty that kept VPA away from stateful workloads is mostly gone: in-place pod resize entered alpha in Kubernetes 1.27 and reached general availability in 1.35. VPA’s InPlaceOrRecreate mode tries a live resize before falling back to eviction. The VPA docs warn that in-place updates “cannot guarantee zero disruption”: a node without headroom can block a resize indefinitely, and shrinking memory can trigger an OOM kill. The known VPA limitations also rule out sharing a resource metric with the HPA, since VPA changes the request the HPA uses as its denominator; pair them on different metrics or keep VPA in Off.
Scale Dynamically with HPA, VPA, and Karpenter
Autoscaling adds capacity along two axes that rightsizing leaves alone: replica count and node count. The HPA handles replicas and Karpenter or the Cluster Autoscaler handles nodes, and both read the requests you set earlier, so scaling a mis-sized request produces a cluster full of mis-sized nodes.
Horizontal Pod Autoscaler (HPA)
The HPA scaling algorithm sets desired replicas to current replicas times the ratio of observed to target utilization, where utilization is a percentage of request averaged across pods. The metrics pipeline, the controller, and pod startup don’t react instantly, so a high utilization target can leave too little headroom. HPA utilization targets vary by workload. You could test targets of 60 to 70 percent for web services or 50 percent for workloads with slow cold starts. For batch workloads, an 80 percent target is another option to test.
For queue depth, Kafka lag, or time windows, Kubernetes Event-Driven Autoscaling (KEDA) creates and manages the HPA for you. The KEDA cron scaler can hold desiredReplicas: "10" from 6:00 to 20:00 in a named timezone and drop to zero outside that window. Scheduled scaling gives you an alternative to reactive scaling for workloads whose traffic follows office hours.
Karpenter and Node Autoscaling
Karpenter, a Kubernetes Special Interest Group (SIG) Autoscaling project, replaces the node-group model with direct instance launches sized to pending pods. Its consolidation loop reclaims nodes that go empty or underutilized when the Karpenter consolidation policy is WhenEmptyOrUnderutilized. Salesforce’s migration across its 1,000-plus Amazon Elastic Kubernetes Service (EKS) clusters saved five percent in fiscal year 2026, with node scaling moving from minutes to seconds.
Use Node Scheduling Controls to Maximize Efficiency
The default scheduler spreads pods to limit blast radius, which works against cost. Switching NodeResourcesFit from the default LeastAllocated to the MostAllocated scoring strategy fills nodes before opening new ones, and consolidation then reclaims the empties. Packing has a cost: a node failure takes down more replicas, and tight packing on underestimated requests raises throttling risk. To reserve nodes for a workload class, taint the batch pool, give only the batch jobs a toleration, and add node affinity so those jobs can’t land anywhere else.
Pod anti-affinity with topologyKey: topology.kubernetes.io/zone spreads replicas across availability zones (AZs); topology spread constraints with maxSkew: 1 do the same job. The docs advise against inter-pod anti-affinity in clusters of several hundred nodes.
Under contention, a PriorityClass with preemptionPolicy: Never queues a job ahead of others without evicting running work, while the default PreemptLowerPriority evicts to make room.
Request calibration comes first, with spread rules alongside it.

Reclaim Idle and Orphaned Resources
Pre-production environments are often the biggest cleanup targets. kubectl top pods –all-namespaces –sort-by=memory and kubectl top nodes give a quick view of what’s consuming what, with –sum to total the listed pods; historical analysis belongs in your Kubernetes monitoring stack. Jobs with no ttlSecondsAfterFinished linger until something deletes them, and ConfigMaps outlive the deployments that referenced them.
PersistentVolumes with a Retain policy stay in Released after you delete their claim until an administrator reclaims them.
Popeye lints a live cluster for unused ConfigMaps, Secrets, persistent volume claims (PVCs), and HPAs. kube-janitor deletes anything you annotate with a time to live (TTL), so kubectl annotate deploy nginx janitor/ttl=24h removes a preview deployment after 24 hours.
Monitor Resource Utilization Continuously
Every request block is a hypothesis about load that stops being true a few deployments later, and rightsized workloads commonly drift back to padded values because new manifests ship with the old numbers. Track request versus actual per pod and per namespace as a trend: kube_pod_container_resource_requests from kube-state-metrics against container_cpu_usage_seconds_total and container_memory_working_set_bytes from cAdvisor, scraped by Prometheus. Grafana is the usual front end for that ratio, and cost-allocation tools such as Kubecost attribute that waste to teams. Alert on the request-to-usage ratio alongside the Kubernetes metrics that track node pressure.
Coralogix’s Kubernetes Infrastructure Monitoring preset for the OpenTelemetry Collector feeds Infrastructure Explorer, which ranks pods by CPU and memory and has a Noisy Neighbor widget in the node detail view. Continuous Profiling uses the OpenTelemetry profile signal to show which function consumes a pod’s CPU, so an over-provisioned pod with one hot function becomes a code fix instead of a request cut.
Treat the Five Layers as a Maturity Curve
Use telemetry to monitor request drift continuously.
- Requests and limits: Calibrated requests and limits come first because every autoscaler reads them.
- VPA: VPA keeps requests calibrated.
- HPA and Karpenter: HPA and Karpenter scale what’s now correctly sized.
- Scheduling controls: Scheduling controls pack and protect workloads.
- Monitoring: Monitoring catches the drift that undoes the first four layers.
For clusters running Kubernetes-native microservices, Coralogix keeps infrastructure metrics, profiles, logs, and traces in one platform. Olly, Coralogix’s autonomous observability agent, answers natural-language questions across logs, metrics, traces, and alerts.

Frequently Asked Questions About Kubernetes Resource Optimization Strategies
Requests determine placement, while limits govern runtime behavior. VPA changes per-container requests, and HPA changes replica counts.
What Is the Difference Between a Resource Request and a Resource Limit in Kubernetes?
A request is the CPU or memory the scheduler reserves when choosing a node, and a limit is the ceiling enforced at runtime. Defaults fill the gaps: a container that sets a limit with no request gets a request equal to the limit, and a LimitRange can inject both. Leaving both requests and limits unset for every container drops the pod to BestEffort.
How Do I Find Over-Provisioned Workloads in My Cluster?
kubectl top pods --all-namespaces --containers shows live usage per container, which you compare against the requests in kubectl describe pod. A pod whose usage sits far below its request day after day is over-provisioned. To confirm across a whole cluster, deploy VPA with updateMode: "Off", which writes recommendations into each VPA object without changing a pod.
Does Running VPA Mean I No Longer Need HPA?
No, they scale different dimensions. VPA adjusts the CPU and memory requests of individual containers, and the HPA adjusts how many replicas run. Production clusters can run both, with the HPA on traffic-driven custom or external metrics and VPA keeping per-pod requests honest, because the two can’t safely share CPU or memory as a signal.
What Is Kubernetes QoS and Why Does It Affect Optimization?
The QoS class Kubernetes assigns from requests and limits sets a pod’s eviction and OOM priority, which makes limits a reliability decision as much as a cost one. Removing CPU limits to avoid throttling moves a pod from Guaranteed to Burstable and raises its eviction risk under node pressure. Workloads that need exclusive CPUs under the static CPU manager policy should stay Guaranteed.
To see your own request-versus-usage gaps before the next invoice, start a free 14-day Coralogix trial and point the Kubernetes collector at one cluster.