# Cluster Hardening: Resource Quotas and Limit Ranges

> Chapter 10 of the Courseiva CNCF-CKS curriculum — https://courseiva.com/learn/cncf-cks/cluster-hardening-resource-quotas-limits

**Official objective:** 3.3 — Apply resource quotas and limit ranges to prevent resource exhaustion

## Introduction

A Kubernetes cluster can crash if one application hogs all the memory or CPU, bringing down every other application running on the same machine. Without limits, a single bad deployment can become a denial-of-service attack against your own system. Understanding resource quotas and limit ranges is essential for the CKS exam because they are the primary tools for preventing resource exhaustion—one of the most common causes of cluster downtime.

## The 250-Person Shared Kitchen Analogy

You are managing a shared kitchen with exactly 250 people living in the same building. Each person needs to cook their own meals, but the kitchen has only one stove, one oven, one sink, and one fridge. Everyone has to share these limited resources. The building manager knows that if even ten people try to boil pasta at the same time, the stove will spark and catch fire, destroying the whole kitchen. 

So the manager sets a rule: "No single person can use more than 20% of the stove's burners at once." That is a resource quota — a hard limit on how much of a shared resource any one person can claim. But the manager also notices that a few people are taking tiny portions, leaving the oven idle for hours. That is wasteful. So the manager adds another rule: "Every person must use at least one burner if they are cooking, and nobody can use less than 10% of the oven's capacity." That is a limit range — a minimum and maximum that makes sure no one hoards too little or too much. 

Now the kitchen runs smoothly. No fires start because no one uses too many burners at once. No oven sits cold because everyone has to use it enough to justify the space. The quota prevents a disaster, and the limit range prevents inefficiency. This is exactly how Kubernetes uses resource quotas and limit ranges to manage the CPU and memory usage of pods, protecting the cluster from crashing and ensuring fair sharing.

## Core explanation

In Kubernetes, a cluster is a group of machines (called nodes) that run containerised applications. Each application runs in a pod, and each pod requests a slice of the node's resources—specifically CPU (processing power) and memory (RAM). Without any controls, a single pod can consume all the CPU on a node, starving other pods and causing them to crash or be evicted. That is where resource quotas and limit ranges come in. 

A resource quota is a Kubernetes object that sets a hard limit on the total amount of resources that all pods in a particular namespace can use. Think of a namespace as a virtual cluster inside the real cluster—it is a way to organise and isolate groups of resources. When you create a resource quota, you define a maximum for things like total CPU, total memory, and total number of pods. If someone tries to create a pod that would push the namespace over that limit, Kubernetes rejects the creation with an error. This prevents any single team or project from exhausting the whole cluster. 

A limit range is a separate object that sets minimum and maximum constraints on individual pods or containers within a namespace. Where a quota limits the total for the whole namespace, a limit range limits each single pod. For example, you might say that every container must request at least 50Mi of memory and can use at most 500Mi of memory. It can also enforce a minimum and maximum for CPU. If a pod violates these limits—either by asking for too little (which might mean it is not set up properly) or too much (which could starve others)—Kubernetes will refuse to let it run. 

Why do we need both? Imagine an office with a shared coffee machine. A resource quota is like saying "the whole office can only use 10 litres of coffee per day." A limit range is like saying "each person is allowed to take between 100 millilitres and 500 millilitres per cup." Without the quota, one team might brew so much coffee that nobody else gets any. Without the limit range, someone might bring a bucket and take 9 litres at once, triggering the quota immediately but unfairly. Together they ensure both fairness and survival. 

When you define a pod, you can specify two numbers for each resource: requests and limits. A request is the minimum amount of resource that Kubernetes guarantees to the pod. A limit is the maximum amount the pod is allowed to use. For CPU, if a pod tries to go over its limit, Kubernetes throttles it (slows it down). For memory, the pod gets killed if it exceeds the limit. A resource quota can set limits on the sum of all requests across the namespace and the sum of all limits. A limit range can enforce per-container minimums and maximums for both requests and limits. 

If you set a resource quota but do not set a limit range, pods could still have wildly different resource profiles—some huge, some tiny—which can cause uneven scheduling and unpredictable performance. If you set a limit range but no quota, a single namespace could still consume the entire cluster. They are designed to work together. The CKS exam expects you to know how to create and apply both, and to understand what happens when they conflict (the stricter rule wins—Kubernetes will reject any pod that violates either the quota or the limit range). 

