Containerization Fundamentals

Kubernetes Namespaces, ResourceQuotas & LimitRanges, In Depth

A Kubernetes cluster starts life as one big shared pool of compute. Anyone with access can create Pods, and by default nothing stops a single team — or a single buggy Deployment with replicas: 500 — from consuming every CPU and gigabyte the nodes can offer, starving everyone else. The first three governance objects you learn are the ones that tame that shared pool into named, fenced, budgeted areas: the Namespace (the boundary you put things in), the ResourceQuota (the budget that says how much that boundary may consume in total), and the LimitRange (the per-object rules that fill in sensible defaults and stop any single Pod from being silly).

These three are the bedrock of multi-tenancy — running many teams, environments, or customers on one cluster without them treading on each other. They are also some of the most commonly misunderstood objects in Kubernetes, because people assume a Namespace is a security boundary (it mostly is not) and assume a ResourceQuota and a LimitRange do the same job (they do not — they are partners). This lesson covers every field of all three, in beginner-friendly language, and then walks through the exact admission sequence that runs every time you create a Pod so you can predict precisely why a Pod is accepted, defaulted, or rejected. Everything runs on a free local cluster.

Learning objectives

By the end of this lesson you will be able to:

Prerequisites & where this fits

You should already know what a Pod is and how requests and limits work at the container level (covered in the Pods deep-dive: requests are what the scheduler reserves; limits are the ceiling the kubelet enforces). You need a local cluster — kind, minikube, or k3d — all free and laptop-friendly. This lesson sits in the Fundamentals module of the Kubernetes Zero-to-Hero course, right after ConfigMaps & Secrets and before kubectl mastery. It is the foundation the later multi-tenancy, RBAC, and scheduling lessons build on.

Core concept: three jobs, three objects

It helps to fix the division of labour before any YAML:

Object Scope Question it answers Acts when
Namespace Cluster-scoped (it contains namespaced objects) “Where does this object live, and who/what is grouped with it?” At name resolution / object placement
ResourceQuota Namespaced “How much may everything in this namespace, added up consume?” At admission, against the namespace’s running total
LimitRange Namespaced “What are the per-object min/max/default for things created in this namespace?” At admission, mutating and then validating each object

A Namespace is the fence. A ResourceQuota is the total budget for everything inside the fence. A LimitRange is the per-item rulebook inside the fence. You almost always want all three together: the Namespace to group, the LimitRange to make sure every Pod has requests/limits (so the quota can actually count them), and the ResourceQuota to cap the aggregate.

Namespaces: what they isolate (and what they don’t)

A Namespace is a virtual cluster inside your real cluster — a way to divide cluster resources between multiple users, teams, or environments. It is purely a logical grouping; it does not spin up new nodes or new control planes.

What a Namespace gives you

What a Namespace does NOT give you

This is where most misunderstandings live. A Namespace by itself does not provide:

Namespaced vs cluster-scoped objects

A frequent exam and interview point: not everything is namespaced. Run kubectl api-resources --namespaced=true and --namespaced=false to see the live split on your cluster. The essentials:

Lives in a namespace (namespaced) Lives outside any namespace (cluster-scoped)
Pods, Deployments, ReplicaSets, StatefulSets, DaemonSets Nodes
Services, Endpoints, EndpointSlices, Ingress Namespaces themselves
ConfigMaps, Secrets PersistentVolumes (the PV; the PVC is namespaced)
ServiceAccounts, Roles, RoleBindings ClusterRoles, ClusterRoleBindings
ResourceQuota, LimitRange, NetworkPolicy StorageClasses, IngressClasses, PriorityClasses
Jobs, CronJobs, HPA, PodDisruptionBudgets CustomResourceDefinitions (the definition; CR instances may be either)
Events (namespaced) Nodes, CSINodes, RuntimeClasses, PodSecurity is via labels on the NS

The pair that trips people up most: a PersistentVolume (PV) is cluster-scoped, but a PersistentVolumeClaim (PVC) is namespaced. A PVC in team-a binds to a cluster-wide PV, which is one reason storage quotas matter.

The built-in namespaces

Every cluster ships with four namespaces. Know what each is for:

