# Resource Quotas, Limits, and LimitRanges

> Chapter 13 of the Courseiva CNCF-CKAD curriculum — https://courseiva.com/learn/cncf-ckad/resource-quotas-and-limits

**Official objective:** 5.4 — Manage resource quotas and limits to control resource consumption

## Introduction

The CKAD exam objective 'Manage resource quotas and limits to control resource consumption' exists because, without boundaries, a single misbehaving application can crash an entire cluster. This chapter explains how Kubernetes enforces fairness and prevents resource starvation, so you can keep your apps stable and pass the exam.

## The Gated Community Apartment Complex Analogy

A gated community apartment complex has a fixed amount of total resources: water pressure in the pipes, electrical capacity from the main line, and parking spaces in the lot.

Every building in the complex (a Kubernetes Namespace) gets a strict quota from the homeowners' association. Building A can use a maximum of 100 litres of water per minute and 50 kilowatts of electricity. Building B, a smaller block, gets 50 litres per minute and 30 kilowatts. These are ResourceQuotas — hard caps on how much each building can draw from the shared supply, no matter what.

Inside each apartment (a Pod), the landlord sets individual limits. Apartment 1 has a LimitRange stating that any one apartment can request at least 5 litres per minute and at most 20 litres per minute, and a maximum of 10 kilowatts. This prevents one noisy neighbour from flooding their toilet while everyone else has no pressure. It also sets a default: if a tenant doesn't specify their water usage, the limit automatically applies 10 litres per minute — a default container limit.

When every apartment sticks to its limits, the whole complex runs smoothly. But if a tenant in Building A tries to turn on every tap and appliance at once, their own circuit breaker trips (the Pod is terminated or evicted). The resource quotas prevent Building A from stealing water and power from Building B. The LimitRanges prevent any single apartment from hogging the building's share. Without these rules, the complex would have brownouts, no hot water, and furious neighbours.

## Core explanation

Kubernetes is a system for running containerised applications across multiple machines (a cluster). Think of memory (RAM) and CPU as the two essential resources every container needs.

A container is a lightweight, standalone package of software that includes everything needed to run it: code, runtime, system tools, and libraries. A Pod is the smallest deployable unit in Kubernetes — it wraps one or more containers.

If you deploy a hundred Pods onto a cluster with no rules, one Pod could consume all available CPU and memory, starving others of resources. This is called a noisy neighbour problem. Resource quotas, limits, and LimitRanges prevent this.

**ResourceQuota** is a Kubernetes object that sets hard constraints on the total amount of resources a Namespace can use. A Namespace is a virtual cluster inside a physical cluster — it groups related resources together. A ResourceQuota can limit total CPU requests, total memory requests, total CPU limits, total memory limits, and even the count of objects like Services or Pods.

For example, a ResourceQuota in a Namespace called 'staging' might say: total CPU requests across all Pods cannot exceed 4 cores, and total memory requests cannot exceed 8 gigabytes. If a developer tries to create a Pod that would push total memory requests over 8 GB, Kubernetes rejects the creation with an error.

A **LimitRange** is a different object. It sets minimum and maximum constraints on individual Pods or containers within a Namespace, as well as default values when the user doesn't specify them.

A LimitRange can enforce: minimum CPU request per container, maximum CPU limit per container, minimum memory request, maximum memory limit, and default request and limit values.

For instance, a LimitRange in the same 'staging' Namespace could state: every container must request at least 64 megabytes of memory and at most 512 megabytes of memory. If a container YAML has a memory request of 32 megabytes, Kubernetes rejects it. If no container specifies a memory limit, the LimitRange automatically applies a default limit of 256 megabytes.

The crucial relationship: ResourceQuotas enforce the total sum of requests/limits across ALL Pods in the Namespace. LimitRanges enforce the boundaries for EACH Pod individually.

**How they work together**

1. An admin creates a Namespace, e.g., 'team-alpha'.
2. The admin applies a ResourceQuota: total CPU limit = 8 cores, total memory limit = 16 GB.
3. The admin applies a LimitRange: each container must have CPU request between 0.1 and 1 core, memory limit between 128 MB and 2 GB, with default values of 0.5 core CPU request and 256 MB memory request if not specified.
4. A developer submits a Pod YAML with a container requesting 1.5 GB memory.
   - LimitRange check: 1.5 GB memory request is within 128 MB to 2 GB — passes.
   - ResourceQuota check: the Namespace currently uses 14 GB of memory total. Adding 1.5 GB would exceed 16 GB — reject.

