Kubernetes ResourceQuota and LimitRange with Examples

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope ResourceQuota for namespace totals and object counts, LimitRange for per-container defaults and min/max bounds, admission failure diagnosis, and quota exhaustion behaviour. Does not cover cluster capacity planning, autoscaling, NetworkPolicy, RBAC, quota scopes, or detailed storage-class quotas.

ResourceQuota and LimitRange both shape what can land in a namespace, but at different levels. ResourceQuota tracks totals across the namespace; LimitRange shapes individual Pods and containers before those totals are calculated. This walkthrough uses one isolated namespace, applies both policies, and deliberately triggers admission failures so you can read the exact error messages.


ResourceQuota vs LimitRange

Neither object applies cluster-wide. Each only affects the namespace where you create it.

Resource Controls
ResourceQuota Aggregate resource use and object counts across one namespace
LimitRange Defaults, minimums, and maximums for individual Pods, containers, or PVCs in one namespace

Per-container requests and limits are covered in Kubernetes requests, limits and QoS. This lesson adds the namespace policies that wrap those fields.


Prepare the Namespace and Apply ResourceQuota

Create the namespace

I use namespace quota-demo so every manifest and command stays in one sandbox.

bash
kubectl create namespace quota-demo

Sample output:

output
namespace/quota-demo created

The namespace starts with no quota or limit policy until you apply the objects below.

Apply and inspect the quota

A ResourceQuota object sets hard caps on what the namespace may consume. This example limits CPU and memory requests and limits, Pod count, and Service count:

yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: ns-quota
  namespace: quota-demo
spec:
  hard:
    requests.cpu: "500m"
    requests.memory: 256Mi
    limits.cpu: "1"
    limits.memory: 512Mi
    pods: "3"
    services: "2"

Apply the quota:

bash
kubectl apply -f ns-quota.yaml

Sample output:

output
resourcequota/ns-quota created

Wait for the quota status to initialize before reading used values:

bash
kubectl wait resourcequota/ns-quota -n quota-demo --for='jsonpath={.status.hard.pods}=3' --timeout=60s

List quotas in the namespace:

bash
kubectl get resourcequota -n quota-demo

Sample output:

output
NAME       REQUEST                                                                    LIMIT                                     AGE
ns-quota   pods: 0/3, requests.cpu: 0/500m, requests.memory: 0/256Mi, services: 0/2   limits.cpu: 0/1, limits.memory: 0/512Mi   1s

The REQUEST and LIMIT columns are shorthand views. For the full ledger, use describe:

bash
kubectl describe resourcequota ns-quota -n quota-demo

Sample output:

output
Name:            ns-quota
Namespace:       quota-demo
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     1
limits.memory    0     512Mi
pods             0     3
requests.cpu     0     500m
requests.memory  0     256Mi
services         0     2

Hard is the ceiling. Used is the current quota-accounted total for non-terminal Pod requests and limits, plus counted objects such as Pods and Services. Admission compares the requested increment against Hard - Used.

ResourceQuota sums requests and limits across non-terminal Pods.

Trigger missing-resource admission errors

With only ResourceQuota in place, try a Pod that omits resources:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: bare-pod
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
  restartPolicy: Never

Submit the manifest:

bash
kubectl apply -f bare-pod.yaml

Sample output:

output
Error from server (Forbidden): error when creating "bare-pod.yaml": pods "bare-pod" is forbidden: failed quota: ns-quota: must specify limits.cpu for: app; limits.memory for: app; requests.cpu for: app; requests.memory for: app

The quota tracks those four fields, but the Pod left them empty. The admission controller rejects the create before scheduling. This is not a scheduling problem—see Pod Pending and ContainerCreating when Pods stall after admission succeeds.

The fix is either add explicit resources to every container or add a LimitRange that injects defaults, which is the next step.


Apply LimitRange Defaults

Inject default requests and limits

LimitRange can supply defaults when a container omits resource fields:

yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: container-limits
  namespace: quota-demo
spec:
  limits:
  - type: Container
    default:
      cpu: 200m
      memory: 128Mi
    defaultRequest:
      cpu: 100m
      memory: 64Mi

Apply the LimitRange:

bash
kubectl apply -f limitrange-defaults.yaml

Sample output:

output
limitrange/container-limits created

