Search across all documentation pages
ResourceQuota caps the aggregate resources and object counts a namespace may consume; a LimitRange sets per-container defaults, minimums, and maximums.requests vs limits, scopeSelector for priority-aware quotas, default injection, and the admission-time enforcement point.Attach a ResourceQuota to each tenant namespace to cap total CPU, memory, storage, and object counts.
Attach a LimitRange to the same namespace so pods that omit requests/limits still get sensible values.
Confirm every pod now carries requests and limits, because once a compute quota exists the two become mandatory.
Watch usage and tune the numbers against real consumption over time.
Cap the namespace total and bound object counts.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-compute
namespace: team-a
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
persistentvolumeclaims: "10"
requests.storage: 100Gi
pods: "60"
services.loadbalancers: "2"Supply per-container defaults and guardrails.
apiVersion: v1
kind: LimitRange
metadata:
name: team-a-limits
namespace: team-a
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "2"
memory: 4Gi
min:
cpu: 50m
memory: 64MiApply and inspect current consumption.
kubectl apply -f quota.yaml -f limitrange.yaml -n team-a
kubectl describe resourcequota team-a-compute -n team-a
kubectl describe limitrange team-a-limits -n team-aThe describe output shows Used versus Hard, which is your live budget check.
requests are what the scheduler reserves and what quota counts for requests.cpu/requests.memory.
limits are the ceiling the kernel enforces at runtime and what limits.cpu/limits.memory count.
A namespace can hit its request quota (nothing more will schedule) long before pods hit their runtime limits.
Once a quota constrains a compute resource, the API server rejects any pod that does not declare that resource.
That is the reason LimitRange matters: its default and defaultRequest inject the missing values at admission so teams are not forced to annotate every container.
Without a LimitRange, a quota turns into a wall of must specify limits.cpu rejections.
Quota is not only about CPU and memory.
Capping pods, persistentvolumeclaims, services.loadbalancers, and count/<resource> protects shared and billable infrastructure - each LoadBalancer Service may provision a real cloud load balancer.
Bounding object counts also limits etcd growth and API churn from a runaway controller.
scopeSelector lets you quota by PriorityClass, so a tenant's high-priority budget is separate from its best-effort budget.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-high-priority
namespace: team-a
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values: ["high"]This prevents a tenant from claiming all high-priority capacity while still allowing generous best-effort use.
max blocks a single container from requesting more than a per-container ceiling, so one pod cannot swallow the whole namespace budget.
min prevents unrealistically tiny requests that would let too many pods pack onto a node and cause contention.
Together they keep individual pods within a sane band.
Quota without LimitRange breaks deploys. Teams suddenly cannot create pods because every container needs explicit requests/limits. Always pair them.
Limits far above requests overcommit nodes. A 100m request with a 4-core limit lets many pods schedule then fight for CPU. Keep the ratio disciplined.
Editing quota does not touch running pods. Lowering a quota below current usage only blocks new pods; existing ones keep running until they restart. Plan reductions accordingly.
LimitRange only affects new pods. Applying it does not retrofit running workloads. Roll deployments to pick up new defaults.
Forgetting storage and object counts. A tenant with no PVC or LoadBalancer cap can still run up cost and etcd pressure. Quota the countable resources too.
Memory limit equals OOMKill. A container that exceeds its memory limit is killed by the kernel, not throttled. Size memory limits from real usage, not guesses.
LimitRange only, no quota. Shapes individual pods but leaves the namespace total unbounded. Fine for a trusted single-team cluster, unsafe for real multi-tenancy.
Quota only, no LimitRange. Bounds the total but forces every team to hand-set requests/limits. Workable if a policy engine enforces those values instead.
Policy engine (Kyverno / Gatekeeper). Mutating or validating policies can inject and require requests/limits with more logic than LimitRange, and enforce ratios. Use when you need rules LimitRange cannot express.
Cluster autoscaling (Karpenter). Adds nodes when demand grows rather than capping tenants. Complementary, not a substitute - you still want quota so one tenant cannot scale the bill without bound.
Do I need both quota and LimitRange? Almost always. Quota bounds the tenant total and makes requests/limits mandatory; LimitRange supplies the defaults that keep pods admissible.
Why can't my team create pods after I added a quota? A compute quota requires every container to declare that resource. Add a LimitRange with defaults or set requests/limits explicitly.
Does lowering a quota evict pods? No. It only blocks new pods and updates that would exceed the new limit. Running pods continue until they restart.
Can I quota GPUs or custom resources? Yes, via requests.<resource> and count/<resource>.<group>, so extended resources and CRDs can be bounded too.
How do I quota by priority? Use a scopeSelector matching PriorityClass values, giving each tier its own budget within the namespace.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026