**CPU and memory units**

CPU is measured in cores. You can use millicores (m): 1000m = 1 core. Memory is measured in bytes, with common suffixes like Mi (mebibyte, 1024*1024 bytes) and Gi (gibibyte, 1024*1024*1024 bytes).

**Requests vs Limits**

A request is the amount of resource guaranteed to a container. Kubernetes uses requests to schedule Pods onto nodes with enough free resources. A limit is the maximum amount a container can burst to. When a container exceeds its memory limit, it gets killed (OOMKilled — Out Of Memory Killed). When it exceeds its CPU limit, it gets throttled (slowed down).

**Why this matters for CKAD**

You need to know how to create ResourceQuotas and LimitRanges using YAML, how to check their status, and how to troubleshoot Pods that fail to start due to quota or limit violations. The exam tests your ability to read and write these objects under time pressure.

## Real-world context

Imagine you are a platform engineer at a mid-sized e-commerce company. Your team of 15 developers share a single Kubernetes cluster for all their microservices. Each microservice runs in its own Namespace.

Without quotas, one developer's bleeding-edge search algorithm could accidentally consume all cluster CPU during a spike, causing the checkout service — which your company relies on for revenue — to slow to a crawl. Real money is lost every second of downtime during a flash sale.

Your job: create a ResourceQuota per Namespace that caps total CPU and memory. For the 'checkout' Namespace, you set: total CPU limit = 4 cores, total memory limit = 8 GB, because it's critical but lightweight. For the 'search' Namespace, you set: total CPU limit = 6 cores, total memory limit = 16 GB, because it's compute-heavy.

Next, you apply a LimitRange to the 'checkout' Namespace: every container must request at least 0.1 cores and at most 2 cores, with a default CPU request of 0.5 cores. This prevents any single container from monopolising the 4-core budget.

Three weeks later, a junior developer deploys a new version of the recommendation engine. The Pod YAML has no resource requests or limits. The LimitRange automatically injects a default request of 0.5 cores and a default limit of 1 core. The ResourceQuota checks show the Namespace still has room, so the Pod starts. The senior engineer is grateful that the defaults caught the missing specifications — no crash, no blame.

Another scenario: a developer accidentally sets a memory request of 4 GB for a container in a Namespace that only has a total memory quota of 2 GB. Kubernetes rejects the Pod immediately with a clear error message: 'exceeded quota: compute-resources, requested: memory=4G, used: memory=0.5G, limited: memory=2G'. The developer sees the error, adjusts the request to 1 GB, and redeploys. The outage never happens.

In a larger enterprise, you might use tools like kubectl describe quota and kubectl describe limitrange to inspect current usage and boundaries. You might also automate enforcement with scripts that scan Namespaces for quota violations.

Step by step, what a real admin does:

- Create a ResourceQuota YAML file.
- Apply it with kubectl apply -f quota.yaml.
- Create a LimitRange YAML file.
- Apply it with kubectl apply -f limitrange.yaml.
- Monitor quota usage with kubectl get quota --namespace=checkout.
- Troubleshoot Pod failures with kubectl describe pod failing-pod, looking for events like 'LimitRange' or 'Failed quota'.
- Educate developers: include resource specs in every deployment YAML as a matter of policy.

The business outcome: predictable performance, no surprise crashes, and high availability during peak traffic.

## Exam focus

The CKAD exam tests your ability to apply ResourceQuotas and LimitRanges quickly and correctly. You will face scenario-based questions where you must create or modify these objects to enforce resource governance.

**Typical question types:**

- Create a ResourceQuota in a given Namespace that limits total CPU requests to 2 cores and total memory requests to 4 Gi.
- Apply a LimitRange that sets a default CPU request of 0.5 cores and a default memory limit of 256 Mi in a Namespace.
- A Pod fails to start. You must examine the events and identify whether a ResourceQuota or LimitRange is blocking it.
- Given a Namespace with an existing ResourceQuota, you must create a Pod that stays within the quota (you have to calculate remaining capacity).
- Modify an existing LimitRange to change the max memory limit per container from 512 Mi to 1 Gi.

**Traps they set:**

