Guía de Requests y Limits de Recursos en Kubernetes
Cómo configurar bien los requests y limits de CPU y memoria en Kubernetes, evitando OOMKills, estrangulamiento de CPU y capacidad desperdiciada.

Los requests y limits de recursos son la configuración que más se malinterpreta en Kubernetes. Si se configuran mal, los Pods terminan OOMKilled en producción, sufren estrangulamiento hasta quedar inutilizables, o desperdician una costosa capacidad del clúster al reservar recursos que nunca llegan a usar. Los valores por defecto no son seguros: los Pods sin requests son los primeros en ser expulsados, y los Pods sin limits pueden consumir un nodo completo.
Entender cómo el scheduler utiliza los requests y cómo el kernel hace cumplir los limits marca la diferencia entre un clúster estable y uno que colapsa bajo carga.
Requests vs Limits
Los requests y los limits cumplen propósitos distintos. Los requests le indican al scheduler cuánta capacidad necesita un Pod. Los limits le indican al kernel el máximo que un Pod puede llegar a usar.
# 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 itselfCómo Funcionan los Requests y Limits de CPU
La CPU es un recurso comprimible. Cuando un Pod supera su limit de CPU, el kernel lo estrangula: el Pod se ejecuta más lento, pero no se elimina. Cuando un Pod está por debajo de su request, el scheduler debería haberlo ubicado en un nodo con suficiente CPU disponible.
# 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"Cómo Funcionan los Limits de Memoria
La memoria es incomprimible. Cuando un Pod supera su limit de memoria, el kernel lo mata de inmediato mediante OOMKill. No existe estrangulamiento para la memoria: o estás dentro de los limits, o tu proceso está muerto.
# 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 limitClases de QoS
Kubernetes asigna una clase de Calidad de Servicio (QoS) a cada Pod según su configuración de recursos. Esto determina la prioridad de expulsión cuando un nodo se queda sin recursos.
# 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',
},
};Estrategia de Dimensionamiento
Definir los valores de recursos correctos requiere medición. Empieza con limits generosos, observa el uso real y luego ajusta.
# 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 y ResourceQuota
Aplica valores por defecto sensatos a nivel de namespace para que los equipos no puedan desplegar por accidente Pods sin limits o con requests excesivos.
# 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 podsPuntos Clave
- Configura siempre tanto los requests como los limits — los Pods que carecen de ellos quedan en QoS BestEffort y son los primeros en ser expulsados bajo presión
- La CPU estrangula, la memoria mata — superar los limits de CPU ralentiza tu Pod; superar los limits de memoria lo termina al instante
- Configura el max-old-space-size de Node.js al 75% del limit de memoria — le da a V8 margen para recolectar basura antes de que el kernel dispare un OOMKill
- Dimensiona en función del uso observado — despliega con limits generosos, monitorea P95/P99 durante una o dos semanas y luego ajusta el tamaño
- Usa LimitRange para los valores por defecto del namespace — evita que los equipos desplieguen Pods sin restricciones de recursos
- QoS Guaranteed para cargas de trabajo críticas — configura los requests iguales a los limits en bases de datos y servicios centrales que no deben ser expulsados


