CertGrid CertGrid
Configuration·Kubernetes and Cloud Native Associate

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

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.

Nothing here depends on the node count - both objects are namespaced and enforced by the API server before a scheduler ever sees the Pod.
Server NameIP AddressOSRolesCPURAMHDD
CKA1001192.168.0.175Ubuntu 26.04 LTSControl Plane Node2 Core4 GB50 GB
CKA1001-NODE01192.168.0.176Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE02192.168.0.177Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE03192.168.0.178Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB

Before you start

  1. 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 run with no resources block 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            256Mi

    This 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.

  2. And a Pod that asked for too much, refused on the spot

    The same LimitRange has a max of 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 2

    Rejected 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 2

    Expected resultA Forbidden error naming the rule, the ceiling and the requested value.

    Success conditionYou can tell an admission rejection from a scheduling failure.

  3. 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      3

    Two 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/configmaps shows 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  1Gi

    Expected 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.

  4. 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           8s

    No 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=4

    This is the difference between creating a Pod yourself and asking a controller to. Your kubectl got the Forbidden in step 2; here the controller got it, so the message went to the controller's object. kubectl describe deploy will not show it. kubectl describe rs will.

    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=4

    Expected 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

Official sources