- They give you a ResourceQuota YAML with a typo in the unit, like 'memory: 2G' instead of 'memory: 2Gi'. 'G' is not recognised; the quota will fail silently or be ignored. The exam expects proper Kubernetes units: Gi, Mi, Ki, or raw bytes.
- They ask you to update a LimitRange but the new max is lower than the current min, which is invalid. You must notice this and adjust both values.
- They give you a Pod YAML with requests that exceed the ResourceQuota. The Pod will stay in 'Pending' state. You must use 'kubectl describe pod' to see the error event 'exceeded quota'.
- They ask you to check quota usage. The command 'kubectl describe resourcequota' shows current usage and hard limits. You must read the 'Used' field accurately.
- They nest a container inside a Pod with multiple containers — each container must individually satisfy the LimitRange. If one container is missing resource spec, the LimitRange default applies.

**Exact concepts you must memorise:**

- ResourceQuota fields: 'hard' (the limits), 'scopes' (optional filters like 'BestEffort' or 'NotTerminating').
- LimitRange fields: 'default' (limits if not specified), 'defaultRequest', 'min', 'max', 'maxLimitRequestRatio'.
- The difference between 'requests' and 'limits' in both objects.
- Units: CPU in cores or millicores (m), memory in Mi or Gi.
- How to check quota usage: 'kubectl get quota -n <namespace>', 'kubectl describe quota <name> -n <namespace>'.
- How to apply changes: 'kubectl apply', 'kubectl edit', 'kubectl replace'.
- Namespace scoping: ResourceQuotas and LimitRanges are namespaced objects. They only affect Pods in the same Namespace.

**Key exam tip:**

Practise writing YAML from scratch. The exam environment has a browser-based terminal. You will not have autocomplete. You must know the exact structure. Memorise the skeleton for a ResourceQuota:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: my-quota
spec:
  hard:
    requests.cpu: "2"
    requests.memory: "4Gi"
    limits.cpu: "4"
    limits.memory: "8Gi"

And for a LimitRange:

apiVersion: v1
kind: LimitRange
metadata:
  name: my-limits
spec:
  limits:
  - max:
      cpu: "2"
      memory: "1Gi"
    min:
      cpu: "0.1"
      memory: "128Mi"
    default:
      cpu: "0.5"
      memory: "256Mi"
    defaultRequest:
      cpu: "0.5"
      memory: "256Mi"
    type: Container

The CKAD expects you to know that 'type: Container' is required in the LimitRange limits array. Without it, the object is invalid.

## Step by step

1. **Define the ResourceQuota YAML** — Create a YAML file with kind: ResourceQuota. Under spec.hard, list the total resources allowed, e.g., requests.cpu: '2', requests.memory: '4Gi'. This sets the pool size for the Namespace.
2. **Apply the ResourceQuota to a Namespace** — Use 'kubectl apply -f quota.yaml -n my-namespace' to create the quota. Kubernetes immediately validates the YAML. The quota is enforced for all subsequent Pod creations.
3. **Define the LimitRange YAML** — Create a YAML file with kind: LimitRange. Under spec.limits, set type: Container, then define min, max, default, and defaultRequest for CPU and memory. These clamp each container's resource consumption.
4. **Apply the LimitRange to the same Namespace** — Use 'kubectl apply -f limitrange.yaml -n my-namespace'. The LimitRange now enforces boundaries for every new Pod's containers in that Namespace.
5. **Create a Pod that stays within limits** — Write a Pod YAML that explicitly sets container.resources.requests and limits within the LimitRange's min and max. The Pod should be created successfully. Test with 'kubectl describe pod' to verify no quota or limit errors.
6. **Verify quota usage** — Run 'kubectl describe quota -n my-namespace'. This shows the current resource usage (Used) against the hard limits. If usage approaches 100%, no further Pods can be created without deleting existing ones.

## Comparisons

### ResourceQuota vs LimitRange

**ResourceQuota:**
- Sets aggregate limits on total resources across all Pods in a Namespace
- Applies only to the Namespace in which it is created
- Can limit object counts (e.g., number of Pods, Services)

**LimitRange:**
- Sets per-Pod or per-container min/max/default resource constraints
- Applies only to the Namespace in which it is created
- Cannot limit object counts; only resource values within containers

### Resource Request vs Resource Limit

**Resource Request:**
- Amount of resource guaranteed to the container by the scheduler
- Used to decide which node the Pod lands on
- If not set, may default to the value of the limit if a LimitRange is present

