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
1apiVersion: v12kind: LimitRange3metadata:4 name: payments-container-limits5 namespace: payments-prod6spec:7 limits:8 # āā Per Container Limits āā9 - type: Container10 default: # Auto-injected limits if container specifies none11 cpu: "500m"12 memory: "512Mi"13 defaultRequest: # Auto-injected requests if container specifies none14 cpu: "100m"15 memory: "128Mi"16 max: # No container can exceed this ceiling17 cpu: "4"18 memory: "8Gi"19 min: # No container can go below this floor20 cpu: "50m"21 memory: "64Mi"22 23 # āā Per Pod Limits āā24 - type: Pod25 max:26 cpu: "8" # Total CPU across all containers in one pod27 memory: "16Gi"28 29 # āā Per PersistentVolumeClaim Limits āā30 - type: PersistentVolumeClaim31 max:32 storage: 100Gi # No single PVC can claim more than 100Gi33 min:34 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:
1# Developer submits this minimal pod spec ā no resources block at all:2containers:3 - name: api4 image: registry.razorpay.in/api:v2.15 6# Kubernetes applies LimitRange and the pod actually runs as:7containers:8 - name: api9 image: registry.razorpay.in/api:v2.110 resources:11 requests:12 cpu: "100m" # <- Auto-injected from defaultRequest13 memory: "128Mi" # <- Auto-injected from defaultRequest14 limits:15 cpu: "500m" # <- Auto-injected from default16 memory: "512Mi" # <- Auto-injected from defaultValidating LimitRange Is Working
1# Inspect the active LimitRange in a namespace2kubectl describe limitrange payments-container-limits -n payments-prod3 4# Deploy a pod without any resource spec5kubectl run test-pod --image=nginx -n payments-prod6 7# Verify LimitRange injected defaults8kubectl get pod test-pod -n payments-prod -o yaml | grep -A 12 resources:9# Should show auto-injected cpu and memory values10 11# Check if a pod was rejected for violating min/max12kubectl get events -n payments-prod | grep LimitRangeError13 14# Clean up test pod15kubectl 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 |
š” 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: Settingmax.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.
š Remember: LimitRange is namespace-scoped. You must create one per namespace that needs guardrails. A LimitRange inpayments-prodhas zero effect on pods inorders-prod.
ā ļø 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.