Another important point: resource quotas and limit ranges only apply within the namespace where they are defined. They are not global across the whole cluster. A common exam scenario is that you need to identify whether a pod creation failure is caused by a quota being exceeded or a limit range being violated. The error messages are different. For a quota violation, you will see a message like "exceeded quota". For a limit range violation, you will see "minimum memory usage per container is 50Mi". 

Finally, there is a special resource called a LimitRange that can also set default values. If a pod does not specify a request or limit, the LimitRange can inject a default. This is useful for ensuring that every pod in a namespace has at least some resource footprint, even if the developer forgot to set one. This defaulting behaviour is a frequent exam topic.

## Real-world context

You are a platform engineer at a company called FinFlow, which runs a microservices architecture on Kubernetes. There are ten development teams, each working on different services: auth, payments, notifications, analytics, and so on. Each team has its own namespace in the cluster. Without resource controls, a single team could deploy a memory-intensive job that eats up the entire cluster, causing all the other teams' services to crash. Real downtime, real cost. 

Your first task is to prevent that. You decide to set a resource quota on each team's namespace. For the finance team's namespace, you define the quota to allow a total of 4 CPU cores and 8 GB of memory across all their pods. You also limit the number of pods to 20. This means even if the finance team goes crazy, they cannot take down the whole cluster. You create the quota with a YAML file and apply it with kubectl. Now any attempt by the finance team to create a 25th pod or request more than 4 CPU cores is rejected at the API level. 

But that alone is not enough. You notice that some developers are creating pods with no requests or limits at all. Kubernetes then defaults to best-effort scheduling, meaning those pods can be killed if resources get tight. That causes unpredictable failures. To fix this, you create a LimitRange in each namespace. You set the minimum memory request to 100 MiB and the maximum to 1 GiB. You also set a default request of 256 MiB and a default limit of 512 MiB. Now every pod that gets created will have at least those minimums, and no pod can hog more than 1 GiB of memory. 

A month later, the analytics team deploys a new data processing job. The job needs 3 CPUs and 6 GB of memory per pod—way above the per-container limit. Kubernetes rejects the pod with an error message saying it violates the LimitRange. The analytics team complains, but you have a procedure: they can request a temporary exception by opening a ticket. You check the cluster capacity—there is enough headroom—so you temporarily adjust the LimitRange for their namespace to allow larger containers for their batch job. After the job finishes, you revert it. 

Everything runs smoothly until a developer on the auth team accidentally creates a deployment with a memory limit of 0.5 GiB—but the namespace already has pods consuming 7.9 GB of the 8 GB quota. The new pod cannot be scheduled because the total would exceed the quota. The developer gets an error: "exceeded quota: finance-quota, requested: memory=500Mi, used: memory=8192Mi". He calls you in a panic. You explain that the quota is a safety net, and he needs to either reduce the resource usage of existing pods or get an exception to increase the quota. He removes an old unused deployment, freeing up exactly enough memory, and the new pod starts. 

In a real production environment, you would also monitor resource usage with tools like Prometheus and set alerts when a namespace approaches 80% of its quota. You would create automated policies that prevent any namespace from going over its quota, and you would document the process for requesting quota increases. This kind of governance is what keeps a multi-tenant Kubernetes cluster stable and fair.

## Exam focus

The CKS exam tests resource quotas and limit ranges under the "Cluster Hardening" domain, specifically regarding preventing resource exhaustion. Expect 2 to 4 multiple-choice questions and one or two performance-based tasks where you must create or modify these objects in a live cluster. 

The most common trap is confusing the scope of each object. A ResourceQuota limits the aggregate of all pods in a namespace. A LimitRange limits individual containers. The exam loves to give you an error message and ask what is wrong. For example, they show a pod creation failure with the message "exceeded quota" and ask you to fix it. The correct answer is to either reduce the pod's resource requests or increase the quota. If the error says "minimum memory per container is 50Mi" then it is a LimitRange violation, and you must adjust the pod spec or the LimitRange. 

Another frequent topic is default values. The exam expects you to know that if a LimitRange defines default requests and limits, those values are injected into any pod that does not specify its own. If a pod specifies only a limit but no request, the LimitRange can set the request equal to the limit (or to a default). You might be asked what happens when a LimitRange has no default—the pod is rejected. 

Key concepts to memorise: 