**Resource Limit:**
- Maximum amount of resource the container is allowed to use
- Exceeding memory limit causes container to be killed; exceeding CPU limit causes throttling
- If not set, may default to a LimitRange default or be unbounded

### CPU Resource Enforcement vs Memory Resource Enforcement

**CPU Resource Enforcement:**
- Exceeding CPU limit results in throttling, not termination
- Measured in cores or millicores (e.g., 0.5, 500m)
- Throttled container runs slower but continues to function

**Memory Resource Enforcement:**
- Exceeding memory limit results in container termination (OOMKilled)
- Measured in bytes with suffixes like Mi, Gi, Ki
- OOMKilled container exits with code 137 and must be restarted or rescheduled

### ResourceQuota 'hard' field vs LimitRange 'default' field

**ResourceQuota 'hard' field:**
- Specifies the total maximum resources allowed in the Namespace
- Enforced at Pod creation time; reject if sum would exceed quota
- Example: spec.hard.requests.memory: "8Gi"

**LimitRange 'default' field:**
- Specifies the default resource limit for a container when the Pod YAML omits it
- Not enforced as a hard stop; it injects a value so the Pod is not rejected
- Example: spec.limits[0].default.memory: "256Mi"

## Diagram

_A cluster with two Namespaces, each with its own ResourceQuota and LimitRange, enforcing resource boundaries on individual Pods._

```mermaid
flowchart TD
    A[Cluster] --> B[Namespace A]
    A --> C[Namespace B]
    B --> D[ResourceQuota: total CPU=4 cores, total Memory=8Gi]
    B --> E[LimitRange: each container min=0.1cpu/64Mi, max=1cpu/512Mi, default=0.5cpu/256Mi]
    B --> F[Pod 1 - container1: requests=0.3cpu/128Mi, limits=0.5cpu/256Mi]
    B --> G[Pod 2 - container2: requests=0.2cpu/64Mi, limits=0.3cpu/128Mi]
    E -.-> F
    E -.-> G
    D -.-> F
    D -.-> G
    C --> H[ResourceQuota: total CPU=2 cores, total Memory=4Gi]
    C --> I[Pod 3]
    H -.-> I
```

## Common misconceptions

- **Misconception:** ResourceQuotas and LimitRanges work globally across all Namespaces. **Reality:** ResourceQuotas and LimitRanges are namespaced objects. They only apply to Pods and other objects created within the same Namespace where the quota or limitrange is defined. (Beginners often confuse namespaced objects with cluster-scoped objects. They see quotas as a cluster-wide concept, but Kubernetes deliberately scopes them to Namespaces for multi-team isolation.)
- **Misconception:** Setting a ResourceQuota with a CPU limit of 4 means each Pod can use up to 4 cores. **Reality:** A ResourceQuota's hard limit on CPU is an aggregate limit for ALL Pods in the Namespace. Individual Pod limits are set via a LimitRange or directly in the Pod spec, not by the ResourceQuota. (The word 'quota' in everyday English suggests a per-person allowance, but in Kubernetes it refers to a pool shared by all. This mismatch causes misinterpretation.)
- **Misconception:** If you create a Pod without specifying resource requests or limits, and there is a LimitRange, the Pod will be rejected. **Reality:** If a LimitRange has a 'default' and/or 'defaultRequest' field, and the Pod does not specify resources, Kubernetes automatically injects those defaults. The Pod starts successfully. Only if the Pod's container violates a 'min' or 'max' constraint will it be rejected. (Test-takers assume any missing spec causes rejection. They overlook the default injection mechanism, which is precisely designed to prevent silent failures.)
- **Misconception:** You can create a ResourceQuota and a LimitRange in a Namespace and then forget about them — they enforce themselves automatically forever. **Reality:** ResourceQuotas only enforce for newly created resources after the quota is applied. Existing resources are not affected unless they are recreated. Also, quotas and limits can be edited or deleted, and misconfigured updates can break enforcement. (People treat policies as immutable. In reality, Kubernetes applies quotas at creation time, and an admin could accidentally delete the quota, removing all enforcement.)
- **Misconception:** A container that exceeds its CPU limit will be killed immediately, just like memory. **Reality:** A container that exceeds its CPU limit is throttled (its CPU time is reduced), not killed. Exceeding the memory limit causes the container to be killed with an OOMKilled exit code. (The exam often tests this distinction. Beginners lump CPU and memory together because both are resources, but Kubernetes treats them differently in enforcement — throttling vs termination.)
- **Misconception:** You need to set both requests and limits in a LimitRange — you cannot set just one. **Reality:** A LimitRange can set just 'default' (limits), just 'defaultRequest', just 'min', just 'max', or any combination. You can set only a maximum memory limit without setting a minimum, for example. (Sample YAMLs often show all fields together, leading learners to believe they are mandatory. Each field is optional.)