Namespace Purpose Should you put workloads here?
default Where your objects go if you don’t specify a namespace No in production — it is a convenience for learning; create proper namespaces instead
kube-system Components Kubernetes itself runs (CoreDNS, kube-proxy, CNI, controller-manager on managed clusters) Never put your apps here
kube-public World-readable namespace (even unauthenticated); holds a cluster-info ConfigMap No — reserved for cluster bootstrap info
kube-node-lease Holds one Lease object per node for fast node heartbeats No — managed automatically

Creating, viewing, and deleting Namespaces

Imperatively:

kubectl create namespace team-a
kubectl get namespaces            # or: kubectl get ns
kubectl describe namespace team-a

Declaratively (the form you commit to Git), with the labels later objects rely on:

apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    team: alpha
    environment: dev
    # PodSecurity admission is configured by labels on the namespace:
    pod-security.kubernetes.io/enforce: baseline

Deleting a namespace is a big hammer: it deletes everything inside it — all Pods, Services, ConfigMaps, Secrets, PVCs — via cascading deletion.

kubectl delete namespace team-a

Gotcha — stuck Terminating namespaces. If a namespace hangs in Terminating, it is almost always a finalizer on the namespace (often left by a removed API extension/CRD) blocking cleanup. Inspect with kubectl get namespace team-a -o yaml and look at spec.finalizers / status. Resolve the underlying API service rather than force-removing finalizers, which can orphan resources.

Setting a default namespace with contexts

Typing -n team-a on every command is tedious and error-prone. A context in your kubeconfig bundles (cluster, user, namespace). Set the namespace once:

# Change the namespace of the current context permanently:
kubectl config set-context --current --namespace=team-a

# Verify what you're pointed at:
kubectl config view --minify | grep namespace:

From then on, bare commands act on team-a. To act on another namespace just for one command, still use -n; to act across all of them, use --all-namespaces (or -A):

kubectl get pods -A

The kubens tool (from kubectx) is a popular quality-of-life add-on for hopping between namespaces, but the built-in kubectl config set-context above needs nothing extra.

ResourceQuota: budgeting a whole namespace

A ResourceQuota caps the aggregate consumption of a single namespace. You write one ResourceQuota object (occasionally a few, scoped differently) in the namespace, and the quota admission controller rejects any new object that would push the namespace over a limit. There are three families of things you can cap: compute, storage, and object counts.

A critical rule to internalise now: if a ResourceQuota constrains requests.cpu/requests.memory/limits.cpu/limits.memory, then every Pod created in that namespace MUST set the corresponding request/limit — otherwise the Pod is rejected with must specify .... This is exactly why you pair quotas with a LimitRange (which supplies defaults). Hold that thought; we trace it fully later.

Compute resource quotas

These cap CPU, memory, ephemeral storage, GPUs, and other extended resources, in two flavours — the sum of all requests and the sum of all limits across non-terminal Pods in the namespace.

Quota key What it caps
requests.cpu Sum of CPU requests of all Pods
requests.memory Sum of memory requests
limits.cpu Sum of CPU limits
limits.memory Sum of memory limits
cpu / memory Shorthand for requests.cpu / requests.memory (older form)
requests.ephemeral-storage / limits.ephemeral-storage Local scratch disk requests/limits
requests.nvidia.com/gpu (or any extended resource) Sum of that extended resource’s requests
hugepages-<size> Sum of huge-page requests (e.g. hugepages-2Mi)
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-compute
  namespace: team-a
spec:
  hard:
    requests.cpu: "4"          # at most 4 cores reserved in total
    requests.memory: 8Gi
    limits.cpu: "8"            # at most 8 cores of ceiling in total
    limits.memory: 16Gi
    requests.nvidia.com/gpu: "2"

CPU is expressed in cores or millicores (500m = half a core). Memory uses binary suffixes (Mi, Gi). The quota counts only Pods in a non-terminal phase (i.e. not Succeeded/Failed), so finished Jobs free their budget.

Storage resource quotas

Storage quotas cap how much persistent storage a namespace can claim, and how many PVCs it can create — overall and per StorageClass.

