LimitRange
A namespace-scoped policy that sets default, minimum, and maximum resource constraints for individual containers and pods within a namespace. While ResourceQuota caps the total consumption of a namespace, LimitRange enforces boundaries at the per-container level — automatically injecting default requests/limits into pods that don't specify them.
LimitRange — Guardrails for Individual Containers
LimitRange vs ResourceQuota — The Key Difference
These two objects are complementary, not competing. Use both together:
+--------------------------------------------+| ResourceQuota | <- Namespace ceiling| "This entire namespace can use at most || 32 CPU and 64Gi memory total" |+--------------------------------------------+ | v+--------------------------------------------+| LimitRange | <- Per-container guardrails| "Each individual container must use || between 100m and 4 CPU" |+--------------------------------------------+ResourceQuota without LimitRange means a single container could claim all 32 CPU at once. LimitRange without ResourceQuota means you have per-container safety but no namespace ceiling. A Razorpay payments namespace running 30 microservices needs both.
A Production-Grade LimitRange
apiVersion: v1kind: LimitRangemetadata: name: payments-container-limits namespace: payments-prodspec: limits: # ── Per Container Limits ── - type: Container default: # Auto-injected limits if container specifies none cpu: "500m" memory: "512Mi" defaultRequest: # Auto-injected requests if container specifies none cpu: "100m" memory: "128Mi" max: # No container can exceed this ceiling cpu: "4" memory: "8Gi" min: # No container can go below this floor cpu: "50m" memory: "64Mi" # ── Per Pod Limits ── - type: Pod max: cpu: "8" # Total CPU across all containers in one pod memory: "16Gi" # ── Per PersistentVolumeClaim Limits ── - type: PersistentVolumeClaim max: storage: 100Gi # No single PVC can claim more than 100Gi min: storage: 1GiThe Auto-Injection Superpower
The most underused feature of LimitRange: if a pod is created without resource requests or limits, the kubelet automatically injects the default and defaultRequest values from the LimitRange before scheduling.
+------------------------------------------+| Developer submits pod with no resources | <- Simple spec, no resources block+------------------------------------------+ | v+------------------------------------------+| Kubernetes Admission Controller | <- Intercepts the create request| checks namespace LimitRange |+------------------------------------------+ | v+------------------------------------------+| LimitRange auto-injects defaults | <- requests: 100m CPU, 128Mi RAM| | limits: 500m CPU, 512Mi RAM+------------------------------------------+ | v+------------------------------------------+| Pod scheduled with safe resource bounds | <- Protects the node and other pods+------------------------------------------+Before and after auto-injection in practice:
# Developer submits this minimal pod spec — no resources block at all:containers: - name: api image: registry.razorpay.in/api:v2.1 # Kubernetes applies LimitRange and the pod actually runs as:containers: - name: api image: registry.razorpay.in/api:v2.1 resources: requests: cpu: "100m" # <- Auto-injected from defaultRequest memory: "128Mi" # <- Auto-injected from defaultRequest limits: cpu: "500m" # <- Auto-injected from default memory: "512Mi" # <- Auto-injected from defaultValidating LimitRange Is Working
# Inspect the active LimitRange in a namespacekubectl describe limitrange payments-container-limits -n payments-prod # Deploy a pod without any resource speckubectl run test-pod --image=nginx -n payments-prod # Verify LimitRange injected defaultskubectl get pod test-pod -n payments-prod -o yaml | grep -A 12 resources:# Should show auto-injected cpu and memory values # Check if a pod was rejected for violating min/maxkubectl get events -n payments-prod | grep LimitRangeError # Clean up test podkubectl delete pod test-pod -n payments-prodLimitRange Troubleshooting Reference
| Symptom | Likely Cause | Fix |
|---|---|---|
Pod rejected: exceeds max limit |
Container requests exceed LimitRange max | Lower the container's resource request or raise LimitRange max |
Pod rejected: below min limit |
Container request set too low | Remove the explicit request and let LimitRange inject defaults |
| LimitRange not injecting defaults | Pod already specifies resources explicitly | LimitRange only injects when the field is completely absent |
| OOMKilled after LimitRange applied | Default memory limit is too low for the workload | Increase default.memory in LimitRange or set explicit limits on that container |
| All pods in namespace failing admission | default.cpu exceeds max.cpu |
Fix the LimitRange — default must be ≤ max |
TipLimitRange is especially useful when onboarding new teams onto a shared cluster at CRED or Zerodha. New engineers can write simple pod specs without worrying about resource tuning — LimitRange ensures they always get sane defaults and cannot accidentally starve neighbouring services.
Common MistakeSetting
max.cpuin LimitRange lower than thedefault.cpuvalue. This creates an impossible constraint — the auto-injected default immediately violates the max, and every pod without explicit resources is rejected at admission. Always verify:default ≤ maxanddefaultRequest ≤ default.
RememberLimitRange is namespace-scoped. You must create one per namespace that needs guardrails. A LimitRange in
payments-prodhas zero effect on pods inorders-prod.
SecurityWithout a LimitRange, any pod in a namespace can consume unbounded CPU and memory — one misconfigured deployment can starve every other service on the node. Always apply a LimitRange to every production namespace before allowing teams to deploy.
Frequently Asked Questions
How does LimitRange interact with ResourceQuota when both are set on a namespace?
ResourceQuota caps the aggregate CPU/memory (and object counts) across the entire namespace, but it actually requires every pod in that namespace to specify requests/limits explicitly — without them, quota can't do its accounting. LimitRange fills that gap: it injects default requests/limits onto any container that omits them, and separately enforces a per-container min/max, so a namespace with both effectively guarantees every pod has explicit resource values within an allowed range.
What's a common mistake when setting LimitRange defaults on a namespace?
Setting a default that's too low for the namespace's typical workloads, so pods that don't specify their own requests silently get under-provisioned defaults and then get OOMKilled or throttled under real load — the failure often looks unrelated to LimitRange because the pod manifest itself never mentions resources at all. It's worth explicitly checking `kubectl describe limitrange` in a namespace before assuming a pod's effective resource values match what's in its own YAML.