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 |
PLACEMENT PRO TIP**Tip:** LimitRange 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 MISTAKE / WARNING**Common Mistake:** Setting `max.cpu` in LimitRange lower than the `default.cpu` value. 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 ≤ max` and `defaultRequest ≤ default`.
REMEMBER THIS**Remember:** LimitRange is namespace-scoped. You must create one per namespace that needs guardrails. A LimitRange in `payments-prod` has zero effect on pods in `orders-prod`.
COMMON MISTAKE / WARNING**Security:** Without 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.