Quota key What it caps
requests.storage Sum of storage requested across all PVCs in the namespace
persistentvolumeclaims Total number of PVCs
<storage-class>.storageclass.storage.k8s.io/requests.storage Sum of storage requested from a specific StorageClass
<storage-class>.storageclass.storage.k8s.io/persistentvolumeclaims Number of PVCs against a specific StorageClass
requests.ephemeral-storage / limits.ephemeral-storage Pod-local ephemeral disk (also a “compute” item)
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-storage
  namespace: team-a
spec:
  hard:
    requests.storage: 100Gi
    persistentvolumeclaims: "10"
    # Per-class caps let you ration expensive tiers:
    fast-ssd.storageclass.storage.k8s.io/requests.storage: 20Gi
    fast-ssd.storageclass.storage.k8s.io/persistentvolumeclaims: "3"

Gotcha. Per-StorageClass quota keys are matched on the exact StorageClass name. If you rename a StorageClass or a PVC omits storageClassName (and falls through to the default), the per-class line won’t match the way you expect.

Object-count quotas

You can cap the number of almost any object type, which protects the API server and etcd from a runaway loop creating thousands of objects. Two syntaxes exist:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-objects
  namespace: team-a
spec:
  hard:
    # Well-known short forms:
    pods: "50"
    services: "10"
    services.loadbalancers: "2"     # cap costly cloud LBs
    services.nodeports: "5"
    configmaps: "100"
    secrets: "100"
    replicationcontrollers: "20"
    persistentvolumeclaims: "10"
    # Generic form: count.<resource>.<group>
    count/deployments.apps: "20"
    count/jobs.batch: "30"
    count/cronjobs.batch: "10"
    count/ingresses.networking.k8s.io: "5"

The generic count/<resource>.<group> form works for any resource, including your own CRDs (count/widgets.example.com: "100"). The short forms (pods, services, secrets, etc.) are special-cased for core types. Note services.loadbalancers and services.nodeports count Services of those types specifically — handy for capping cloud load balancers, which cost real money.

Quota scopes — counting only some Pods

By default a compute quota counts all Pods. Scopes let one quota apply only to a subset, so you can, for example, give batch/best-effort work a different budget from your guaranteed services. There are two mechanisms.

spec.scopes takes a list of built-in scopes (a Pod must match all listed scopes to be counted):

Scope Matches Pods that…
Terminating Have a positive activeDeadlineSeconds (run-to-completion / batch)
NotTerminating Have no activeDeadlineSeconds (long-running services)
BestEffort Have QoS class BestEffort (no requests/limits at all)
NotBestEffort Have requests or limits set (QoS Burstable/Guaranteed)
PriorityClass Are matched by a scopeSelector on priority class (see below)
CrossNamespacePodAffinity Use cross-namespace pod (anti)affinity terms

Rule: a BestEffort quota may only constrain the pods count (BestEffort Pods have no requests/limits to sum). A NotBestEffort quota may constrain compute resources.

spec.scopeSelector is the more expressive form, currently used for priority classes, letting you give high-priority workloads a separate budget:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: high-priority-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    pods: "20"
  scopeSelector:
    matchExpressions:
      - scopeName: PriorityClass
        operator: In        # In | NotIn | Exists | DoesNotExist
        values: ["high"]

Important cluster behaviour. Once any ResourceQuota in a namespace uses a PriorityClass scope, Pods that set priorityClassName are only admitted if a matching scoped quota exists/permits them. This is sometimes enforced cluster-wide via the admission config’s LimitedResources. In short: scoped priority quotas can make priority classes “opt-in” per namespace.

Inspecting quota usage

The single most useful command for operators and on a quota is describe, which shows Used vs Hard side by side:

kubectl get resourcequota -n team-a
kubectl describe resourcequota team-a-compute -n team-a
Name:            team-a-compute
Namespace:       team-a
Resource         Used   Hard
--------         ----   ----
limits.cpu       2      8
limits.memory    4Gi    16Gi
requests.cpu     1500m  4
requests.memory  3Gi    8Gi

That Used/Hard view is how you answer “why was my Pod rejected?” — if Used + new request > Hard, admission fails.

LimitRange: per-object rules and defaults

