--- title: "Requests y limits en Kubernetes: cómo ajustar CPU y memoria" description: "Configura requests y limits de CPU y memoria en Kubernetes, entiende las clases QoS, aplica LimitRange y ResourceQuota por namespace y diagnostica un OOMKilled." category: "Kubernetes Avanzado" language: es source: https://cubepath.com/es/docs/kubernetes-avanzado/optimizacion-de-limites-y-solicitudes-de-recursos-de-kubernetes updated: 2026-09-25 --- # Requests y limits en Kubernetes: cómo ajustar CPU y memoria Kubernetes decide en qué nodo colocar cada pod según los recursos que solicita, y limita lo que puede consumir según los límites que le pongas. Unas `requests` mal calculadas provocan nodos saturados, pods en `Pending` o contenedores que mueren por falta de memoria. En este tutorial configurarás requests y limits en un Deployment, comprobarás su clase de calidad de servicio (QoS), fijarás valores por defecto y cuotas por namespace, y provocarás y diagnosticarás un `OOMKilled` para ver cómo se comporta el clúster. ## Requisitos previos - Un clúster de Kubernetes en funcionamiento (por ejemplo K3s o MicroK8s sobre un VPS de CubePath con Ubuntu 24.04) y `kubectl` configurado con permisos de administrador. - metrics-server instalado, necesario para `kubectl top`. K3s lo incluye; en MicroK8s se activa con `microk8s enable metrics-server`; en otros clústeres se instala con: ```bash kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml ``` Comprueba que responde antes de continuar: ```bash kubectl top nodes ``` ## Paso 1: Entender requests, limits y sus unidades Cada contenedor puede declarar dos valores por recurso: | Campo | Para qué sirve | Qué pasa si se supera | |-------|----------------|-----------------------| | `requests` | El planificador reserva esa cantidad en el nodo al colocar el pod. | Nada: el contenedor puede usar más si el nodo tiene recursos libres. | | `limits` de CPU | Tope de CPU que puede consumir el contenedor. | El kernel lo ralentiza (throttling); no se reinicia. | | `limits` de memoria | Tope de memoria del contenedor. | El kernel mata el proceso (`OOMKilled`) y el kubelet lo reinicia. | Las unidades son: - **CPU**: en núcleos. `1` es un núcleo (o una vCPU) y `250m` son 250 milinúcleos, es decir, un cuarto de núcleo. - **Memoria**: en bytes, con sufijos binarios `Ki`, `Mi`, `Gi` (1 Gi = 1024 Mi). Evita el sufijo `m` en memoria: `512m` significa 0,512 bytes, no 512 MiB. Para ver cuánto se ha reservado ya en un nodo, revisa la sección `Allocated resources` de su descripción: ```bash kubectl describe node your_node_name | grep -A 8 "Allocated resources" ``` ```output Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 850m (42%) 1500m (75%) memory 1180Mi (30%) 2410Mi (62%) ``` Un pod solo se programa en un nodo si la suma de sus requests cabe en lo que queda libre de la capacidad asignable del nodo, independientemente del uso real. ## Paso 2: Definir requests y limits en un Deployment Crea un namespace de pruebas para trabajar aislado: ```bash kubectl create namespace recursos-demo ``` Crea el manifiesto de un Deployment con requests y limits: ```bash nano web.yaml ``` ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: web namespace: recursos-demo spec: replicas: 2 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: nginx image: nginx:stable resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi ``` Aplícalo y comprueba que los pods arrancan: ```bash kubectl apply -f web.yaml kubectl -n recursos-demo get pods ``` ```output NAME READY STATUS RESTARTS AGE web-6d8f7c9b8d-4kx2p 1/1 Running 0 12s web-6d8f7c9b8d-q7hzl 1/1 Running 0 12s ``` ## Paso 3: Comprobar la clase QoS de los pods Kubernetes asigna a cada pod una clase de calidad de servicio según sus requests y limits: | Clase | Condición | |-------|-----------| | `Guaranteed` | Todos los contenedores tienen requests y limits de CPU y memoria, y requests igual a limits. | | `Burstable` | Al menos un contenedor tiene request o limit de CPU o memoria, pero no cumple lo anterior. | | `BestEffort` | Ningún contenedor define requests ni limits. | Consulta la clase de los pods del namespace: ```bash kubectl -n recursos-demo get pods -o custom-columns=NOMBRE:.metadata.name,QOS:.status.qosClass ``` ```output NOMBRE QOS web-6d8f7c9b8d-4kx2p Burstable web-6d8f7c9b8d-q7hzl Burstable ``` Son `Burstable` porque las requests son menores que los limits. La clase influye cuando un nodo se queda sin memoria: - Si el nodo entra en presión de memoria, el kubelet desaloja (evicta) pods. Primero elige los que usan más memoria de la que solicitaron, después tiene en cuenta la prioridad del pod y finalmente cuánto superan su request. En la práctica, los pods `BestEffort` son los primeros en salir y los `Guaranteed` que respetan su request, los últimos. - Si el kernel tiene que matar procesos por falta de memoria, ajusta su preferencia según la clase QoS, de modo que los contenedores `Guaranteed` son los menos propensos a morir. Usa `Guaranteed` para cargas críticas con consumo predecible, como bases de datos, y `Burstable` para la mayoría de aplicaciones web. Evita `BestEffort` en producción. ## Paso 4: Medir el consumo real para dimensionar Las requests deben reflejar el consumo habitual, no el máximo. Consulta el uso actual por contenedor: ```bash kubectl -n recursos-demo top pods --containers ``` ```output POD NAME CPU(cores) MEMORY(bytes) web-6d8f7c9b8d-4kx2p nginx 0m 3Mi web-6d8f7c9b8d-q7hzl nginx 0m 3Mi ``` `kubectl top` solo muestra el instante actual. Para dimensionar bien, observa el consumo durante al menos una semana con carga real (Prometheus, Grafana o el sistema de métricas que uses) y aplica un criterio sencillo: - **Request de CPU**: alrededor del consumo habitual (percentil 50 a 75). - **Request de memoria**: cerca del consumo máximo sostenido, porque la memoria no se puede recuperar sin matar el proceso. - **Limit de memoria**: con margen sobre el pico observado (por ejemplo, un 25 % a un 50 % más). - **Limit de CPU**: opcional. Un limit bajo provoca throttling y latencias altas aunque el nodo tenga CPU libre. Muchos equipos fijan solo la request de CPU y dejan el limit sin definir o con bastante margen. Para cambiar los valores de un Deployment sin editar el YAML, usa `kubectl set resources`. Kubernetes recreará los pods con los nuevos valores: ```bash kubectl -n recursos-demo set resources deployment web -c nginx --requests=cpu=50m,memory=64Mi --limits=memory=128Mi ``` ## Paso 5: Fijar valores por defecto con LimitRange Un LimitRange aplica valores por defecto a los contenedores de un namespace que no los declaran, y rechaza los que se salen de un mínimo o máximo. Así evitas que alguien despliegue un pod `BestEffort` por olvido: ```bash nano limitrange.yaml ``` ```yaml apiVersion: v1 kind: LimitRange metadata: name: valores-por-defecto namespace: recursos-demo spec: limits: - type: Container defaultRequest: cpu: 100m memory: 128Mi default: cpu: 500m memory: 256Mi min: cpu: 10m memory: 16Mi max: cpu: "2" memory: 2Gi ``` `defaultRequest` se usa como request cuando el contenedor no la declara y `default` como limit. Aplícalo y crea un pod sin recursos para comprobar el efecto: ```bash kubectl apply -f limitrange.yaml kubectl -n recursos-demo run sin-recursos --image=nginx:stable kubectl -n recursos-demo get pod sin-recursos -o jsonpath='{.spec.containers[0].resources}{"\n"}' ``` ```output {"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"100m","memory":"128Mi"}} ``` Si intentas crear un contenedor con un limit de memoria superior a `2Gi`, la API lo rechaza con un error `Forbidden` que menciona el LimitRange. Los LimitRange solo actúan al crear pods; no modifican los que ya existen. ## Paso 6: Limitar el total del namespace con ResourceQuota Una ResourceQuota fija el consumo total que puede reservar un namespace, útil cuando varios equipos o proyectos comparten el clúster: ```bash nano quota.yaml ``` ```yaml apiVersion: v1 kind: ResourceQuota metadata: name: cuota-recursos namespace: recursos-demo spec: hard: requests.cpu: "2" requests.memory: 4Gi limits.cpu: "4" limits.memory: 8Gi pods: "20" persistentvolumeclaims: "5" ``` ```bash kubectl apply -f quota.yaml kubectl -n recursos-demo describe resourcequota cuota-recursos ``` ```output Name: cuota-recursos Namespace: recursos-demo Resource Used Hard -------- ---- ---- limits.cpu 1500m 4 limits.memory 512Mi 8Gi persistentvolumeclaims 0 5 pods 3 20 requests.cpu 200m 2 requests.memory 256Mi 4Gi ``` > Importante: cuando una cuota limita `requests` o `limits` de un recurso, todos los pods nuevos del namespace deben declarar ese valor. Sin un LimitRange que aporte valores por defecto, los pods sin recursos se rechazan con el error `must specify limits.cpu` o similar. Si un Deployment supera la cuota, sus pods no llegan a crearse. El motivo aparece en los eventos del ReplicaSet, no en el pod: ```bash kubectl -n recursos-demo get events --field-selector reason=FailedCreate ``` ## Paso 7: Provocar y diagnosticar un OOMKilled Para ver qué ocurre cuando un contenedor supera su límite de memoria, crea un pod que intenta reservar 250 MiB con un límite de 100 MiB. La imagen `polinux/stress` es la que usa la documentación oficial de Kubernetes para este ejemplo: ```bash nano oom.yaml ``` ```yaml apiVersion: v1 kind: Pod metadata: name: prueba-oom namespace: recursos-demo spec: containers: - name: stress image: polinux/stress command: ["stress"] args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"] resources: requests: memory: 50Mi limits: memory: 100Mi ``` ```bash kubectl apply -f oom.yaml kubectl -n recursos-demo get pod prueba-oom -w ``` ```output NAME READY STATUS RESTARTS AGE prueba-oom 0/1 ContainerCreating 0 2s prueba-oom 0/1 OOMKilled 0 5s prueba-oom 0/1 OOMKilled 1 (3s ago) 9s prueba-oom 0/1 CrashLoopBackOff 1 (12s ago) 20s ``` Pulsa `Ctrl+C` para salir. Confirma el motivo de la última terminación: ```bash kubectl -n recursos-demo describe pod prueba-oom | grep -A 4 "Last State" ``` ```output Last State: Terminated Reason: OOMKilled Exit Code: 137 ``` El código de salida 137 indica que el proceso recibió `SIGKILL`. Cuando lo veas en una aplicación real, las soluciones son, por orden: comprobar si hay una fuga de memoria, ajustar la configuración de memoria de la aplicación (por ejemplo, el tamaño del heap de la JVM o los workers de un servidor web) y, si el consumo es legítimo, subir el limit y la request de memoria. Un pod en `Pending` es el problema contrario: pide más de lo que cabe. `kubectl describe pod` lo explica en los eventos con un mensaje como `0/3 nodes are available: 3 Insufficient memory`. En ese caso reduce la request o añade capacidad al clúster. ## Paso 8: Limpiar el entorno de pruebas Borra el namespace, que elimina también el Deployment, los pods, el LimitRange y la cuota: ```bash kubectl delete namespace recursos-demo ``` ## Conclusión Has configurado requests y limits, comprobado cómo determinan la clase QoS, fijado valores por defecto con LimitRange, acotado un namespace con ResourceQuota y diagnosticado un contenedor `OOMKilled`. Como siguientes pasos, instala Prometheus y Grafana para observar el consumo histórico, prueba el Vertical Pod Autoscaler en modo de solo recomendación para obtener sugerencias de requests, o configura un HorizontalPodAutoscaler, que calcula el uso de CPU en porcentaje sobre la request declarada.