Kubernetes LimitRanges and ResourceQuotas: Stop Your Cluster from Starving
Kubernetes resource governance with LimitRanges and ResourceQuotas.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Basic Kubernetes knowledge: pods, namespaces, kubectl
- ✓Familiarity with YAML manifests
- ✓Understanding of CPU/memory resource concepts
Use ResourceQuotas to set hard caps on total CPU and memory per namespace, and LimitRanges to enforce per-pod resource requests and limits. Without them, a single misconfigured deployment can exhaust cluster resources and starve other workloads.
Think of a shared office kitchen. ResourceQuota is the rule that says 'no one can take more than 10 cups of coffee a day total.' LimitRange is the rule that says 'each person can only fill their mug up to 12 ounces.' Without both, one person could brew a whole pot for themselves and leave everyone else with nothing.
You've deployed your microservices, everything's humming, and then one day a team deploys a cron job that requests 32GB of memory. Your cluster runs out, pods get evicted, and your payments service goes down at 3am. That's not bad luck — that's missing resource governance.
Kubernetes gives you two tools to prevent this: LimitRanges and ResourceQuotas. They're not optional in any multi-tenant or production cluster. Without them, you're one resources: {} away from a cluster-wide outage.
By the end of this article, you'll know exactly how to configure LimitRanges and ResourceQuotas to protect your cluster, enforce team fairness, and debug the inevitable 'exceeded quota' errors without panicking.
Why You Need Resource Governance: The Noisy Neighbor Problem
Without resource governance, any pod can request any amount of CPU or memory. In a multi-team cluster, one team's misconfigured deployment can consume all available resources, starving other teams' pods. This is the noisy neighbor problem.
ResourceQuotas solve this by capping total resource usage per namespace. LimitRanges solve it by enforcing per-pod resource boundaries. Together they ensure fair sharing and prevent a single bad deployment from taking down the cluster.
Before these tools existed, teams had to rely on manual monitoring and hope. I've seen a single node-exporter daemonset with resources: {} consume 8GB on every node because it defaulted to unlimited. That's a production incident waiting to happen.
ResourceQuotas: The Hard Cap Per Namespace
A ResourceQuota sets a hard limit on total resource usage within a namespace. You can cap CPU, memory, storage, and even object counts (pods, services, etc.). Once the quota is reached, no new pods can be created until resources are freed.
This is your first line of defense against a team accidentally consuming the entire cluster. But there's a catch: you must set resource requests on every pod, or the quota won't be enforced. Kubernetes rejects pods without requests if a quota exists.
I've seen teams create a quota and then wonder why their pods won't start — they forgot to add resources.requests to their deployments. The error message exceeded quota is clear, but people still miss it.
LimitRanges: Per-Pod Resource Boundaries
A LimitRange enforces min, max, and default resource values per pod or container within a namespace. This prevents a single pod from requesting an absurd amount of resources while also ensuring every pod gets a baseline.
Without LimitRanges, a team could create a pod with requests.memory: 100Gi — which would be denied by quota if the total exceeds it, but if quota has room, it's allowed. LimitRange catches this at the pod level.
LimitRanges also set default requests and limits for pods that don't specify them. This is useful but dangerous: if your app needs 2Gi but the default is 512Mi, it will get OOMKilled. Always verify defaults match your workload.
defaultRequest higher than default for the same resource. Kubernetes will reject the LimitRange with an error like Invalid value: "...": must be less than or equal to ... limit.How LimitRanges and ResourceQuotas Interact
LimitRanges and ResourceQuotas work together but enforce different scopes. LimitRanges validate each pod individually at creation time. ResourceQuotas track cumulative usage across all pods in the namespace.
When a pod is created, Kubernetes first checks the LimitRange (if any) to ensure the pod's resources are within min/max. Then it checks the ResourceQuota to ensure the new pod doesn't exceed the namespace's total cap.
A common pitfall: if you set a LimitRange with a max that is larger than the remaining quota, a pod that passes the LimitRange check may still fail the quota check. This is correct behavior, but it can confuse developers who only see the quota error.
Production Patterns: Setting Realistic Defaults
In production, you need to balance protection with flexibility. Setting overly restrictive defaults breaks deployments. Setting none leaves you exposed.
Pattern 1: Start with generous limits and tighten over time. Set max high enough to accommodate any reasonable workload, then monitor actual usage and reduce.
Pattern 2: Use defaultRequest and default to match your most common workload. If most services use 256Mi-512Mi, set defaults accordingly. For outliers, teams must explicitly override.
Pattern 3: Always set min to prevent zero-request pods. A pod with requests.cpu: 0 can still consume CPU, causing unpredictability. Set min to at least 10m or 50m.
kubectl describe quota and kubectl describe limitrange to see current usage and enforcement. This is your first step when debugging resource issues.When Not to Use LimitRanges and ResourceQuotas
In a single-team cluster where everyone trusts each other, you might skip these. But even then, a misconfigured deployment can cause issues. I'd still recommend at least a ResourceQuota.
For ephemeral clusters (CI/CD, test environments), strict quotas can cause unnecessary failures. Consider setting high quotas or none at all for short-lived clusters.
If you use autoscaling (Cluster Autoscaler), quotas can prevent the cluster from scaling up because new pods are rejected before the autoscaler can add nodes. This is a known pain point — you need to ensure quota is high enough to allow burst scaling.
Debugging Resource Governance Issues
When a pod fails to start, the error message usually points to quota or limit range. But sometimes it's not obvious.
Step 1: Run kubectl describe pod <pod-name> and look for events at the bottom. You'll see FailedCreate or Failed with a reason.
Step 2: Check quota: kubectl get quota -n <ns> -o yaml. Look at status.hard vs status.used.
Step 3: Check LimitRange: kubectl describe limitrange -n <ns>. Verify min/max/defaults.
Step 4: If the pod has no resources set, the LimitRange defaults are applied. Check if those defaults are appropriate.
I once spent an hour debugging a pod that kept getting OOMKilled. Turns out the LimitRange default memory limit was 256Mi, but the app needed 1Gi. The fix was to set explicit resources on the deployment.
Common Mistakes and How to Avoid Them
Mistake 1: Setting defaultRequest higher than default for the same resource. Kubernetes rejects the LimitRange. Fix: ensure defaultRequest <= default.
Mistake 2: Forgetting to set resource requests on pods when a quota exists. Pods fail with exceeded quota. Fix: always set resources.requests in your deployments.
Mistake 3: Setting quota too low, causing valid deployments to fail. Fix: monitor usage over time and adjust quota accordingly. Use kubectl describe quota to see usage.
Mistake 4: Not setting limits.storage in quota, allowing unbounded PVC usage. Fix: always include requests.storage and persistentvolumeclaims in quota.
requests.cpu: 0 in a LimitRange min is allowed but defeats the purpose. A pod with zero CPU request can still use CPU, causing unpredictable scheduling. Always set a positive min.The 4GB Container That Kept Dying
analytics namespace kept restarting with OOMKilled every 10 minutes.defaultRequest: 512Mi and default: 1Gi for memory, but the pod's deployment didn't set any resources. The LimitRange applied defaults: request 512Mi, limit 1Gi. The pod's actual memory usage was 2Gi, so it got OOMKilled immediately.resources.requests.memory: 2Gi and resources.limits.memory: 4Gi. Also updated the LimitRange to have maxLimitRequestRatio: 2 to catch future mismatches.- Always set explicit resource requests and limits on your pods.
- Relying on LimitRange defaults is a trap — they silently apply values that may not match your workload.
FailedCreate: quota exceededkubectl describe quota -n <ns> to see current usage vs hard limits. 2. Identify which resource is exhausted (CPU, memory, pods). 3. Either increase quota or reduce resource requests of pending pods.kubectl describe pod <pod> and check container resource limits. 2. Run kubectl describe limitrange -n <ns> to see default limits. 3. If default limit is too low, set explicit resources on the deployment.Invalid: spec.containers[0].resources.requests: Invalid value: ...: must be less than or equal to ... limitkubectl describe limitrange -n <ns>. 2. Adjust pod resource requests/limits to fall within the range.kubectl describe quota -n <ns>kubectl top pods -n <ns>| File | Command / Code | Purpose |
|---|---|---|
| noisy-neighbor-example.yaml | apiVersion: v1 | Why You Need Resource Governance |
| resource-quota-example.yaml | apiVersion: v1 | ResourceQuotas |
| limit-range-example.yaml | apiVersion: v1 | LimitRanges |
| production-patterns.yaml | apiVersion: v1 | Production Patterns |
| debug-commands.sh | kubectl describe pod my-pod -n my-namespace | Debugging Resource Governance Issues |
Key takeaways
Interview Questions on This Topic
What happens when a pod's resource request exceeds the remaining ResourceQuota but is within the LimitRange? Does the pod get created?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Kubernetes. Mark it forged?
3 min read · try the examples if you haven't