A LimitRange is a policy applied to individual objects in a namespace. Where a ResourceQuota caps the namespace total, a LimitRange governs each Container, each Pod, and each PVC as it is created. It does two jobs:

  1. Mutates — fills in default (limit) and defaultRequest (request) on Containers that omit them. This is what makes a quota usable without forcing every author to write requests/limits by hand.
  2. Validates — rejects objects outside min/max, or whose limit/request ratio exceeds maxLimitRequestRatio.

A LimitRange has a list of items, each with a type:

type Applies to
Container Each container in a Pod (most common)
Pod The Pod as a whole (sum across its containers)
PersistentVolumeClaim Each PVC (only min/max on storage are meaningful)

Within each item you may set these fields:

Field Meaning Applies to type
default Default limit if the container sets none Container only
defaultRequest Default request if the container sets none Container only
min Minimum allowed value (reject if below) Container, Pod, PVC
max Maximum allowed value (reject if above) Container, Pod, PVC
maxLimitRequestRatio Max allowed limit ÷ request for a resource Container, Pod

A full example covering all three types:

apiVersion: v1
kind: LimitRange
metadata:
  name: team-a-limits
  namespace: team-a
spec:
  limits:
    - type: Container
      default:                 # becomes the container's LIMIT if unset
        cpu: "500m"
        memory: 512Mi
      defaultRequest:          # becomes the container's REQUEST if unset
        cpu: "100m"
        memory: 128Mi
      min:                     # no container may request/limit below this
        cpu: "50m"
        memory: 64Mi
      max:                     # no container may request/limit above this
        cpu: "2"
        memory: 2Gi
      maxLimitRequestRatio:    # limit may be at most 4x the request
        cpu: "4"
        memory: "4"
    - type: Pod
      max:                     # the WHOLE Pod (sum of containers)
        cpu: "4"
        memory: 4Gi
    - type: PersistentVolumeClaim
      min:
        storage: 1Gi
      max:
        storage: 50Gi

How the defaulting rules actually resolve

The defaulting logic has subtle but important corners — these are favourite interview gotchas:

LimitRange for PVCs

For type: PersistentVolumeClaim only min and max on storage are meaningful — there is no defaulting of PVC size. This stops someone claiming a 5 TiB volume by mistake (cap with max) or creating uselessly tiny volumes (min).

Inspecting a LimitRange

kubectl get limitrange -n team-a
kubectl describe limitrange team-a-limits -n team-a
Name:       team-a-limits
Namespace:  team-a
Type        Resource  Min  Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---  ---  ---------------  -------------  -----------------------
Container   cpu       50m  2    100m             500m           4
Container   memory    64Mi 2Gi  128Mi            512Mi          4
Pod         cpu       -    4    -                -              -
...

How Namespace + ResourceQuota + LimitRange interact

This is the heart of the lesson — the exact sequence that runs every time you kubectl apply a Pod (or a Deployment, which creates Pods). Understanding this order lets you predict precisely why a Pod is admitted, defaulted, or rejected.

When a Pod create/update request arrives at the API server, admission controllers run in two phases — mutating first, then validating:

  1. The LimitRange admission plugin (mutating) runs first. For every container missing requests/limits, it injects defaultRequest (as request) and default (as limit) according to the resolution rules above. After this step, the Pod spec has concrete numbers on every constrained resource. (This is why a Pod with no resources written by the author can still be counted by a quota.)
  2. The LimitRange admission plugin (validating) checks min/max/ratio. Using the now-defaulted values, it rejects the Pod if any container or the Pod total is below min, above max, or violates maxLimitRequestRatio.
  3. The ResourceQuota admission plugin (validating) runs. It reads the namespace’s current Used totals, adds this Pod’s (now concrete) requests/limits and object counts, and rejects the Pod if Used + new > Hard for any constrained dimension. It also enforces the rule that if a compute resource is under quota, the Pod must have that request/limit set — but because step 1 already supplied defaults, that requirement is usually satisfied automatically.

The dependency chain, in one sentence: the Namespace is where the LimitRange and ResourceQuota live; the LimitRange makes Pods concrete and bounded; the ResourceQuota then sums those concrete numbers against the namespace budget.

Kubernetes core objects

