Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Kubernetes Resource Limits and Requests

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.

Requests vs Limits LifecycleSCHEDULING PHASEScheduler checks Node AllocatableReserves REQUESTED amount(Memory: 512Mi | CPU: 250m)RUNTIME PHASEKubelet enforces LIMITS via cgroupsHard cap on consumption(Memory: 1Gi | CPU: 500m)KEY BEHAVIORAL DIFFERENCECPU Limit Exceeded → Throttled (slow but alive)Memory Limit Exceeded → OOMKilled (pod terminated immediately)
Kubernetes Resource Limits and Requests operate in distinct phases: scheduling reservation versus runtime enforcement.

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 ClassRequest/Limit RelationshipEviction PriorityUse CaseRisk Profile
Guaranteedrequests == limits (both CPU & mem)Lowest (evicted last)Critical services, DBsPredictable, reserved cost
Burstablerequests < limits OR partial matchMiddleWeb apps, workersVariable perf under load
BestEffortNo requests/limits setHighest (evicted first)Dev/test onlyUnpredictable, 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.

Sizing Decision FlowSTART: Profile WorkloadIs latency-sensitive or stateful?YESNOGUARANTEED QoSrequest = limitSize to p99 observedBURSTABLE QoSrequest = p95 baselinelimit = 2x–3x requestAdd 10–20% safety marginMonitor & adjust monthly
Decision framework for choosing QoS class and sizing Kubernetes Resource Limits and Requests based on workload criticality.

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.

HPA Scaling CalculationMETRICS SERVERReports: 450m CPU used(actual consumption)POD SPECCPU Request: 500m(denominator for %)HPA CONTROLLERTarget: 70%450/500 = 90% → SCALE UPCOMMON PITFALLLow request (250m) + same usage (450m) = 180% → aggressive over-scalingBEST PRACTICESet request ≈ expected normal load so target % reflects true headroom
HPA calculates utilization percentage against requests, making accurate Kubernetes Resource Limits and Requests essential for stable autoscaling.

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:

  1. 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.
  2. Find over-provisioned workloads: Compare p99 usage over 7 days against requests using kubectl top pods historical data or Prometheus queries. Reduce requests where utilization stays below 30% consistently.
  3. Detect limit/request mismatches: Flag pods where limits exceed requests by more than 4x; such wide ratios indicate guessing and increase noisy-neighbor risk.
  4. Validate QoS alignment: Ensure all production-critical namespaces contain only Guaranteed or Burstable pods with documented rationale.
  5. 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.

Frequently Asked Questions

Requests guarantee minimum CPU and memory for scheduling. Limits cap maximum usage to prevent node exhaustion. Pods are scheduled based on requests but throttled or killed if they exceed limits.

Profile locally with `ab` or `wrk` against realistic traffic, measure p95 memory/CPU, then set requests to that baseline. Add 20% headroom. Never guess; unprofiled defaults cause OOMKills or wasted spend.

The kernel OOM-kills the container immediately. Kubernetes restarts it per restartPolicy. Repeated kills trigger CrashLoopBackOff. This indicates the limit is too low or the app has a memory leak requiring profiling.

The Linux CFS scheduler enforces CPU limits by restricting runtime within each 100ms period. Exceeding the quota causes throttling, increasing latency without killing the pod. Monitor `container_cpu_throttled_seconds_total` to detect performance degradation caused by overly restrictive CPU limits in production PHP-FPM or Node.js workloads.

For stateless web apps like Laravel or WordPress, setting limits equal to requests creates Guaranteed QoS, preventing eviction during node pressure. However, this wastes resources if actual usage fluctuates significantly. In my experience deploying PHP-FPM containers, allowing limits at 1.5x requests provides burst capacity while maintaining predictable scheduling behavior under load.

Eviction occurs when node-level memory or disk pressure exceeds thresholds, regardless of individual pod limits. Pods with Burstable QoS (limits > requests) are evicted first. Ensure requests reflect true baseline needs and configure PodDisruptionBudgets. On shared EC2 instances I manage, monitoring node metrics via Prometheus prevents surprise evictions during traffic spikes.

Requests determine schedulable capacity, not actual usage. Over-requesting reserves unused resources, inflating infrastructure bills. Under-requesting risks performance issues. Right-sizing requests to observed p95 usage optimizes cost. For Nepal-based clients billing in NPR, even small over-provisioning across dozens of microservices compounds into significant monthly waste measurable in lakhs.

Use Vertical Pod Autoscaler in recommendation mode, Prometheus with kube-state-metrics, or Goldilocks. These analyze historical usage patterns to suggest optimal values. Avoid manual tuning based on single snapshots. On production Laravel deployments, I combine VPA recommendations with application-specific load testing to validate suggestions before applying changes to live environments.

No. Resource fields are immutable after creation. You must update the Deployment spec and trigger a rolling rollout. Zero-downtime updates require proper readiness probes and surge settings. Using Deployer 7 or GitLab CI pipelines ensures consistent, auditable changes. Always test limit adjustments in staging first to avoid accidental OOMKills during the rollout process.

HPA scales based on metric utilization relative to requests, not limits. If requests are too low, HPA triggers premature scaling. If too high, pods won't scale until severely overloaded. Align requests with actual baseline consumption. For WooCommerce stores handling seasonal Dashain traffic, accurate requests ensure HPA responds appropriately to genuine demand rather than artificial thresholds.

Setting limits below PHP-FPM's max_children memory footprint causes immediate OOMKills. Ignoring OPcache shared memory leads to double-counting. Not accounting for worker process forks underestimates peak usage. Always calculate limits as (worker_memory × max_children) + master_overhead + opcache. Validate with stress tests mimicking production concurrency before deploying to nodes serving legal-tech portals or e-commerce platforms.

Use node taints and tolerations to isolate workload types. Set LimitRanges per namespace to enforce sane defaults. Apply ResourceQuotas to prevent tenant runaway consumption. On shared infrastructure hosting both Laravel APIs and WordPress sites, separating workloads prevents noisy-neighbor problems. Consistent labeling and admission controllers ensure policies apply correctly across diverse application stacks running on the same cluster.

Missing limits enable denial-of-service through resource exhaustion. Overly permissive limits allow cryptomining or fork bombs. Unbounded pods can starve critical system services. Always define explicit limits via LimitRange defaults. For client portals handling sensitive legal documents, enforcing strict resource boundaries is as important as network policies. Defense-in-depth requires treating resource governance as a security control, not just an operational concern.

Run `kubectl describe pod ` to see scheduler events. Check node allocatable vs requested resources with `kubectl top nodes`. Verify no taints block scheduling. Inspect ResourceQuota and LimitRange constraints. Often the issue is fragmented free space rather than total capacity. On clusters I maintain, enabling scheduler metrics reveals whether pending pods result from genuine scarcity or suboptimal bin-packing decisions.

Ephemeral storage limits constrain local scratch space like /tmp and container logs. Exceeding them triggers eviction. Persistent volumes handle durable data independently of pod lifecycle. For Laravel apps generating temporary PDFs or processing uploads, set ephemeral limits to prevent log bloat from killing pods. Reserve PVs for databases and media libraries. Confusing these causes either data loss or unnecessary evictions during normal operations.

Share this article

Quick Contact Options
Choose how you want to connect me: