LimitRange Defaults for Pods
A Pod created with no resources at all comes back holding a CPU request, a memory request and both limits. A second Pod is refused outright before it is stored. Both are the namespace acting on its own - and the third case, a Deployment that quietly runs half its replicas, reports the refusal somewhere you would not look.
Kubernetes Fundamentals Guide 5 of 46 Beginner
- Kubernetes1.36.4
- Cluster4 nodes
- Runtimecontainerd 2.2.6
- CNICalico v3.32.1
- TimeAbout 15 min
- Reviewed22 August 2026
Written against the versions above. LimitRange and ResourceQuota are core Kubernetes with stable behaviour. Both are enforced by admission controllers that are on by default in any conformant cluster, including managed ones.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA1001 | 192.168.0.175 | Ubuntu 26.04 LTS | Control Plane Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE01 | 192.168.0.176 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE02 | 192.168.0.177 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE03 | 192.168.0.178 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
Before you start
- A cluster and permission to create namespaces, ResourceQuotas and LimitRanges.
- This session creates a
quota-demonamespace holding a ResourceQuota (team-quota: 500m requests.cpu, 1Gi requests.memory, 4 pods, 3 configmaps) and a LimitRange (team-defaults: default request 100m/128Mi, default limit 200m/256Mi, max 1 CPU / 1Gi per container).
-
A Pod that asked for nothing, holding four values
The namespace is the unit of administration in Kubernetes: it is where quotas, defaults and permissions are attached. Here is what that means concretely.
kubectl runwith noresourcesblock at all - then read the Pod back:{"limits":{"cpu":"200m","memory":"256Mi"}, "requests":{"cpu":"100m","memory":"128Mi"}}Nobody typed those numbers. The LimitRange in this namespace supplied them during admission, before the object was stored. What you get back from the API is not what you sent it.
The LimitRange says exactly where each number came from - a default request and a default limit per container, plus a ceiling:
Type Resource Min Max Default Request Default Limit Container cpu - 1 100m 200m Container memory - 1Gi 128Mi 256MiThis matters more than it looks. A Pod with no requests is unschedulable in any cluster with a quota on requests, and it is invisible to the scheduler's capacity arithmetic. A LimitRange is how a cluster administrator stops that being every team's problem - and it is also why the same manifest behaves differently in two namespaces.
bash Example session kubectl create namespace quota-demonamespace/quota-demo createdkubectl run nolimits --image=nginx:1.29-alpine -n quota-demo --restart=Neverpod/nolimits createdkubectl get pod nolimits -n quota-demo -o jsonpath="{.spec.containers[0].resources}{\"\n\"}"{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}kubectl describe limitrange team-defaults -n quota-demoName: team-defaultsNamespace: quota-demoType Resource Min Max Default Request Default Limit Max Limit/Request Ratio---- -------- --- --- --------------- ------------- -----------------------Container cpu - 1 100m 200m -Container memory - 1Gi 128Mi 256Mi -Expected resultA Pod with no resources specified comes back with all four values filled in from the LimitRange.
Success conditionYou can point at the object that supplied numbers you never typed.
-
And a Pod that asked for too much, refused on the spot
The same LimitRange has a
maxof 1 CPU per container. Ask for 2:Error from server (Forbidden): pods "toobig" is forbidden: maximum cpu usage per Container is 1, but limit is 2Rejected synchronously, by the API server, before anything was stored. No Pod exists. Nothing went Pending, no event was written, and there is nothing to troubleshoot later - the error came straight back to whoever typed the command.
That is worth contrasting with the other way Kubernetes refuses work. A Pod that is Pending or CrashLooping *exists*, and you go looking for why. This one never existed. Admission rejections are the one class of Kubernetes failure you find out about immediately, and the message names the rule and both numbers.
bash Example session kubectl run toobig --image=nginx:1.29-alpine -n quota-demo --restart=Never --overrides='{"spec":{"containers":[{"name":"toobig","image":"nginx:1.29-alpine","resources":{"requests":{"cpu":"2","memory":"64Mi"},"limits":{"cpu":"2","memory":"128Mi"}}}]}}'Error from server (Forbidden): pods "toobig" is forbidden: maximum cpu usage per Container is 1, but limit is 2Expected resultA Forbidden error naming the rule, the ceiling and the requested value.
Success conditionYou can tell an admission rejection from a scheduling failure.
-
The quota keeps a running total
A ResourceQuota is the other half: not per-container rules but a budget for the whole namespace, with a live tally.
Resource Used Hard pods 1 4 requests.cpu 100m 500m requests.memory 128Mi 1Gi count/configmaps 1 3Two things to notice. The used column already counts the Pod from step 1 - and counts the values the LimitRange gave it, not the nothing that was typed. The two objects work together: defaults make every Pod countable, and the quota counts.
And
count/configmapsshows quotas are not only about compute. You can bound the number of almost any object in a namespace, which is how a cluster stops one team exhausting etcd with Secrets or Services.A quota is enforced at admission too, but only against whoever is creating the object - which leads directly to the next step.
bash Example session kubectl describe quota team-quota -n quota-demoName: team-quotaNamespace: quota-demoResource Used Hard-------- ---- ----count/configmaps 1 3limits.cpu 200m 1limits.memory 256Mi 2Gipods 1 4requests.cpu 100m 500mrequests.memory 128Mi 1GiExpected resultUsed and Hard side by side, with the step-1 Pod already counted.
Success conditionYou can see the LimitRange's defaults being spent against the quota.
-
Six replicas, three Pods, and no error on the Deployment
Now the case that catches people. A Deployment asking for 6 replicas in a namespace whose quota allows 4 Pods:
NAME READY UP-TO-DATE AVAILABLE AGE overquota 3/6 3 3 8sNo error. The Deployment was created successfully - it is a valid object, and creating it breaks no rule. Its ReplicaSet reports 6 desired and 3 present, and keeps trying.
The refusal is written on the ReplicaSet's events, not on the Deployment:
Warning FailedCreate replicaset-controller Error creating: pods "overquota-..." is forbidden: exceeded quota: team-quota, requested: pods=1, used: pods=4, limited: pods=4This is the difference between creating a Pod yourself and asking a controller to. Your
kubectlgot the Forbidden in step 2; here the controller got it, so the message went to the controller's object.kubectl describe deploywill not show it.kubectl describe rswill.A HorizontalPodAutoscaler hits this the same way and it is worth knowing in advance: the HPA writes a replica count onto the Deployment, the ReplicaSet controller tries to create the Pods, and the quota refuses them. The HPA then reports the count it wanted while the cluster runs fewer - and again, the only place the refusal is written is the ReplicaSet's events.
bash Example session sleep 8; kubectl get deploy overquota -n quota-demoNAME READY UP-TO-DATE AVAILABLE AGEoverquota 3/6 3 3 8skubectl get rs -n quota-demo -o custom-columns=NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.replicas --no-headersoverquota-84b7f7b9f7 6 3kubectl describe rs -n quota-demo | grep -A3 'Warning' | head -8 Warning FailedCreate 9s replicaset-controller Error creating: pods "overquota-84b7f7b9f7-jhrxd" is forbidden: exceeded quota: team-quota, requested: pods=1, used: pods=4, limited: pods=4 Warning FailedCreate 9s replicaset-controller Error creating: pods "overquota-84b7f7b9f7-z4m7f" is forbidden: exceeded quota: team-quota, requested: pods=1, used: pods=4, limited: pods=4Expected resultA Deployment reporting 3/6 with no error of its own, and FailedCreate on its ReplicaSet.
Success conditionYou know which object to describe when a Deployment under-delivers.
Troubleshooting
A Pod has resource requests and limits that are not in the manifest.
Why: A LimitRange in the namespace supplied defaults during admission.
Fix:Read it:
kubectl describe limitrange -n <ns>. The Default Request and Default Limit columns are where the values came from.A Deployment says 3/6 and there is no error anywhere on it.
Why: A controller, not you, is being refused - most often by a ResourceQuota. The message lands on the object doing the creating.
Fix:
kubectl describe rs -n <ns>and read the FailedCreate events, thenkubectl describe quota -n <ns>for the Used and Hard columns.The same manifest works in one namespace and is Forbidden in another.
Why: LimitRange max values and ResourceQuota hard values are per namespace. Nothing about the manifest changed.
Fix:Compare both namespaces:
kubectl get limitrange,resourcequota -n <ns> -o yaml.A quota exists but Pods with no resources still get created.
Why: A quota on
requests.cpuorrequests.memoryonly rejects Pods that specify them; without a LimitRange to supply defaults the behaviour differs by cluster version and configuration.Fix:Pair every compute quota with a LimitRange in the same namespace. That is the intended combination.