Skip to main content
RunBook Academy

KubernetesXIV · Namespace ArchitectureTenancy and isolation

ResourceQuota per namespace — capping total resource consumption

Advanced⏱ ~16 minkubectl

What you'll learn

  • Configure ResourceQuota to cap total resource consumption per namespace
  • Distinguish ResourceQuota from LimitRange (cap vs defaults)
  • Reason about quota enforcement and the error a quota-exceeded Pod receives
  • Apply quota sizing patterns for production

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

ResourceQuota is the namespace-level cap that prevents a team from consuming the entire cluster. This lesson covers quota configuration, the relationship with LimitRange, and the production discipline around quota sizing.

ResourceQuota anatomy

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-prod
  namespace: team-a-prod
spec:
  hard:
    requests.cpu: "32"
    requests.memory: 64Gi
    limits.cpu: "64"
    limits.memory: 128Gi
    requests.ephemeral-storage: 100Gi
    limits.ephemeral-storage: 200Gi
    persistentvolumeclaims: "20"
    requests.storage: 500Gi
    services: "100"
    secrets: "50"
    configmaps: "100"
    pods: "200"

The hard field is a set of limits. The total in the namespace cannot exceed any of these.

The fields:

  • requests.cpu / requests.memory: total requests summed across all Pods.
  • limits.cpu / limits.memory: total limits summed across all Pods.
  • persistentvolumeclaims: max number of PVCs.
  • requests.storage: total storage requested by PVCs.
  • services, secrets, configmaps, pods: max object counts.

Quota enforcement

The quota admission plugin runs when:

  • A Pod is created: the Pod’s requests and limits are added to the running totals; if the new totals exceed hard, the Pod is rejected.
  • A PVC is created: the storage request is added; if it exceeds hard, the PVC is rejected.
  • A ConfigMap/Secret/Service is created: the count is incremented; if it exceeds hard, the object is rejected.

The error message:

Error from server (Forbidden): error when creating "pod.yaml":
exceeded quota: team-a-prod,
requested: requests.cpu=1,requests.memory=1Gi,
used: requests.cpu=31,requests.memory=60Gi,
limited: requests.cpu=32,requests.memory=64Gi

The error shows the requested, used, and limited values. Operators can see exactly what quota was hit.

ResourceQuota vs LimitRange

The two work together:

LimitRangeResourceQuota
ScopePer-containerPer-namespace
DefaultsYes (default, defaultRequest)No
ConstraintsYes (min, max, maxLimitRequestRatio)No
TotalsNoYes (sum of all Pods)
flowchart TD
    Pod[Pod created] --> LR[LimitRange: applies defaults + per-container constraints]
    LR --> RQ[ResourceQuota: checks namespace total]
    RQ --> Allow{Passes?}
    Allow -- yes --> Create[Pod created]
    Allow -- no --> Reject[Pod rejected]

LimitRange ensures every Pod has resources (no BestEffort). ResourceQuota caps the total. Together: every Pod counts, and the total is bounded.

Production patterns

Team quota:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-prod
  namespace: team-a-prod
spec:
  hard:
    requests.cpu: "32"
    requests.memory: 64Gi
    limits.cpu: "64"
    limits.memory: 128Gi
    persistentvolumeclaims: "20"
    requests.storage: 500Gi
    pods: "200"

This team gets 32 CPU / 64Gi requests and 64 CPU / 128Gi limits. They can have up to 200 Pods, 20 PVCs, and 500Gi of storage.

Environment-based quotas:

# Production: tighter limits
apiVersion: v1
kind: ResourceQuota
metadata:
  name: prod
  namespace: prod
spec:
  hard:
    requests.cpu: "100"
    requests.memory: 200Gi
    pods: "500"

---
# Development: more generous
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev
  namespace: dev
spec:
  hard:
    requests.cpu: "200"
    requests.memory: 400Gi
    pods: "1000"

Production has tighter limits (predictable workloads); development has more (experimentation).

Quota and BestEffort Pods

A BestEffort Pod (no resources) does not count against requests.cpu or requests.memory. But:

  • It does count against pods quota (every Pod counts).
  • It does count against services, configmaps, etc.

Production discipline: pair ResourceQuota with LimitRange to prevent BestEffort Pods. The pods quota is not enough.

Quota and PVCs

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-prod
  namespace: team-a-prod
spec:
  hard:
    persistentvolumeclaims: "20"
    requests.storage: 500Gi

Two fields:

  • persistentvolumeclaims: max number of PVCs in the namespace.
  • requests.storage: total storage requested by PVCs.

A PVC with requests.storage: 100Gi counts 100Gi against the quota. If the namespace already has 450Gi in use, a new 50Gi PVC succeeds; a new 100Gi PVC would be rejected.

Monitoring quota usage

kubectl describe resourcequota team-a-prod -n team-a-prod

Output:

Name:            team-a-prod
Namespace:       team-a-prod
Resource         Used    Hard
--------         ----    ----
requests.cpu     24      32
requests.memory  48Gi    64Gi
limits.cpu       48      64
limits.memory    96Gi    128Gi
pods             84      200

The Used column shows current usage; Hard shows the quota. Production discipline: monitor quota usage; alert when usage > 80%.

# Quota usage ratio
kube_resourcequota{resource="requests.cpu", type="used"}
/ kube_resourcequota{resource="requests.cpu", type="hard"}
> 0.8

Alert when quota usage exceeds 80% of the hard limit. The team needs a quota increase.

Production discipline

Size quotas based on team needs:

  • Small team / dev: 8 CPU, 16Gi memory, 50 Pods.
  • Medium team / staging: 32 CPU, 64Gi memory, 200 Pods.
  • Large team / prod: 100 CPU, 200Gi memory, 500 Pods.

Size based on actual usage; raise as teams grow.

Pair with LimitRange:

# LimitRange: defaults
apiVersion: v1
kind: LimitRange
metadata:
  name: default
  namespace: team-a-prod
spec:
  limits:
  - type: Container
    default: {cpu: 500m, memory: 512Mi}
    defaultRequest: {cpu: 100m, memory: 128Mi}

# ResourceQuota: namespace total
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-prod
  namespace: team-a-prod
spec:
  hard:
    requests.cpu: "32"
    requests.memory: 64Gi

LimitRange ensures every Pod has resources; ResourceQuota caps the total. Together, a complete resource policy.

  • Monitor quota usage. Alert when usage > 80%; review with the team before hitting the limit.
  • Use StorageClass-specific quotas to control expensive storage usage.
  • Document the quota increase process. Teams need a clear path to get more capacity when they need it.

Cross-course references

  • The Linux course part XXXVII-Linux-Resources covers cgroup resource management; ResourceQuota is the cluster-level equivalent for namespaces.
  • The Ansible course part XLIX-Ansible-Compliance covers policy compliance; ResourceQuota is the cluster-level equivalent.
  • The Observability course part LXXXIV-Kubernetes-CapacityPlanning covers capacity planning; quotas are part of the plan.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the difference between ResourceQuota and LimitRange?

  2. Q2. A BestEffort Pod (no resources) bypasses ResourceQuota because it has no requests to count.

  3. Q3. A team's Deployment of 50 replicas hits the namespace's `pods: 100` quota. The team cannot deploy until quota is raised. Walk through the diagnosis and the fix.

    Team `team-a-prod` has ResourceQuota with `pods: 100`. The team currently has 95 Pods running. The team wants to deploy a new Deployment with 10 replicas. Total would be 105 Pods; quota is 100. The Deployment fails.

  4. Q4. How do you size a ResourceQuota for a new team namespace?

Passing score: 75%. Answers are checked in this browser.