Inspect the defaults:

bash
kubectl describe limitrange container-limits -n quota-demo

Sample output:

output
Name:       container-limits
Namespace:  quota-demo
Type        Resource  Min  Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---  ---  ---------------  -------------  -----------------------
Container   cpu       -    -    100m             200m           -
Container   memory    -    -    64Mi             128Mi          -

Inspect the admitted Pod

Re-apply the same bare Pod manifest:

bash
kubectl apply -f bare-pod.yaml

The Pod is admitted. Check what the API stored:

bash
kubectl get pod bare-pod -n quota-demo -o jsonpath='{.spec.containers[0].resources}{"\n"}'

Sample output:

output
{"limits":{"cpu":"200m","memory":"128Mi"},"requests":{"cpu":"100m","memory":"64Mi"}}

Your original YAML had no resources block. The admitted Pod spec shows values the LimitRange injected at admission time.


Enforce Minimum, Maximum, and Ratio Rules

Replace the defaults-only LimitRange with one that also sets bounds and a limit-to-request ratio:

yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: container-limits
  namespace: quota-demo
spec:
  limits:
  - type: Container
    min:
      cpu: 50m
      memory: 32Mi
    max:
      cpu: 300m
      memory: 256Mi
    default:
      cpu: 100m
      memory: 64Mi
    defaultRequest:
      cpu: 50m
      memory: 32Mi
    maxLimitRequestRatio:
      cpu: 2
      memory: 2

Apply the updated LimitRange (this replaces the defaults-only object in the same namespace):

bash
kubectl apply -f limitrange-minmax.yaml

Sample output:

output
limitrange/container-limits configured

Reject requests below the minimum

LimitRange minimums validate requests. A Pod below the minimum request fails:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: tiny-pod
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 10m
        memory: 16Mi
      limits:
        cpu: 20m
        memory: 32Mi
  restartPolicy: Never
bash
kubectl apply -f tiny-pod.yaml

Sample output:

output
Error from server (Forbidden): error when creating "tiny-pod.yaml": pods "tiny-pod" is forbidden: [minimum cpu usage per Container is 50m, but request is 10m, minimum memory usage per Container is 32Mi, but request is 16Mi]

Exact ordering may vary.

The resulting error focuses on the minimum CPU and memory requests. LimitRange minimums validate requests, maximums validate limits, and maxLimitRequestRatio validates the limit divided by the request.

Reject limits above the maximum

LimitRange maximums validate limits. Use values that remain inside ResourceQuota but violate LimitRange:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: huge-pod
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 100m
        memory: 100Mi
      limits:
        cpu: 400m
        memory: 300Mi
  restartPolicy: Never
bash
kubectl apply -f huge-pod.yaml

Sample output:

output
Error from server (Forbidden): error when creating "huge-pod.yaml":
pods "huge-pod" is forbidden:
[maximum cpu usage per Container is 300m, but limit is 400m,
maximum memory usage per Container is 256Mi, but limit is 300Mi,
cpu max limit to request ratio per Container is 2, but provided ratio is 4.000000,
memory max limit to request ratio per Container is 2, but provided ratio is 3.000000]

Exact formatting and ordering may vary.

Handle defaults lower than explicit requests

When a container sets requests but omits limits, LimitRange supplies the default limit. If that limit is below the explicit request, admission fails. LimitRange does not verify that its injected defaults are consistent with explicitly supplied requests.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: high-req-pod
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 250m
        memory: 128Mi
  restartPolicy: Never

The LimitRange injects limits of 100m and 64Mi, which are lower than the explicit requests:

bash
kubectl apply -f high-req-pod.yaml

Sample output:

output
The Pod "high-req-pod" is invalid:
* spec.containers[0].resources.requests: Invalid value: "128Mi": must be less than or equal to memory limit of 64Mi
* spec.containers[0].resources.requests: Invalid value: "250m": must be less than or equal to cpu limit of 100m

Raise the LimitRange default limit or lower the Pod request so request stays at or below limit.

LimitRange checks run per container before ResourceQuota totals are evaluated.


Demonstrate ResourceQuota Exhaustion

Fill the Pod and compute quotas

With bare-pod already admitted, add two more Pods each requesting 100m CPU / 64Mi memory and limiting 200m CPU / 128Mi memory. Save both in fill-pods.yaml:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: fill-pod-1
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 100m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 128Mi
  restartPolicy: Never