- ResourceQuota applies to a namespace, not to the whole cluster. 
- LimitRange applies per container, not per pod (though it also can apply per pod). 
- Both are enforced at admission time (when you try to create a pod), not at runtime. 
- CPU and memory are the most common resources; you can also set limits on ephemeral storage, persistent volume claims, and object counts (like pods, services, secrets). 
- For CPU, exceeding the limit causes throttling (slowing down) but not killing. For memory, exceeding the limit causes the container to be killed (OOMKilled). 
- A pod that specifies no requests or limits will be subject to the LimitRange defaults. If no defaults exist, the pod will be rejected if it violates any LimitRange minimums or maximums. 
- You can have multiple ResourceQuotas in one namespace; the effective limit is the most restrictive combination. 
 
Exam traps: 
- They may ask you to identify what happens when a pod's limit exceeds the quota. The pod is rejected. 
- They may give a YAML with a ResourceQuota that has 'hard' and 'scopes' fields; scopes restrict the quota to certain kinds of pods (like those with a specific priority class). 
- They may ask you to create a LimitRange that sets 'min' and 'max' but not 'default'—the pod must specify values within that range. 
- They may present a scenario where a pod has a request larger than the quota's total—the pod is rejected. 
- They may test you on the difference between 'requests' and 'limits' and which one a quota enforces on. The quota can enforce on requests, on limits, or both. 

Practical preparation: learn the kubectl commands. To see the current quota: kubectl get resourcequota -n <namespace>. To see a LimitRange: kubectl get limitrange -n <namespace>. To describe them: kubectl describe resourcequota <name> -n <namespace>. You should be comfortable editing YAML files for both objects. You should also understand how to use kubectl create quota and kubectl create limitrange with command-line flags.

## Step by step

1. **Define the Namespace** — Resource quotas and limit ranges are tied to a namespace. First, create or identify the namespace you want to protect. This is where your rules will apply.
2. **Create the ResourceQuota YAML** — Write a YAML file specifying the hard limits for total CPU, memory, and pod count in the namespace. For example, spec.hard.cpu: '4', spec.hard.memory: '8Gi'. This tells Kubernetes the maximum aggregate resources allowed in that namespace.
3. **Apply the ResourceQuota with kubectl** — Use 'kubectl apply -f quota.yaml -n <namespace>' to enforce the quota. After this, any pod creation that would cause the namespace to exceed the quota will be rejected.
4. **Create the LimitRange YAML** — Write a YAML file defining minimum and maximum per-container values for CPU and memory. Optionally add default requests and limits. For example, spec.limits[0].min.memory: '100Mi', spec.limits[0].max.memory: '1Gi'.
5. **Apply the LimitRange with kubectl** — Use 'kubectl apply -f limitrange.yaml -n <namespace>'. Now any container that tries to start with memory below 100Mi or above 1Gi will be rejected.
6. **Test by creating a pod that violates the limits** — Attempt to create a pod with resource requests outside the limit range. Observe the error message. This confirms the enforcement is working and teaches you to diagnose failures.

## Comparisons

### ResourceQuota vs LimitRange

**ResourceQuota:**
- Limits total resources across all pods in a namespace
- Enforced as a sum (aggregate) across all pods
- Does not specify per-container minimums or maximums

**LimitRange:**
- Limits per-container resources within a namespace
- Enforced individually on each container at creation time
- Can specify per-container minimums, maximums, and default values

### CPU Limit vs Memory Limit

**CPU Limit:**
- Exceeding the limit causes throttling, not termination
- Measured in CPU cores (e.g., 1, 500m)
- Can be oversubscribed (more requests than physical cores)

**Memory Limit:**
- Exceeding the limit causes the container to be killed (OOMKilled)
- Measured in bytes (e.g., 256Mi, 1Gi)
- Cannot be oversubscribed—physical memory is finite

### Pod Requests vs Pod Limits

**Pod Requests:**
- Guaranteed minimum resource for the pod
- Used by scheduler to decide which node to place the pod on
- If requests exceed node capacity, pod stays Pending

**Pod Limits:**
- Maximum resource the pod is allowed to use
- Used to cap resource consumption under load
- If limits exceed quota, pod is rejected

## Diagram

_This diagram shows how a namespace contains both a ResourceQuota (overall limits) and a LimitRange (per-container limits), with their respective constraints._

```mermaid
flowchart TD
    A[Namespace] --> B[Resource Quota]
    A --> C[Limit Range]
    B --> D[Total CPU limit: 4 cores]
    B --> E[Total Memory limit: 8 GB]
    B --> F[Max Pods: 20]
    C --> G[Per-container min CPU: 100m]
    C --> H[Per-container max CPU: 2 cores]
    C --> I[Per-container min Memory: 100Mi]
    C --> J[Per-container max Memory: 1Gi]
    C --> K[Default request: 256Mi]
    C --> L[Default limit: 512Mi]
```