The diagram shows how these governance objects sit alongside the workload objects inside a namespace: the LimitRange and ResourceQuota fence the Pods, ConfigMaps, Secrets and PVCs that live in the same namespace, while cluster-scoped objects (Nodes, PVs, StorageClasses) sit outside.

The classic failure: quota without LimitRange

This is the single most common real-world surprise, and a frequent exam question:

You add a ResourceQuota that constrains requests.cpu. Suddenly Deployments that worked yesterday fail to create Pods, with must specify requests.cpu.

Why: once a quota constrains a compute resource, every Pod must declare that request/limit. Existing manifests that omitted resources now violate the rule. Fix: add a LimitRange with defaultRequest/default so omitted values are auto-filled — or update every manifest to set resources explicitly. The LimitRange is what makes a compute quota painless to roll out.

A worked example of the interaction

Given the team-a-limits LimitRange and team-a-compute quota above, suppose you create this Pod:

apiVersion: v1
kind: Pod
metadata: { name: demo, namespace: team-a }
spec:
  containers:
    - name: app
      image: nginx:1.27
      # NOTE: no resources block at all

Walk the sequence:

  1. LimitRange mutates: container app gets requests: {cpu: 100m, memory: 128Mi} and limits: {cpu: 500m, memory: 512Mi} from the defaults.
  2. LimitRange validates: 100m/500m CPU and 128Mi/512Mi memory are within min/max and the 4× ratio. Pass.
  3. ResourceQuota validates: if current Used for requests.cpu is 1500m and Hard is 4 (=4000m), then 1500m + 100m = 1600m ≤ 4000m. Pass. Same check for memory and limits.

The Pod is admitted with sensible numbers the author never wrote. Now imagine Used requests.cpu were already 3950m: 3950m + 100m = 4050m > 4000mrejected with exceeded quota. And if the author had written cpu: limit 5 on the container, step 2 would reject it with maximum cpu usage per Container is 2 before the quota ever ran.

Hands-on lab

You will build a complete tenancy: a namespace, a LimitRange, and a ResourceQuota, then prove each behaviour — defaulting, min/max rejection, quota exhaustion, and object-count caps. Everything runs on your free local cluster.

Step 0 — Confirm a cluster

kubectl get nodes

No cluster? kind create cluster (or minikube start) — both free, both local.

Step 1 — Create the namespace and switch to it

kubectl create namespace lab-tenant
kubectl config set-context --current --namespace=lab-tenant
kubectl config view --minify | grep namespace:

Expected: namespace: lab-tenant. Bare commands now act on this namespace.

Step 2 — Apply a LimitRange

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: LimitRange
metadata:
  name: lab-limits
spec:
  limits:
    - type: Container
      default:        { cpu: "300m", memory: 256Mi }
      defaultRequest: { cpu: "100m", memory: 128Mi }
      min:            { cpu: "50m",  memory: 64Mi }
      max:            { cpu: "1",    memory: 1Gi }
EOF

kubectl describe limitrange lab-limits

Step 3 — Prove defaulting works

Create a Pod with no resources and confirm the LimitRange filled them in:

kubectl run defaulted --image=nginx:1.27 --restart=Never
kubectl get pod defaulted -o jsonpath='{.spec.containers[0].resources}'; echo

Expected (defaults injected by the LimitRange):

