The Nautilus DevOps team has noticed performance issues in some Kubernetes-hosted applications due to resource constraints. To address this, they plan to set limits on resource utilization. Here are the details:
Create a pod named httpd-pod with a container named httpd-container. Use the httpd image with the latest tag (specify as httpd:latest). Configure the following container-level resource requests and limits for the container:
Requests: Memory: 15Mi, CPU: 100m
Limits: Memory: 20Mi, CPU: 100m
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
Mastering Kubernetes Resource Management: A Complete Guide to Setting Pod Limits
Introduction
Resource management is one of the most critical aspects of running applications in Kubernetes. Without proper resource limits, a single misbehaving application can consume all available cluster resources, causing performance degradation or complete failure of other applications. This is exactly the challenge the Nautilus DevOps team faced, and in this comprehensive guide, we'll explore how to solve it.
In this blog post, we'll walk through a real-world scenario of setting resource limits in Kubernetes pods, understanding the theory behind resource requests and limits, and learning best practices for production environments.
The Problem: Why Resource Limits Matter
Imagine this scenario: You're running multiple microservices in your Kubernetes cluster. One of them, let's say a logging agent, has a memory leak. Without resource limits, it will continue consuming memory until the node runs out of RAM, causing the entire node to fail and taking down all other applications running on it.
This is exactly the kind of problem the Nautilus DevOps team was facing. Their applications were experiencing performance issues due to resource constraints, and they needed a solution to prevent any single application from overwhelming the cluster.
Real-World Impact
| Issue | Without Limits | With Limits |
|---|---|---|
| Memory Leak | Node crashes, all pods affected | Only the leaking pod is OOM-killed |
| CPU Spike | Other applications starve | Pod is throttled, others remain unaffected |
| Resource Exhaustion | Cluster becomes unresponsive | Resources are fairly distributed |
| Cost | Over-provisioning leads to waste | Efficient resource utilization |
Understanding Resource Requests and Limits
Before diving into the implementation, let's understand the core concepts:
What are Resource Requests?
Requests are the minimum amount of resources a container needs to run. Kubernetes uses these for scheduling decisions.
resources:
requests:
memory: "15Mi" # Minimum memory guaranteed
cpu: "100m" # Minimum CPU guaranteed (0.1 CPU core)
What are Resource Limits?
Limits are the maximum amount of resources a container can use. If a container exceeds these limits:
- Memory: The container will be terminated (OOMKilled)
- CPU: The container will be throttled (slowed down)
resources:
limits:
memory: "20Mi" # Maximum memory allowed
cpu: "100m" # Maximum CPU allowed (0.1 CPU core)
Resource Units Explained
CPU Units:
-
1= 1 CPU core -
100m= 100 millicores (0.1 CPU core) -
500m= 500 millicores (0.5 CPU core) -
1000m= 1 CPU core
Memory Units:
-
Ki= Kibibyte (1024 bytes) -
Mi= Mebibyte (1024 KiB) -
Gi= Gibibyte (1024 Mi) -
Ti= Tebibyte (1024 Gi)
Why Both Requests and Limits?
┌─────────────────────────────────────────┐
│ Node Resources │
│ ┌─────────────────────────────────────┐ │
│ │ Total Available │ │
│ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │
│ │ │ App 1 │ │ App 2 │ │ App 3 │ │ │
│ │ │ R:15Mi│ │ R:10Mi│ │ R:20Mi│ │ │
│ │ │ L:20Mi│ │ L:15Mi│ │ L:25Mi│ │ │
│ │ └───────┘ └───────┘ └───────┘ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
- Requests guarantee resources for scheduling
- Limits prevent resource abuse
- Together they ensure fair resource distribution
Step-by-Step Implementation
Prerequisites
- Kubernetes cluster
-
kubectlconfigured on your jump-host - Basic understanding of Kubernetes concepts
Step 1: Generate the Pod YAML
First, let's generate a base YAML manifest:
kubectl run httpd-pod \
--image=httpd:latest \
--restart=Never \
--dry-run=client -o yaml > httpd-pod.yaml
Step 2: Edit the YAML to Add Resource Limits
Open the file and add the resource specifications:
nano httpd-pod.yaml
Before:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: httpd-pod
name: httpd-pod
spec:
containers:
- image: httpd:latest
name: httpd-pod
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Never
status: {}
After:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: httpd-pod
name: httpd-pod
spec:
containers:
- image: httpd:latest
name: httpd-container
resources:
requests:
memory: "15Mi"
cpu: "100m"
limits:
memory: "20Mi"
cpu: "100m"
dnsPolicy: ClusterFirst
restartPolicy: Never
status: {}
Step 3: Apply the Configuration
Create the pod with resource limits:
kubectl apply -f httpd-pod.yaml
Expected output:
pod/httpd-pod created
Step 4: Quick Verification
Check if the pod is running:
kubectl get pods
Output:
NAME READY STATUS RESTARTS AGE
httpd-pod 1/1 Running 0 10s
Verification and Validation
1. Using kubectl describe
Get detailed information about the pod:
kubectl describe pod httpd-pod
Look for the resources section:
Containers:
httpd-container:
Container ID: containerd://...
Image: httpd:latest
State: Running
Limits:
cpu: 100m
memory: 20Mi
Requests:
cpu: 100m
memory: 15Mi
2. Using JSONPath
Extract specific resource values:
# CPU Request
kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.requests.cpu}'
# Output: 100m
# Memory Request
kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.requests.memory}'
# Output: 15Mi
# CPU Limit
kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.limits.cpu}'
# Output: 100m
# Memory Limit
kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.limits.memory}'
# Output: 20Mi
3. Using YAML Output
View the full pod specification:
kubectl get pod httpd-pod -o yaml | grep -A 6 resources
Expected output:
resources:
limits:
cpu: 100m
memory: 20Mi
requests:
cpu: 100m
memory: 15Mi
4. Check Pod Events
Verify no resource-related errors:
kubectl get events --field-selector involvedObject.name=httpd-pod
5. Comprehensive Verification Script
Here's a complete verification script:
#!/bin/bash
echo "=== Pod Resource Verification ==="
echo "Pod Status: $(kubectl get pod httpd-pod -o jsonpath='{.status.phase}')"
echo "CPU Request: $(kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.requests.cpu}')"
echo "Memory Request: $(kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.requests.memory}')"
echo "CPU Limit: $(kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.limits.cpu}')"
echo "Memory Limit: $(kubectl get pod httpd-pod -o jsonpath='{.spec.containers[0].resources.limits.memory}')"
echo "QoS Class: $(kubectl get pod httpd-pod -o jsonpath='{.status.qosClass}')"
Quality of Service (QoS) Classes Explained
Based on the resource requests and limits you set, Kubernetes assigns a QoS class to each pod. This determines the pod's priority when resources are scarce.
1. Guaranteed QoS (Highest Priority)
Conditions: Requests = Limits for ALL resources
resources:
requests:
cpu: "100m"
memory: "15Mi"
limits:
cpu: "100m"
memory: "15Mi"
Characteristics:
- Highest priority in the cluster
- Least likely to be evicted
- Best for critical applications
2. Burstable QoS (Medium Priority)
Conditions: Requests < Limits for at least one resource
resources:
requests:
cpu: "50m"
memory: "10Mi"
limits:
cpu: "100m"
memory: "20Mi"
Characteristics:
- Can use more resources when available
- Moderate priority
- Most common for production applications
3. BestEffort QoS (Lowest Priority)
Conditions: No requests or limits specified
resources: {}
Characteristics:
- Lowest priority
- Most likely to be evicted
- Only use for non-critical workloads
QoS Class Comparison
| QoS Class | CPU | Memory | Priority | Use Case |
|---|---|---|---|---|
| Guaranteed | Requests = Limits | Requests = Limits | Highest | Critical apps (control plane, databases) |
| Burstable | Requests < Limits | Requests = Limits | Medium | Most production workloads |
| Burstable | Requests = Limits | Requests < Limits | Medium | Most production workloads |
| Burstable | Requests < Limits | Requests < Limits | Medium | Most production workloads |
| BestEffort | No limits | No limits | Lowest | Batch jobs, testing |
Advanced Resource Management
1. Namespace-Level Resource Quotas
Limit total resources in a namespace:
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: development
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "10"
Apply the quota:
kubectl apply -f resource-quota.yaml
View quota usage:
kubectl describe quota -n development
2. Default Resource Limits with LimitRange
Set default resource limits for all pods in a namespace:
apiVersion: v1
kind: LimitRange
metadata:
name: resource-limits
namespace: development
spec:
limits:
- max:
cpu: "500m"
memory: "256Mi"
min:
cpu: "50m"
memory: "64Mi"
default:
cpu: "200m"
memory: "128Mi"
defaultRequest:
cpu: "100m"
memory: "64Mi"
type: Container
Apply the limit range:
kubectl apply -f limit-range.yaml
3. Pod with Multiple Containers
Different containers can have different resource limits:
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- name: web-server
image: httpd:latest
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
- name: logger
image: busybox
command: ["sh", "-c", "tail -f /dev/null"]
resources:
requests:
cpu: "50m"
memory: "32Mi"
limits:
cpu: "100m"
memory: "64Mi"
4. Horizontal Pod Autoscaler (HPA)
Automatically scale pods based on resource usage:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: httpd-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: httpd-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Monitoring Resource Usage
1. Using kubectl top
Monitor real-time resource usage:
# View pod resource usage
kubectl top pod httpd-pod
# View all pods resource usage
kubectl top pods
# View node resource usage
kubectl top nodes
# View with specific namespace
kubectl top pods -n production
2. Setting Up Metrics Server
If kubectl top is not available:
# Install metrics server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Verify installation
kubectl get deployment metrics-server -n kube-system
3. Continuous Monitoring
# Monitor pod usage every 5 seconds
watch -n 5 'kubectl top pod httpd-pod'
# Monitor all pods in namespace
watch -n 5 'kubectl top pods -n production'
# Monitor node usage
watch -n 5 'kubectl top nodes'
4. Using Prometheus and Grafana
For enterprise monitoring, set up Prometheus:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: httpd-monitor
spec:
selector:
matchLabels:
app: httpd
endpoints:
- port: metrics
interval: 30s
Common Issues and Troubleshooting
Issue 1: Pod Stuck in Pending State
Error:
0/1 nodes are available: 1 Insufficient cpu
Solution:
# Check node resources
kubectl describe nodes
# Check pod events
kubectl describe pod httpd-pod | grep -A 10 Events
# Reduce resource requests
kubectl patch pod httpd-pod -p '{"spec":{"containers":[{"name":"httpd-container","resources":{"requests":{"cpu":"50m","memory":"10Mi"}}}]}}'
Issue 2: Container Terminated with OOMKilled
Error:
State: Terminated
Reason: OOMKilled
Solution:
# Check pod status
kubectl get pod httpd-pod
# View termination reason
kubectl describe pod httpd-pod | grep -A 5 "State"
# Increase memory limit
kubectl patch pod httpd-pod -p '{"spec":{"containers":[{"name":"httpd-container","resources":{"limits":{"memory":"256Mi"}}}]}}'
# Or check for memory leaks in the application
kubectl logs httpd-pod --previous
Issue 3: CPU Throttling
Symptoms:
- Application performance degradation
- Increased response times
Diagnosis:
# Check CPU usage
kubectl top pod httpd-pod
# Check CPU throttling from inside container
kubectl exec httpd-pod -- cat /sys/fs/cgroup/cpu/cpu.stat
Solution:
# Increase CPU limit
kubectl patch pod httpd-pod -p '{"spec":{"containers":[{"name":"httpd-container","resources":{"limits":{"cpu":"200m"}}}]}}'
Issue 4: Resource Quota Exceeded
Error:
error: failed to create: pods "httpd-pod" is forbidden: exceeded quota: compute-quota
Solution:
# Check current quota usage
kubectl describe resourcequota compute-quota -n development
# Either reduce resource requests or increase quota
kubectl patch resourcequota compute-quota -n development -p '{"spec":{"hard":{"requests.cpu":"8"}}}'
Issue 5: Pod Not Respecting Limits
Verification:
# Check if limits are properly applied
kubectl exec httpd-pod -- cat /sys/fs/cgroup/memory/memory.limit_in_bytes
kubectl exec httpd-pod -- cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
Best Practices for Production
✅ DO's
1. Always Set Both Requests and Limits
# GOOD - Both set
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
# BAD - No resources set
resources: {}
2. Use Appropriate Resource Values
| Application Type | CPU Request | CPU Limit | Memory Request | Memory Limit |
|---|---|---|---|---|
| Static Web | 50m | 100m | 10Mi | 20Mi |
| API Service | 100m | 200m | 64Mi | 128Mi |
| Database | 500m | 1000m | 1Gi | 2Gi |
| Data Processing | 500m | 2000m | 2Gi | 4Gi |
3. Implement Liveness and Readiness Probes
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 15
periodSeconds: 5
4. Use Namespace Defaults
Create LimitRange and ResourceQuota at the namespace level:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
spec:
limits:
- default:
cpu: 200m
memory: 128Mi
defaultRequest:
cpu: 100m
memory: 64Mi
type: Container
5. Monitor Resource Usage Regularly
# Regular monitoring
kubectl top pods
# Setup alerts for resource thresholds
# Use monitoring tools like Prometheus, Grafana, or Datadog
6. Test Resource Limits in Staging
Always test resource limits in a staging environment before production:
# Test memory limit
kubectl exec httpd-pod -- /bin/bash -c "dd if=/dev/zero of=/dev/null bs=1M count=100"
# Test CPU limit
kubectl exec httpd-pod -- /bin/bash -c "while true; do :; done"
7. Document Resource Requirements
Create a document specifying resource requirements for each microservice:
Microservice: httpd-web
CPU Request: 100m
CPU Limit: 200m
Memory Request: 64Mi
Memory Limit: 128Mi
Reason: Average load is 50m CPU and 32Mi memory
❌ DON'Ts
1. Don't Use :latest Tag in Production
# BAD - Unpredictable versions
image: httpd:latest
# GOOD - Specific version
image: httpd:2.4.54
2. Don't Overcommit Resources
# BAD - Limits exceed node capacity
resources:
limits:
cpu: "4"
memory: "16Gi"
# GOOD - Appropriate limits
resources:
limits:
cpu: "1"
memory: "2Gi"
3. Don't Set Requests Too High
# BAD - Wastes resources
requests:
cpu: "2"
memory: "4Gi"
# Actual usage: 50m CPU, 128Mi memory
# GOOD - Realistic requests
requests:
cpu: "100m"
memory: "256Mi"
4. Don't Ignore Memory Limits
Memory is a more critical resource than CPU:
- CPU overuse → throttling
- Memory overuse → OOM kill
# GOOD - Memory limits set
resources:
limits:
memory: "128Mi"
# BAD - Missing memory limits
resources:
limits:
cpu: "100m"
5. Don't Use Burstable for Critical Apps
For critical applications, use Guaranteed QoS:
# GOOD - Guaranteed QoS (for critical apps)
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
# BAD - Burstable (for critical apps)
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "100m"
memory: "128Mi"
Real-World Examples
Example 1: E-commerce Application
apiVersion: v1
kind: Pod
metadata:
name: web-frontend
namespace: production
labels:
app: ecommerce
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.21.6
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 20
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 10
periodSeconds: 5
Example 2: Data Processing Application
apiVersion: v1
kind: Pod
metadata:
name: data-processor
namespace: production
labels:
app: data-pipeline
tier: backend
spec:
containers:
- name: processor
image: data-processor:2.1.0
resources:
requests:
cpu: "500m"
memory: "2Gi"
limits:
cpu: "1000m"
memory: "4Gi"
env:
- name: JVM_OPTS
value: "-Xmx3g -Xms1g"
volumeMounts:
- name: data
mountPath: /data
restartPolicy: Always
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
Example 3: Microservices with Different Requirements
apiVersion: apps/v1
kind: Deployment
metadata:
name: microservice-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: microservice
template:
metadata:
labels:
app: microservice
spec:
containers:
# API Gateway (lightweight)
- name: api-gateway
image: api-gateway:1.0.0
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "100m"
memory: "128Mi"
# User Service (medium)
- name: user-service
image: user-service:1.0.0
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "200m"
memory: "512Mi"
# Search Service (heavy)
- name: search-service
image: search-service:1.0.0
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1000m"
memory: "2Gi"
Capacity Planning and Right-Sizing
Step 1: Monitor Current Usage
# Monitor for a week to understand patterns
kubectl top pod --all-namespaces --no-headers > resource-usage.log
Step 2: Analyze Usage Patterns
#!/usr/bin/env python3
import json
import subprocess
# Get pod resource usage
def get_pod_usage():
cmd = "kubectl top pods --all-namespaces -o json"
output = subprocess.check_output(cmd, shell=True)
data = json.loads(output)
for item in data['items']:
name = item['metadata']['name']
namespace = item['metadata']['namespace']
cpu = item['containers'][0]['usage']['cpu']
memory = item['containers'][0]['usage']['memory']
print(f"{namespace}/{name}: CPU={cpu}, Memory={memory}")
Step 3: Set Appropriate Requests and Limits
Based on usage patterns:
| Metric | Request | Limit |
|---|---|---|
| CPU | Average + 10% | Peak + 20% |
| Memory | Average + 20% | Peak + 30% |
Step 4: Validate with Load Testing
# Test with increased load
kubectl run load-test --image=busybox --rm -it -- /bin/sh
# Inside the container:
wget -O /dev/null http://httpd-pod:80/index.html
# Or use tools like Apache Bench
ab -n 10000 -c 100 http://httpd-pod:80/
Advanced: Resource Quotas and Limit Ranges
Complete Namespace Configuration
---
# Namespace
apiVersion: v1
kind: Namespace
metadata:
name: development
labels:
environment: dev
owner: engineering
---
# Resource Quota
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: development
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"
persistentvolumeclaims: "5"
---
# Limit Range
apiVersion: v1
kind: LimitRange
metadata:
name: dev-limits
namespace: development
spec:
limits:
- max:
cpu: "500m"
memory: "256Mi"
min:
cpu: "50m"
memory: "64Mi"
default:
cpu: "200m"
memory: "128Mi"
defaultRequest:
cpu: "100m"
memory: "64Mi"
type: Container
---
# Network Policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dev-network-policy
namespace: development
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
environment: dev
egress:
- to:
- namespaceSelector:
matchLabels:
environment: dev
Security Implications of Resource Limits
Preventing Resource Exhaustion Attacks
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: app
image: app:1.0
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsNonRoot: true
runAsUser: 1000
Best Practices for Security
- Set CPU limits to prevent CPU starvation attacks
- Set memory limits to prevent memory exhaustion attacks
- Use LimitRanges to enforce limits at the namespace level
- Implement ResourceQuotas to limit total consumption
- Use PodSecurityPolicies to restrict privileged containers
- Monitor resource usage for unusual patterns
- Set appropriate requests to prevent scheduling on nodes with insufficient resources
Troubleshooting Resource Issues
Common Error Messages
| Error | Cause | Solution |
|---|---|---|
0/1 nodes are available: 1 Insufficient cpu |
Not enough CPU | Reduce CPU requests or add nodes |
0/1 nodes are available: 1 Insufficient memory |
Not enough memory | Reduce memory requests or add nodes |
The pod was OOMKilled |
Exceeded memory limit | Increase memory limit or optimize app |
Container is being throttled |
CPU limit too low | Increase CPU limit |
exceeded quota |
Resource quota exceeded | Reduce resource requests or increase quota |
pod stuck in Pending |
Various scheduling issues | Check node resources and events |
Debugging Commands
# Check node capacity
kubectl describe nodes
# Check node resource usage
kubectl top nodes
# Check pod events
kubectl describe pod httpd-pod
# Check all events
kubectl get events --sort-by='.lastTimestamp'
# Check resource usage
kubectl top pod httpd-pod
# Check container logs
kubectl logs httpd-pod --previous
Clean Up
# Delete the pod
kubectl delete pod httpd-pod
# Delete using YAML
kubectl delete -f httpd-pod.yaml
# Delete namespace and all resources
kubectl delete namespace development
# Verify cleanup
kubectl get pods --all-namespaces
Conclusion
Resource management is a fundamental skill for any Kubernetes administrator or DevOps engineer. By properly setting resource requests and limits, you can:
✅ Prevent resource starvation - Ensure fair distribution of cluster resources
✅ Improve cluster stability - Prevent a single application from impacting others
✅ Optimize costs - Right-size your applications for efficient resource utilization
✅ Ensure predictable performance - Guarantee resources for critical applications
✅ Implement proper scheduling - Help Kubernetes make better placement decisions
Top comments (0)