## Common misconceptions

- **Misconception:** Resource quotas and limit ranges are global settings that protect the entire cluster at once. **Reality:** They are namespace-scoped objects. A resource quota only limits resources within the namespace where it is created. To protect the whole cluster, you need to create a quota in every namespace, or use a cluster-wide policy mechanism like a MutatingAdmissionWebhook. (People see the word 'quota' and assume it works like a global monthly data cap, but Kubernetes intentionally scopes it to namespaces to allow multi-tenancy.)
- **Misconception:** If a pod exceeds its memory limit, Kubernetes just slows it down. **Reality:** For memory, exceeding the limit causes the container to be killed with an OOMKilled status. CPU exceeding the limit causes throttling, not killing. (This confusion arises because CPU and memory behave differently under stress. Many beginners assume all resources behave the same way.)
- **Misconception:** Limit ranges set the minimum and maximum for an entire pod. **Reality:** A LimitRange can set minimum and maximum per container, but it can also be defined at the pod scope. However, by default it applies to containers. You need to specify a 'default' type if you want pod-level rules. (People read 'limit range' and think it is a simple min-max sliders for pods, but the container-vs-pod nuance is subtle and often overlooked.)
- **Misconception:** You must always set both a resource quota and a limit range for a namespace to be secure. **Reality:** You can set either one independently. A quota without a limit range still prevents namespace-wide exhaustion. A limit range without a quota still forces each pod to meet resource constraints. They are complementary but not mandatory together. (Best-practice guides often show both together, leading to the belief they are inseparable. The exam tests your understanding that they are separate objects with different purposes.)

## Key takeaways

- Resource quotas set a hard limit on the total CPU, memory, and object counts per namespace, preventing any single team from exhausting the cluster.
- Limit ranges set per-container minimum and maximum resource values, ensuring no pod is too small or too greedy.
- When a pod violates a quota, Kubernetes rejects it at creation time with an 'exceeded quota' error.
- When a pod violates a limit range, Kubernetes rejects it with a 'minimum' or 'maximum' constraint violation error.
- For CPU, exceeding the limit causes throttling; for memory, exceeding the limit kills the container (OOMKilled).
- Both quotas and limit ranges are namespace-scoped—they do not protect the entire cluster unless applied to every namespace.

## FAQ

**Can a resource quota be changed after pods are running?**

Yes, you can edit the ResourceQuota object and reapply it. Existing pods are not killed, but new pod creations will be subject to the new limits.

**What happens if a pod's limit range conflicts with a resource quota?**

Both constraints are enforced. A pod that meets the limit range but causes the namespace to exceed the quota will be rejected. The stricter rule always wins.

**Do resource quotas work for any resource type?**

They work for CPU, memory, ephemeral storage, persistent volume claims, and object counts like pods, services, secrets, and configmaps. The most common are CPU and memory.

**What is the difference between 'requests' and 'limits' in a quota?**

A quota can limit either the sum of requests, the sum of limits, or both. 'Requests' is the guaranteed amount; 'limits' is the maximum allowed. The quota can restrict either or both totals.

**Can I have multiple limit ranges in the same namespace?**

Yes, but they are merged. If two limit ranges define different defaults, the most restrictive combined set is used. It is simpler to have one per namespace.

**Does a resource quota apply to existing pods?**

No, it only applies to new pod creations. Existing pods count towards the quota's usage, but they are not evicted if the quota is reduced. Only new creations are rejected.

## Check your understanding

1. **What happens to a container that tries to use more memory than its limit?**

   Answer: The container is killed with an OOMKilled status.

2. **What is the difference in scope between a ResourceQuota and a LimitRange?**

   Answer: A ResourceQuota limits aggregate totals across all pods in a namespace; a LimitRange limits individual containers within a namespace.

3. **Can a single namespace have both a ResourceQuota and a LimitRange?**

   Answer: Yes, they are complementary and can coexist. Both constraints are enforced at creation time.

4. **If a pod does not specify any resource requests or limits, and a LimitRange has defaults set, what values will the pod use?**

   Answer: The pod will use the default request and default limit defined in the LimitRange.

5. **What type of error message would you see if a pod creation violates a ResourceQuota?**

   Answer: An error saying 'exceeded quota' along with details of which resource was exceeded.

---

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