{"limits":{"cpu":"300m","memory":"256Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}

You wrote no resources, yet the Pod has them — that is the LimitRange mutating admission step.

Step 4 — Prove min/max validation rejects bad Pods

Try to exceed the max of 1 CPU:

kubectl run toobig --image=nginx:1.27 --restart=Never \
  --overrides='{"spec":{"containers":[{"name":"toobig","image":"nginx:1.27","resources":{"requests":{"cpu":"2"}}}]}}'

Expected — rejected before scheduling:

Error from server (Forbidden): pods "toobig" is forbidden:
maximum cpu usage per Container is 1, but request is 2

Step 5 — Apply a ResourceQuota

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ResourceQuota
metadata:
  name: lab-quota
spec:
  hard:
    requests.cpu: "500m"     # tiny on purpose, to hit the cap fast
    requests.memory: 512Mi
    pods: "3"
    count/deployments.apps: "2"
EOF

kubectl describe resourcequota lab-quota

Note Used already reflects the defaulted Pod from Step 3 (it counts existing Pods).

Step 6 — Prove the quota caps the namespace total

Each default Pod requests 100m CPU. With requests.cpu capped at 500m, you can fit five Pods’ requests — but pods: "3" caps the count first. Create Pods until one is rejected:

kubectl run a --image=nginx:1.27 --restart=Never
kubectl run b --image=nginx:1.27 --restart=Never   # now 3 pods incl. 'defaulted'
kubectl run c --image=nginx:1.27 --restart=Never   # should fail

Expected on the last one:

Error from server (Forbidden): pods "c" is forbidden:
exceeded quota: lab-quota, requested: pods=1, used: pods=3, limited: pods=3

Step 7 — Prove the “quota requires requests” rule

Delete the LimitRange, then try a Pod with no resources while the compute quota is still in force:

kubectl delete limitrange lab-limits
kubectl run noreq --image=nginx:1.27 --restart=Never

Expected — without the LimitRange to supply defaults, the quota now rejects it:

Error from server (Forbidden): pods "noreq" is forbidden:
failed quota: lab-quota: must specify requests.cpu,requests.memory

This is the partnership made visible: the quota requires requests; the LimitRange supplies them.

Step 8 — Inspect the final state

kubectl describe resourcequota lab-quota

Cleanup

kubectl config set-context --current --namespace=default
kubectl delete namespace lab-tenant

Cost note. Everything ran on a local kind/minikube cluster — zero cloud cost. Deleting the namespace cascades and removes every object you created.

Common mistakes & troubleshooting

Symptom Likely cause Fix
Pods suddenly fail with must specify requests.cpu after adding a quota A compute ResourceQuota now requires requests/limits on every Pod Add a LimitRange with defaultRequest/default, or set resources in every manifest
exceeded quota: ... used: ... limited: ... The namespace total would exceed Hard Raise the quota, delete unused objects, or right-size requests/limits
maximum cpu usage per Container is X Container’s request/limit exceeds the LimitRange max Lower the container’s value or raise the LimitRange max
minimum memory usage per Container is X Value below LimitRange min (or a default below min) Raise the value, or fix the defaultRequest so it’s ≥ min
LimitRange exists but Pods still have no resources LimitRange created after the Pods, or wrong namespace Defaults apply only at creation; recreate Pods; check -n
Per-StorageClass storage quota never triggers Quota key name ≠ actual StorageClass, or PVC omits storageClassName Match the exact class name; set storageClassName on the PVC
Namespace stuck Terminating A finalizer (often a removed CRD/API service) blocks cleanup Restore/remove the offending API service; inspect -o yaml
Deployment shows FailedCreate but no Pods appear The ReplicaSet’s Pods are rejected by quota/LimitRange kubectl describe replicaset <rs> — the admission error is in its events

Key debugging move: when a Deployment won’t produce Pods, the error is not on the Deployment — it’s in the ReplicaSet’s events (or the controller-manager logs). Always kubectl describe rs / kubectl get events to surface admission rejections.

Best practices

Security notes

Interview & exam questions

  1. Is a Namespace a security boundary? No. It is a logical grouping and a policy attachment point. Pods across namespaces share nodes, the kernel, and a flat network by default. Real isolation needs NetworkPolicy, PodSecurity, RBAC, and sometimes separate node pools/sandboxed runtimes.

  2. Name three things a Namespace does not isolate. Nodes (Pods from any namespace can land on the same node), network traffic (flat by default), and cluster-scoped objects (Nodes, PVs, StorageClasses, ClusterRoles live outside any namespace).

  3. What is the difference between a ResourceQuota and a LimitRange? A ResourceQuota caps the aggregate consumption/object-count of a whole namespace; a LimitRange sets per-object min/max/defaults for each Container/Pod/PVC. They are partners: the LimitRange fills defaults so the quota can count them.

  4. You add a ResourceQuota on requests.cpu and Pods stop being created with must specify requests.cpu. Why, and how do you fix it? Once a compute resource is under quota, every Pod must declare that request/limit. Existing manifests that omit it now fail. Fix by adding a LimitRange with defaultRequest/default, or by setting resources explicitly in every manifest.

  5. In what order do the LimitRange and ResourceQuota admission controllers run? LimitRange mutating first (inject defaults), then LimitRange validating (min/max/ratio), then ResourceQuota validating (aggregate against Hard). Mutating always precedes validating in admission.

  6. A container sets a CPU limit but no request. What happens under a LimitRange? The request is set equal to the limit (a limit implies a request), not to defaultRequest. It is then validated against min/max/ratio.

  7. What does maxLimitRequestRatio: 1 achieve? It forces limit == request for that resource, which yields Guaranteed QoS for the container.

  8. How do you cap the number of cloud load balancers a namespace can create? A ResourceQuota with services.loadbalancers: "N" (it counts Services of type LoadBalancer specifically).

  9. What is the difference between the BestEffort and NotBestEffort quota scopes? BestEffort matches Pods with no requests/limits (it may only constrain the pods count); NotBestEffort matches Pods with requests/limits set (it may constrain compute resources).

  10. PV or PVC — which is namespaced? The PVC is namespaced (and counts against storage quotas); the PV is cluster-scoped. A namespaced PVC binds to a cluster-scoped PV.

  11. A Deployment shows FailedCreate but no Pods exist. Where is the real error? In the ReplicaSet’s events (the controller creating the Pods), and/or controller-manager logs — not on the Deployment. The admission rejection (quota/LimitRange) surfaces there.

  12. How do you stop typing -n team-a on every command? Set the namespace on the current context: kubectl config set-context --current --namespace=team-a.

Quick check

  1. Which object caps the total CPU a namespace may request: ResourceQuota or LimitRange?
  2. True or false: deleting a Namespace deletes every object inside it.
  3. If a container sets a limit but no request under a LimitRange, what becomes its request?
  4. Name the quota key that limits the number of Services of type LoadBalancer.
  5. Which admission step runs first: ResourceQuota validation or LimitRange defaulting?

Answers

  1. ResourceQuota (aggregate). The LimitRange governs per-object min/max/defaults.
  2. True — namespace deletion cascades to all contained objects.
  3. Its request is set equal to the limit (a limit implies a request).
  4. services.loadbalancers.
  5. LimitRange defaulting (mutating) runs first; ResourceQuota validation runs after defaults are in place.

Exercise

Build a self-service tenant template for a team called payments running in dev:

  1. Write a Namespace manifest labelled team: payments and environment: dev, with pod-security.kubernetes.io/enforce: baseline.
  2. Write a LimitRange that defaults containers to request 100m/128Mi, limit 500m/512Mi, with min 50m/64Mi, max 1/1Gi, and a maxLimitRequestRatio of 4 on CPU and memory. Add a PersistentVolumeClaim item capping storage at min 1Gi, max 20Gi.
  3. Write a ResourceQuota capping requests.cpu: 4, requests.memory: 8Gi, limits.cpu: 8, limits.memory: 16Gi, pods: 30, services.loadbalancers: 1, count/deployments.apps: 10, and requests.storage: 50Gi.
  4. Apply all three, then deploy an app with no resources block and confirm via kubectl get pod ... -o jsonpath that the defaults were injected.
  5. Scale the Deployment until you hit a quota limit, read the exact rejection message, and explain which dimension was exceeded.

Stretch: add a second, PriorityClass-scoped ResourceQuota that gives only priorityClassName: high Pods an extra requests.cpu: 4, and observe how setting a priority class changes admission.

Certification mapping

Exam Relevance
CKAD Heavily tested — creating namespaces, applying ResourceQuotas and LimitRanges, and understanding why Pods are rejected are core “Application Design and Build” / “Services & Networking” tasks. Expect to set defaults and read describe quota output under time pressure.
CKA Cluster administration includes multi-tenancy basics: namespaces, quotas, and limit ranges as cluster governance, plus the namespaced-vs-cluster-scoped distinction.
CKS Touches this via multi-tenancy isolation, the “namespace is not a security boundary” principle, and quotas as a resource-exhaustion control (paired with NetworkPolicy and PodSecurity).

Glossary

Next steps

KubernetesNamespacesResourceQuotaLimitRangemulti-tenancykubectl
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading