
August 19, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Misconfigured Kubernetes Resource Limits and Requests are the single most common cause of mysterious pod restarts, node instability, and wasted cloud spend in production clusters. While developers often treat these values as optional metadata, the kube-scheduler and kubelet rely on them for every placement and eviction decision. Getting this configuration right is essential whether you are deploying a high-traffic Laravel API or a background queue worker. For teams managing infrastructure alongside application code, understanding these primitives is as critical as writing efficient SQL; if you are also tuning database performance, my guide on MySQL optimization for SaaS indexing and partitioning covers the complementary data-layer side of this equation.
How do Kubernetes Resource Limits and Requests actually work?
The distinction between requests and limits is fundamental to how Kubernetes operates, yet it remains widely misunderstood. Requests represent the amount of resources the kube-scheduler guarantees to a pod before placing it on a node. If a node has 4GB of allocatable memory and you request 1GB, the scheduler reserves that 1GB regardless of actual usage. Limits, conversely, are enforced at runtime by the container runtime (containerd/CRI-O) via Linux cgroups v2. They act as a hard ceiling; exceeding a memory limit triggers an immediate OOM kill, while exceeding a CPU limit results in throttling rather than termination.
In practice, this asymmetry matters enormously. On a recent legal-tech portal I maintained, we saw intermittent 504 errors during document generation. The pods had generous memory limits but CPU requests set far too low. During PDF rendering spikes, the kernel throttled the container because it exceeded its CPU share relative to neighbors, even though the node had idle cores. Raising the CPU request to match typical burst usage eliminated the latency spikes without changing the limit. This illustrates why treating requests as mere "hints" is dangerous: they directly determine your application's performance floor under contention.
What are the three Quality of Service (QoS) classes in Kubernetes?
Kubernetes assigns one of three QoS classes to every pod based entirely on how you configure Kubernetes Resource Limits and Requests. This classification determines eviction priority when nodes face resource pressure. Understanding these tiers is non-negotiable for production reliability.
- Guaranteed: Every container in the pod has identical requests and limits for both CPU and memory. These pods are last in line for eviction and receive the highest scheduling priority. Use this for databases, critical APIs, and payment processors.
- Burstable: At least one container has a request lower than its limit, or only one resource type matches. These pods can use excess node capacity but are evicted before Guaranteed pods when resources tighten. Suitable for web servers, batch jobs, and development environments.
- BestEffort: No requests or limits specified. These pods consume whatever is available and are evicted first. Never use this in production except for truly disposable debugging tasks.
| QoS Class | Request/Limit Relationship | Eviction Priority | Use Case | Risk Profile |
|---|---|---|---|---|
| Guaranteed | requests == limits (both CPU & mem) | Lowest (evicted last) | Critical services, DBs | Predictable, reserved cost |
| Burstable | requests < limits OR partial match | Middle | Web apps, workers | Variable perf under load |
| BestEffort | No requests/limits set | Highest (evicted first) | Dev/test only | Unpredictable, unstable |
For Nepal-based startups optimizing cloud spend, Burstable is often the pragmatic default. You might set memory requests to your observed p95 baseline and limits to 2x that value, allowing occasional spikes without paying for peak capacity 24/7. However, for any service handling financial transactions or legal documents where downtime equals lost trust, Guaranteed QoS justifies the premium. The key is making this choice deliberately per workload, not accidentally through inconsistent YAML.
How should you size CPU and memory values for production workloads?
Sizing Kubernetes Resource Limits and Requests requires empirical data, not guesswork. Start with observability: deploy metrics-server and Prometheus/Grafana before tuning. Run load tests that mirror real traffic patterns, then examine p95 and p99 consumption over sustained periods. For CPU, remember that 1000m equals one vCPU core. A common mistake is setting CPU requests based on average usage rather than baseline latency requirements; averages hide spikes that cause user-visible degradation.
Memory sizing demands different logic because of the OOM kill risk. Always set memory requests slightly above your observed steady-state usage to account for garbage collection pauses and temporary allocations. For PHP-FPM or Laravel applications, remember that each worker process consumes separate memory; a pod running 20 workers at 64MB each needs ~1.3GB minimum just for workers, plus overhead for the master process and OS buffers. I've seen teams set memory limits based on single-process benchmarks and wonder why pods crash under load. Profile the entire pod, not individual processes.
<!-- Example: Burstable Laravel Queue Worker -->
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
<!-- Example: Guaranteed MySQL Primary -->
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "4Gi"
cpu: "2000m" Note the units: memory uses binary suffixes (Mi, Gi), while CPU uses millicores (m). Mixing MB and Mi causes subtle miscalculations. Always validate manifests with kubectl apply --dry-run=client -o yaml before deploying to catch unit errors early.
Why do pods get OOMKilled despite having sufficient limits?
OOMKilled events when limits appear adequate usually stem from three root causes. First, memory leaks in application code gradually consume available headroom until the limit hits. Second, JVM or runtime heap settings conflict with container limits; a Java app with -Xmx4g inside a 4Gi limit will OOM because the JVM doesn't account for metaspace, thread stacks, and native memory. Third, child processes spawned outside the main container process tree may escape cgroup accounting in misconfigured runtimes.
Debugging requires systematic investigation. Check kubectl describe pod <name> for the exact OOM timestamp and last state exit code 137. Correlate with metrics to see if memory climbed steadily (leak) or spiked suddenly (burst allocation). For PHP applications, enable opcache memory profiling and check for unclosed database connections or large collection hydrations. On a recent e-commerce project, we discovered that exporting 10,000-product CSVs loaded entire result sets into memory; switching to chunked cursors reduced peak memory by 80% and eliminated OOM kills without raising limits. Application-level fixes often beat infrastructure-level band-aids. If you're building APIs that handle large datasets, the patterns in Laravel API best practices include memory-safe pagination strategies that prevent these issues upstream.
How do resource configurations interact with Horizontal Pod Autoscaler?
HPA depends entirely on accurate Kubernetes Resource Limits and Requests to calculate scaling thresholds. When you configure HPA to target 70% CPU utilization, it divides current usage by the pod's CPU request, not its limit. If requests are set too low, HPA scales prematurely; if too high, it delays scaling until users experience degradation. This coupling means resource tuning and autoscaling policy must be designed together, not independently.
Consider using custom metrics alongside or instead of pure CPU/memory for business-meaningful scaling. Queue depth, request latency percentiles, or active session counts often correlate better with user experience than raw resource usage. When combining HPA with cluster autoscaler, ensure your node pools have sufficient headroom; otherwise, HPA requests new pods that sit Pending while waiting for node provisioning, creating visible latency spikes during traffic surges.
Practical checklist for auditing existing resource configurations
Auditing live clusters prevents drift and catches misconfigurations before they cause incidents. Run these checks quarterly or after major deployments:
- Identify BestEffort pods:
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[].resources.requests == null)'— migrate these to Burstable or Guaranteed immediately. - Find over-provisioned workloads: Compare p99 usage over 7 days against requests using
kubectl top podshistorical data or Prometheus queries. Reduce requests where utilization stays below 30% consistently. - Detect limit/request mismatches: Flag pods where limits exceed requests by more than 4x; such wide ratios indicate guessing and increase noisy-neighbor risk.
- Validate QoS alignment: Ensure all production-critical namespaces contain only Guaranteed or Burstable pods with documented rationale.
- Check VPA recommendations: Even if you don't enable Vertical Pod Autoscaler in auto mode, its recommender provides valuable right-sizing suggestions based on actual usage history.
Document your resource policies in a central location. Teams that maintain living runbooks with per-service resource rationales recover faster from incidents and onboard new engineers more efficiently. Treat resource configuration as code-reviewed infrastructure, not ad-hoc YAML tweaks.
Stabilize Your Cluster With Intentional Resource Policies
Mastering Kubernetes Resource Limits and Requests transforms cluster management from reactive firefighting to predictable engineering. Start by profiling real workloads, choose QoS classes deliberately based on business criticality, and audit configurations regularly as traffic patterns evolve. The time invested in getting these values right pays dividends in reduced pager fatigue, lower cloud bills, and happier users. If your team needs hands-on help auditing resource configurations, designing autoscaling strategies, or migrating legacy deployments to modern Kubernetes practices, reach out to discuss your infrastructure challenges. Stable platforms start with intentional foundations.