---
apiVersion: v1
kind: Pod
metadata:
  name: fill-pod-2
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 100m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 128Mi
  restartPolicy: Never

Apply both Pods:

bash
kubectl apply -f fill-pods.yaml

Sample output:

output
pod/fill-pod-1 created
pod/fill-pod-2 created

Wait for the quota ledger to reflect three Pods:

bash
kubectl wait resourcequota/ns-quota -n quota-demo --for='jsonpath={.status.used.pods}=3' --timeout=60s

Check consumption:

bash
kubectl describe resourcequota ns-quota -n quota-demo

Sample output:

output
Resource         Used   Hard
--------         ----   ----
limits.cpu       600m   1
limits.memory    384Mi  512Mi
pods             3      3
requests.cpu     300m   500m
requests.memory  192Mi  256Mi
services         0      2

Let the updated LimitRange inject its current defaults for the next Pod:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: over-quota-pod
  namespace: quota-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
  restartPolicy: Never

This keeps CPU and memory within quota, making pods=3 the only exceeded hard limit. Try to create it:

bash
kubectl apply -f over-quota-pod.yaml

Sample output:

output
Error from server (Forbidden): error when creating "over-quota-pod.yaml": pods "over-quota-pod" is forbidden: exceeded quota: ns-quota, requested: pods=1, used: pods=3, limited: pods=3

The three admitted, non-terminal Pods remain in the namespace. ResourceQuota blocks the additional create request but does not remove existing Pods.

Release quota and retry admission

Delete one Pod and wait for the quota ledger to update before retrying:

bash
kubectl delete pod fill-pod-1 -n quota-demo
bash
kubectl wait resourcequota/ns-quota -n quota-demo --for='jsonpath={.status.used.pods}=2' --timeout=60s
bash
kubectl apply -f over-quota-pod.yaml

The create succeeds because used dropped below hard. kubectl wait supports JSONPath-based conditions and prints confirmation when the value is reached.

Limit Service and other object counts

ResourceQuota can cap API object counts. This lab already sets pods: "3" and services: "2".

Create two ClusterIP Services:

bash
kubectl create service clusterip svc1 -n quota-demo --tcp=80:80

Sample output:

output
service/svc1 created
bash
kubectl create service clusterip svc2 -n quota-demo --tcp=8080:8080

Sample output:

output
service/svc2 created

A third Service hits the quota:

bash
kubectl create service clusterip svc3 -n quota-demo --tcp=9090:9090

Sample output:

output
error: failed to create ClusterIP service: services "svc3" is forbidden: exceeded quota: ns-quota, requested: services=1, used: services=2, limited: services=2

You can add persistentvolumeclaims the same way when you need to cap storage claims per namespace. Detailed storage-class quota keys are out of scope here; see Kubernetes persistent volumes for PVC basics.


Understand How ResourceQuota and LimitRange Interact

Admission applies namespace policy in a practical sequence:

  1. LimitRange may inject default requests and limits on containers that omit them.
  2. LimitRange validates per-container minimums, maximums, and limit-to-request ratios.
  3. ResourceQuota sums the resulting values (and object counts) against namespace hard limits.
  4. The API accepts or rejects the request.

The bare Pod example shows step 1 solving step 3: once LimitRange filled in requests.cpu and limits.cpu, the Pod satisfied the quota that previously returned must specify limits.cpu.

If a Deployment stops creating Pods after admission succeeds, check controller events in Deployment not creating Pods.

Existing workloads and policy changes

A few rules that catch people in production:

  • Changing a LimitRange does not rewrite resources on Pods that are already running.
  • Non-terminal Pods contribute their requests and limits to ResourceQuota used. The displayed status can take a short time to reflect recent creates, updates, or deletions.
  • New creates and updates must satisfy the policy in force at admission time.
  • Neither ResourceQuota nor LimitRange adds CPU or memory to nodes—they only constrain what the namespace may admit.

In-place resource resize

ResourceQuota does not resize running containers itself. It checks new objects and supported updates; Kubernetes also enforces ResourceQuota when an in-place Pod resize is requested, rejecting a resize that would exceed the namespace quota.

Kubernetes applies ResourceQuota and LimitRange constraints to supported in-place Pod resource resize requests.

