Leitfaden zu Ressourcen-Requests und -Limits in Kubernetes
Wie man CPU- und Speicher-Requests und -Limits richtig setzt und dabei OOMKills, CPU-Drosselung und verschwendete Cluster-Kapazität vermeidet.

Ressourcen-Requests und -Limits sind die am häufigsten falsch konfigurierten Einstellungen in Kubernetes. Werden sie falsch gesetzt, enden Pods in der Produktion als OOMKilled, werden bis zur Unbrauchbarkeit gedrosselt oder verschwenden teure Cluster-Kapazität, weil sie Ressourcen reservieren, die sie nie nutzen. Die Standardwerte sind nicht sicher: Pods ohne Requests werden als Erste verdrängt, und Pods ohne Limits können einen ganzen Knoten auslasten.
Zu verstehen, wie der Scheduler die Requests nutzt und wie der Kernel die Limits durchsetzt, macht den Unterschied zwischen einem stabilen Cluster und einem, der unter Last abstürzt.
Requests vs. Limits
Requests und Limits erfüllen unterschiedliche Zwecke. Requests teilen dem Scheduler mit, wie viel Kapazität ein Pod benötigt. Limits teilen dem Kernel das Maximum mit, das ein Pod nutzen darf.
# Pod resource configuration
apiVersion: v1
kind: Pod
metadata:
name: api-server
spec:
containers:
- name: api
image: api-server:latest
resources:
requests:
cpu: "250m" # Scheduler reserves 0.25 CPU cores
memory: "256Mi" # Scheduler reserves 256 MiB
limits:
cpu: "1000m" # Kernel throttles above 1 CPU core
memory: "512Mi" # Kernel OOMKills above 512 MiB# ❌ No requests or limits — dangerous
apiVersion: v1
kind: Pod
spec:
containers:
- name: api
image: api-server:latest
# No resource constraints!
# - Scheduler doesn't know how much capacity this needs
# - Pod can consume unlimited CPU and memory
# - Pod is BestEffort QoS — first to be evicted
# - One runaway pod can crash an entire node
# ✅ Properly configured resources
apiVersion: v1
kind: Pod
spec:
containers:
- name: api
image: api-server:latest
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
# Scheduler places pod on a node with enough capacity
# Pod is Burstable QoS — better eviction priority
# Memory is capped — no OOM on the node itselfWie CPU-Requests und -Limits funktionieren
CPU ist eine komprimierbare Ressource. Überschreitet ein Pod sein CPU-Limit, drosselt der Kernel ihn – der Pod läuft langsamer, wird aber nicht beendet. Liegt ein Pod unter seinem Request, sollte der Scheduler ihn auf einem Knoten mit genug freier CPU platziert haben.
# CPU is measured in millicores
# 1000m = 1 full CPU core
# 250m = 0.25 cores (25% of one core)
# 100m = 0.1 cores (10% of one core)
resources:
requests:
cpu: "250m" # Guaranteed 25% of a core
limits:
cpu: "1000m" # Can burst to 100% of a core when available// The CPU throttling problem — a real production issue
// Application handles 100 requests/second at 250m CPU
// Under load spike: 400 requests/second needs 800m CPU
// CPU limit is 1000m — no problem
// But CFS (Completely Fair Scheduler) enforces limits per 100ms period
// At 1000m limit: pod gets 100ms of CPU per 100ms period
// A single request that takes 15ms blocks the entire quota for 15ms
// Other requests wait, causing latency spikes
// This is "CPU throttling" — the pod has burst capacity
// but the CFS period enforcement creates micro-pauses
// Monitoring commands:
// kubectl top pod api-server
// kubectl describe pod api-server | grep -A5 "Limits"Wie Speicher-Limits funktionieren
Speicher ist nicht komprimierbar. Überschreitet ein Pod sein Speicher-Limit, beendet der Kernel ihn sofort per OOMKill. Für Speicher gibt es kein Drosseln – entweder man bleibt innerhalb der Limits, oder der Prozess ist tot.
# Memory is measured in bytes with suffixes
# Mi = mebibytes (1 Mi = 1,048,576 bytes)
# Gi = gibibytes
# M = megabytes (1 M = 1,000,000 bytes)
# Use Mi/Gi to be precise
resources:
requests:
memory: "256Mi" # Scheduler reserves this much
limits:
memory: "512Mi" # OOMKill above this threshold# Diagnosing OOMKill events
kubectl describe pod api-server
# Look for:
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# Check memory usage over time
kubectl top pod api-server --containers
# Check node memory pressure
kubectl describe node <node-name> | grep -A5 "Conditions"// Common causes of OOMKill in Node.js applications
// 1. Memory leaks — unbounded caches, event listener accumulation
const cache = new Map(); // Grows forever without eviction
// 2. Large request payloads loaded entirely into memory
app.post('/upload', (req, res) => {
const body = req.body; // 500MB JSON payload = OOMKill
});
// 3. Heap size exceeds container limit
// Node.js default max heap ≈ 1.7GB (V8 default)
// Container limit set to 512Mi → OOMKill before V8 GC kicks in
// Fix: set --max-old-space-size to ~75% of the container memory limit
// Container limit: 512Mi → --max-old-space-size=384
// This gives V8 room to garbage collect before hitting the limitQoS-Klassen
Kubernetes weist jedem Pod anhand seiner Ressourcenkonfiguration eine Dienstgüteklasse (QoS) zu. Diese bestimmt die Verdrängungspriorität, wenn einem Knoten die Ressourcen ausgehen.
# BestEffort — no requests or limits (evicted first)
resources: {}
# Burstable — requests set, different from limits
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
# Guaranteed — requests equal limits (evicted last)
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"// Choosing the right QoS class
const qosGuide = {
guaranteed: {
useFor: [
'Production databases',
'Core API servers',
'Anything where eviction means downtime',
],
tradeoff: 'No bursting — you pay for reserved capacity',
},
burstable: {
useFor: [
'Most web applications',
'Background workers',
'Services with variable load',
],
tradeoff: 'Can be evicted under node pressure (after BestEffort)',
},
bestEffort: {
useFor: [
'Development/staging namespaces',
'Batch jobs that can be retried',
'Non-critical cron jobs',
],
tradeoff: 'First to be evicted — no guarantees',
},
};Dimensionierungsstrategie
Die richtigen Ressourcenwerte zu setzen erfordert Messung. Beginne mit großzügigen Limits, beobachte die tatsächliche Nutzung und verschärfe sie dann.
# Step 1: Deploy with generous limits, monitor for 1-2 weeks
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "2000m"
memory: "1Gi"
# Step 2: Check actual usage with metrics
kubectl top pod -l app=api-server --containers
# Step 3: Query Prometheus for P99 usage over 7 days
# CPU: rate(container_cpu_usage_seconds_total[5m])
# Memory: container_memory_working_set_bytes
# Step 4: Set requests to P95 usage, limits to P99 + 25% headroom
# If P95 CPU is 200m and P99 is 350m:
resources:
requests:
cpu: "200m" # P95 — covers normal operation
memory: "256Mi"
limits:
cpu: "500m" # P99 + headroom — covers spikes
memory: "384Mi"# ❌ Common sizing mistakes
resources:
requests:
cpu: "2000m" # Massively over-provisioned
memory: "2Gi" # Wastes cluster capacity
limits:
cpu: "2000m"
memory: "2Gi"
# Pod uses 100m CPU and 200Mi memory
# You're paying for 20x the CPU you need
# ✅ Right-sized based on observed usage
resources:
requests:
cpu: "150m" # Based on P95 observed usage
memory: "256Mi" # Based on steady-state usage + 20%
limits:
cpu: "500m" # Room for burst
memory: "384Mi" # Room for spikes, triggers GC before OOMLimitRange und ResourceQuota
Erzwinge sinnvolle Standardwerte auf Namespace-Ebene, damit Teams nicht versehentlich Pods ohne Limits oder mit überzogenen Requests deployen können.
# LimitRange — default limits for the namespace
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4000m"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
type: Container
---
# ResourceQuota — cap total resource usage per namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-budget
namespace: team-alpha
spec:
hard:
requests.cpu: "10" # Total CPU requests across all pods
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50" # Max number of podsDie wichtigsten Erkenntnisse
- Setze immer sowohl Requests als auch Limits — Pods ohne beide laufen als BestEffort-QoS und werden unter Druck als Erste verdrängt
- CPU drosselt, Speicher tötet — das Überschreiten von CPU-Limits verlangsamt deinen Pod; das Überschreiten von Speicher-Limits beendet ihn sofort
- Setze das max-old-space-size von Node.js auf 75 % des Speicher-Limits — das gibt V8 Spielraum für die Garbage Collection, bevor der Kernel einen OOMKill auslöst
- Dimensioniere anhand der beobachteten Nutzung — deploye mit großzügigen Limits, beobachte P95/P99 für ein bis zwei Wochen und passe die Größe dann richtig an
- Nutze LimitRange für Namespace-Standardwerte — verhindert, dass Teams Pods ohne Ressourcenbeschränkungen deployen
- Guaranteed-QoS für kritische Workloads — setze Requests gleich den Limits für Datenbanken und Kerndienste, die nicht verdrängt werden dürfen