## Key takeaways

- A ResourceQuota sets a hard aggregated limit on the total resources (CPU, memory, storage) that all Pods in a Namespace can consume. 
- A LimitRange sets individual min, max, and default resource boundaries for each Pod or container in a Namespace. 
- Requests are what Kubernetes guarantees to a container for scheduling; limits are the maximum the container can burst to. 
- CPU limits cause throttling; memory limits cause the container to be killed. 
- If a Pod's container omits resource specs, a LimitRange's 'default' and 'defaultRequest' values are automatically injected. 
- ResourceQuotas and LimitRanges are namespaced, not cluster-scoped — they only affect resources in the same Namespace. 
- Always use proper Kubernetes units: Gi (not G), Mi (not M), and millicores (e.g., '500m') for CPU. 
- The CKAD exam loves testing the difference between ResourceQuotas (aggregate pool) and LimitRanges (per-Pod constraints). 

## FAQ

**What happens if I create a Pod that requests more memory than the ResourceQuota allows?**

Kubernetes rejects the Pod and it stays in 'Pending' state. Use 'kubectl describe pod pod-name' to see the event 'exceeded quota: compute-resources'. You must reduce the Pod's resource requests or delete other Pods to free up quota.

**Can a ResourceQuota limit the number of Pods in a Namespace?**

Yes. ResourceQuotas can limit the count of Kubernetes objects by using spec.hard with keys like 'count/pods' or 'count/deployments.apps'. For example, 'count/pods: "10"' limits the Namespace to 10 Pods.

**What is the difference between 'default' and 'defaultRequest' in a LimitRange?**

'default' sets the container's resource limit if the Pod YAML doesn't specify one. 'defaultRequest' sets the container's resource request. They are independent fields; you can set one without the other.

**Do ResourceQuotas apply to Pods that are already running when the quota is created?**

No. ResourceQuotas only enforce limits for Pods created AFTER the quota is applied. Existing Pods are unaffected until they are deleted and recreated.

**How do I check the current resource usage against a ResourceQuota?**

Use 'kubectl describe quota <quota-name> -n <namespace>' or 'kubectl get quota -n <namespace>'. The output shows 'Used' (current total consumption) and 'Hard' (the quota limits).

**Can I apply a ResourceQuota to a Namespace that already has Pods running?**

Yes, but the quota only applies to new Pods. The existing Pods' resource usage is not counted towards the quota until they are restarted or rescheduled. You may need to recreate existing Pods to enforce the quota properly.

## Check your understanding

1. **What is the difference in enforcement when a container exceeds its CPU limit versus its memory limit?**

   Answer: When a container exceeds its CPU limit, it is throttled (slowed down). When it exceeds its memory limit, it is terminated (OOMKilled).

2. **You have a ResourceQuota with 'hard.requests.memory: 4Gi' and current usage is 3.5Gi. Can you create a new Pod with a memory request of 1Gi?**

   Answer: No, because 3.5Gi + 1Gi = 4.5Gi, which exceeds the 4Gi quota. The Pod creation will be rejected by Kubernetes.

3. **If a LimitRange defines 'default' but not 'defaultRequest', and a Pod YAML omits both request and limit, what values does the container get?**

   Answer: The container's limit is set to the 'default' value (e.g., 256Mi). The request is not set by the LimitRange; it may default to the same value as the limit by Kubernetes behaviour, or the Pod may be rejected depending on scheduler settings.

4. **A Pod in a Namespace with a LimitRange specifying a minimum memory request of 128Mi is created with a memory request of 64Mi. What happens?**

   Answer: Kubernetes rejects the Pod because the container's memory request (64Mi) is below the LimitRange's minimum (128Mi). The Pod remains in 'Pending' state with an error event.

5. **What Kubernetes command shows the current resource usage of a ResourceQuota in a Namespace?**

   Answer: 'kubectl describe quota <quota-name> -n <namespace>' or 'kubectl get quota -n <namespace>' shows used and hard limits for each resource tracked by the quota.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/cncf-ckad/resource-quotas-and-limits