Inspect hard and used values

A compact inspection set for any namespace:

bash
kubectl get resourcequota,limitrange -n quota-demo

Sample output:

output
NAME                     REQUEST                                                                           LIMIT                                            AGE
resourcequota/ns-quota   pods: 3/3, requests.cpu: 250m/500m, requests.memory: 160Mi/256Mi, services: 2/2   limits.cpu: 500m/1, limits.memory: 320Mi/512Mi   2m

NAME                          CREATED AT
limitrange/container-limits   2026-07-26T16:06:42Z

bare-pod retained the original defaults, fill-pod-2 uses explicit resources, and over-quota-pod received the newer LimitRange defaults. Updating a LimitRange does not rewrite existing Pods.

Drill into the quota ledger:

bash
kubectl describe resourcequota ns-quota -n quota-demo

Then inspect the LimitRange defaults and bounds:

bash
kubectl describe limitrange container-limits -n quota-demo

The describe output for LimitRange with min/max active looks like:

output
Type        Resource  Min   Max    Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---   ---    ---------------  -------------  -----------------------
Container   cpu       50m   300m   50m              100m           2
Container   memory    32Mi  256Mi  32Mi             64Mi           2

Diagnose Common Admission Errors

must specify limits.cpu

The ResourceQuota tracks CPU limits, but the container left limits unset and LimitRange did not supply a default. Add explicit resources or create a LimitRange with default and defaultRequest.

exceeded quota

The object would push namespace used above a hard value. Delete or scale down workloads, raise the quota, or reduce the Pod's requests and limits.

maximum cpu usage per Container

The container limit exceeds the LimitRange max for that resource. Lower the limit or relax the LimitRange maximum.

minimum memory usage per Container

The container request is below the LimitRange min. Raise the request or lower the LimitRange minimum.

Default limit is lower than an explicit request

When a container sets requests but omits limits, LimitRange supplies the default limit. If that limit is below the explicit request, admission fails. See the high-req-pod example earlier in this walkthrough.


What's Next


References


Summary

ResourceQuota caps what a namespace may consume in aggregate—CPU and memory requests and limits summed across Pods, plus counts of objects such as Pods and Services. LimitRange works on individual containers, injecting defaults when fields are missing and rejecting values outside configured minimums, maximums, or limit-to-request ratios.

The lab showed the natural order: a quota-only namespace rejected a bare Pod with must specify limits.cpu; adding LimitRange defaults let the same manifest through; min/max LimitRange rules produced targeted forbidden errors; and filling the Pod quota blocked further creates while three admitted Pods remained in the namespace. Neither policy changes node capacity—they only gate admission.

On exams, read the full forbidden message: must specify usually means missing fields, minimum or maximum points to LimitRange, and exceeded quota means namespace totals. Pair namespace policy with per-container requests and limits from Kubernetes requests, limits and QoS before you tune production namespaces.


Frequently Asked Questions

1. What is the difference between ResourceQuota and LimitRange?

ResourceQuota caps aggregate resource use and object counts across an entire namespace. LimitRange sets defaults, minimums, and maximums for individual Pods, containers, or PVCs in that namespace. Neither policy applies cluster-wide.

2. Why was my Pod rejected with must specify limits.cpu?

The namespace ResourceQuota tracks CPU limits, but the Pod manifest left limits unset and no LimitRange supplied a default. Add explicit resources or create a LimitRange with default and defaultRequest values.

3. Does ResourceQuota kill running Pods when the quota is full?

No. ResourceQuota blocks new create or update requests that would exceed a hard limit. Existing Pods keep running until you delete them or a controller scales them down.

4. Do LimitRange defaults change already running Pods?

No. LimitRange affects admission of new Pods and updates to Pod specs. Changing a LimitRange does not rewrite resources on Pods that are already running.

5. What counts toward ResourceQuota used?

Non-terminal Pods contribute their container requests and limits to compute quota. The pods object quota also counts only non-terminal Pods, while quotas such as services, configmaps, and persistentvolumeclaims count existing objects of that type.

6. Can I use ResourceQuota without LimitRange?

Yes, but every Pod must then declare the resource fields your quota tracks. Without defaults from LimitRange, bare Pods with no resources fail admission when the quota requires limits or requests.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)