Ready to get your hands dirty with Kubernetes resource management? This practical guide will walk you through simple exercises to understand how requests and limits work in real scenarios. No complex theory—just practical examples you can run right now.
Exercise 1: Creating Your First Pod with Resources
Let's start simple. We'll create a pod that requests specific CPU and memory resources.
Step 1: Create a Basic Pod
Create a file called simple-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: simple-app
spec:
containers:
- name: nginx
image: nginx:latest
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- requests.memory: "64Mi" - Kubernetes will find a node with at least 64MB free
- requests.cpu: "250m" - 250 millicores = 0.25 CPU cores
- limits.memory: "128Mi" - Pod will be killed if it uses more than 128MB
- limits.cpu: "500m" - Pod will be throttled if it tries to use more than 0.5 cores
Step 2: Deploy and Observe
# Apply the configuration
kubectl apply -f simple-pod.yaml
# Check if the pod is running
kubectl get pods
# View detailed information
kubectl describe pod simple-app
Look for the QoS Class in the output. It should say Burstable because
requests ≠ limits.
Exercise 2: Understanding QoS Classes
Let's create three pods with different QoS classes to see how they behave.
Pod 1: Guaranteed QoS
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: app
image: nginx:latest
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "100Mi" # Same as requests
cpu: "100m" # Same as requests
Pod 2: Burstable QoS
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: app
image: nginx:latest
resources:
requests:
memory: "50Mi"
cpu: "50m"
limits:
memory: "200Mi" # Higher than requests
cpu: "200m" # Higher than requests
Pod 3: BestEffort QoS
apiVersion: v1
kind: Pod
metadata:
name: besteffort-pod
spec:
containers:
- name: app
image: nginx:latest
# No resources defined at all!
Deploy and Compare
# Deploy all three pods
kubectl apply -f guaranteed-pod.yaml
kubectl apply -f burstable-pod.yaml
kubectl apply -f besteffort-pod.yaml
# Check their QoS classes
kubectl describe pod guaranteed-pod | grep "QoS Class"
kubectl describe pod burstable-pod | grep "QoS Class"
kubectl describe pod besteffort-pod | grep "QoS Class"
- guaranteed-pod → QoS Class: Guaranteed
- burstable-pod → QoS Class: Burstable
- besteffort-pod → QoS Class: BestEffort
Exercise 3: Simulating Memory Pressure
Now let's see what happens when a pod tries to use more memory than its limit.
Create a Memory-Hungry Pod
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
containers:
- name: memory-eater
image: polinux/stress
resources:
requests:
memory: "50Mi"
limits:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
# Deploy the pod
kubectl apply -f memory-demo.yaml
# Watch what happens (it will get OOMKilled)
kubectl get pods -w
# Check the reason for termination
kubectl describe pod memory-demo
You should see Reason: OOMKilled in the output. The pod exceeded its memory limit and
was terminated by the kernel!
Exercise 4: CPU Throttling in Action
Unlike memory, CPU is a compressible resource. Let's see throttling in action.
apiVersion: v1
kind: Pod
metadata:
name: cpu-demo
spec:
containers:
- name: cpu-burner
image: polinux/stress
resources:
requests:
cpu: "100m"
limits:
cpu: "200m"
command: ["stress"]
args: ["--cpu", "2"]
# Deploy the pod
kubectl apply -f cpu-demo.yaml
# Monitor CPU usage
kubectl top pod cpu-demo
# Check for throttling (if metrics-server is installed)
kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods/cpu-demo
The pod will try to use 2 full CPU cores, but Kubernetes will throttle it to 200m (0.2 cores).
Exercise 5: Resource Quotas for Namespaces
Let's create a namespace with resource limits to prevent resource hogging.
apiVersion: v1
kind: Namespace
metadata:
name: limited-namespace
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: limited-namespace
spec:
hard:
requests.cpu: "1"
requests.memory: "1Gi"
limits.cpu: "2"
limits.memory: "2Gi"
pods: "10"
# Create the namespace and quota
kubectl apply -f resource-quota.yaml
# Try to create a pod that exceeds the quota
kubectl run big-pod --image=nginx \
--namespace=limited-namespace \
--requests='cpu=2,memory=2Gi' \
--limits='cpu=2,memory=2Gi'
# This should fail! Check why:
kubectl describe resourcequota compute-quota -n limited-namespace
Key Takeaways
Always Set Requests
Requests help the scheduler find the right node. Without them, your pod might land on an overloaded node.
Use Limits Wisely
Memory limits prevent OOM situations. CPU limits prevent one pod from starving others.
Understand QoS Classes
Guaranteed pods are protected during eviction. BestEffort pods are sacrificed first.
Monitor Your Resources
Use kubectl top and metrics-server to track actual usage vs. limits.
Clean Up
Don't forget to clean up your test resources:
# Delete all test pods
kubectl delete pod simple-app guaranteed-pod burstable-pod besteffort-pod memory-demo cpu-demo
# Delete the test namespace
kubectl delete namespace limited-namespace
Next Steps
Now that you've practiced the basics, you're ready to dive deeper! Check out my other post on Kubernetes Internals to understand what's happening under the hood with the Linux kernel, OOM killer, and CFS scheduler.