Resource Management
Without resource limits, a single runaway Pod can consume all CPU and memory on a node, starving other workloads. Resource management in Kubernetes ensures fair sharing of cluster resources and prevents noisy neighbors from causing outages.
Requests vs. Limits
Every container can specify two resource parameters:
| Parameter | Description | Impact |
|---|---|---|
| Requests | Minimum guaranteed resources | Used by the Scheduler to decide which node can run the Pod |
| Limits | Maximum allowed resources | Enforced by the kubelet. Container is throttled (CPU) or killed (memory) if exceeded. |
resources:
requests:
cpu: "250m" # Guaranteed 0.25 CPU cores
memory: "128Mi" # Guaranteed 128 MiB memory
limits:
cpu: "500m" # Max 0.5 CPU cores
memory: "256Mi" # Max 256 MiB memory
CPU Resources
CPU is measured in cores or millicores:
| Value | Meaning |
|---|---|
1 |
1 full CPU core |
0.5 or 500m |
Half a CPU core |
0.25 or 250m |
Quarter of a CPU core |
100m |
0.1 CPU cores |
CPU is compressible — when a container hits its CPU limit, it’s throttled (slowed down), not killed.
Memory Resources
Memory is measured in bytes with standard suffixes:
| Value | Meaning |
|---|---|
128Mi |
128 mebibytes (134,217,728 bytes) |
256Mi |
256 mebibytes |
1Gi |
1 gibibyte |
128M |
128 megabytes (128,000,000 bytes) |
Memory is incompressible — when a container exceeds its memory limit, it’s OOMKilled (Out of Memory Killed) and restarted.
Tip: Use Mi and Gi (binary units) consistently. Avoid mixing M (decimal) and Mi (binary).
Setting Resources in a Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: nginx:1.27
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "250m"
memory: "256Mi"
- name: sidecar
image: busybox
command: ["sh", "-c", "sleep infinity"]
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "100m"
memory: "128Mi"
Total Pod resources = sum of all containers:
- Requests: 150m CPU, 192Mi memory
- Limits: 350m CPU, 384Mi memory
QoS Classes
Kubernetes assigns a Quality of Service class to each Pod based on its resource settings. This determines eviction priority when a node is under resource pressure.
| QoS Class | Condition | Eviction Priority |
|---|---|---|
| Guaranteed | All containers have requests = limits for both CPU and memory | Last to be evicted (highest priority) |
| Burstable | At least one container has a request or limit set | Evicted after BestEffort |
| BestEffort | No resources set on any container | First to be evicted (lowest priority) |
Guaranteed
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m" # Same as request
memory: "256Mi" # Same as request
Burstable
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m" # Different from request
memory: "256Mi" # Different from request
BestEffort
# No resources specified at all
containers:
- name: app
image: nginx
# No resources block
# Check a Pod's QoS class
kubectl get pod <pod-name> -o jsonpath='{.status.qosClass}'
What Happens When Limits Are Exceeded
CPU Limit Exceeded
- Container is throttled — limited to the specified CPU
- No crash, no restart, just slower performance
- Monitor with
kubectl top pods
Memory Limit Exceeded
- Container is OOMKilled — terminated immediately
- Kubernetes restarts it based on
restartPolicy - Status shows:
OOMKilled
# Demonstrate OOMKill
kubectl run mem-hog --image=polinux/stress --restart=Never \
--limits='memory=128Mi' -- stress --vm 1 --vm-bytes 256M
# Watch it get killed
kubectl get pod mem-hog -w
# STATUS: OOMKilled
LimitRange (Namespace Defaults)
Set default resource constraints for all Pods in a namespace:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: development
spec:
limits:
- type: Container
default: # Default limits if not specified
cpu: "500m"
memory: "256Mi"
defaultRequest: # Default requests if not specified
cpu: "100m"
memory: "128Mi"
max: # Maximum allowed
cpu: "2"
memory: "2Gi"
min: # Minimum required
cpu: "50m"
memory: "64Mi"
- type: Pod
max:
cpu: "4"
memory: "4Gi"
Now any Pod in the development namespace without explicit resources gets the defaults.
ResourceQuota (Namespace Totals)
Limit the total resources a namespace can consume:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: development
spec:
hard:
requests.cpu: "4" # Total CPU requests
requests.memory: "8Gi" # Total memory requests
limits.cpu: "8" # Total CPU limits
limits.memory: "16Gi" # Total memory limits
pods: "20" # Max Pods
services: "10"
persistentvolumeclaims: "5"
configmaps: "20"
secrets: "20"
kubectl describe resourcequota team-quota -n development
Output:
Name: team-quota
Namespace: development
Resource Used Hard
-------- ---- ----
limits.cpu 750m 8
limits.memory 768Mi 16Gi
pods 3 20
requests.cpu 300m 4
requests.memory 384Mi 8Gi
Monitoring Resource Usage
# Node resource usage
kubectl top nodes
# Pod resource usage
kubectl top pods
# Pod usage in a specific namespace
kubectl top pods -n development
# Sort by CPU usage
kubectl top pods --sort-by=cpu
# Sort by memory usage
kubectl top pods --sort-by=memory
Note: kubectl top requires the Metrics Server to be installed. In Minikube: minikube addons enable metrics-server.
Resource Sizing Guidelines
| Workload Type | CPU Request | CPU Limit | Memory Request | Memory Limit |
|---|---|---|---|---|
| Web server | 100m–250m | 500m–1000m | 128Mi–256Mi | 256Mi–512Mi |
| API service | 250m–500m | 1000m–2000m | 256Mi–512Mi | 512Mi–1Gi |
| Database | 500m–2000m | 2000m–4000m | 512Mi–2Gi | 2Gi–8Gi |
| Worker/Batch | 100m–500m | 1000m–2000m | 128Mi–512Mi | 512Mi–2Gi |
Rule of thumb:
- Start with conservative requests, generous limits
- Monitor actual usage with
kubectl topand adjust - Set limits 2-4x of requests for burstable workloads
- Keep requests = limits for critical services (Guaranteed QoS)
Summary
- Requests = guaranteed minimum; used for scheduling
- Limits = enforced maximum; throttle (CPU) or kill (memory)
- QoS Classes: Guaranteed > Burstable > BestEffort (eviction order)
- LimitRange sets namespace defaults
- ResourceQuota caps total namespace consumption
- Always set resources in production — avoid BestEffort QoS
- Use
kubectl topto monitor actual usage
What’s Next?
Now let’s learn about Health Checks — using liveness, readiness, and startup probes to ensure Kubernetes only routes traffic to healthy Pods and automatically restarts unhealthy ones.