Optimización de Recursos en Kubernetes: Limitando y Solicitando CPU/Memoria para tus Contenedores 🚀
Este tutorial te guiará a través de la configuración de requests y limits para CPU y memoria en Kubernetes. Entenderás cómo gestionar los recursos de forma eficiente para garantizar la estabilidad de tus aplicaciones y optimizar el uso del clúster, evitando problemas de rendimiento y costos innecesarios.
La gestión eficiente de recursos es fundamental en cualquier entorno de producción, y Kubernetes no es la excepción. Sin una configuración adecuada, tus aplicaciones pueden sufrir de bajo rendimiento o, peor aún, inestabilidad, afectando a otras cargas de trabajo en el mismo clúster.
En este tutorial, profundizaremos en cómo Kubernetes maneja los recursos de CPU y memoria, y cómo puedes utilizar las configuraciones de requests y limits para asegurar que tus contenedores obtengan lo que necesitan sin acaparar excesivamente los recursos compartidos.
💡 Entendiendo Requests y Limits en Kubernetes
Kubernetes ofrece dos mecanismos principales para gestionar los recursos de los contenedores: requests (solicitudes) y limits (límites).
-
Requests (Solicitudes): Es la cantidad de recursos que un contenedor solicita al programador (scheduler) de Kubernetes. El programador usa este valor para decidir en qué nodo colocar el Pod. Un nodo debe tener suficiente capacidad disponible para satisfacer todas las solicitudes de los Pods que se ejecuten en él. Si un contenedor no especifica un request, se le asigna un valor por defecto (dependiendo de la configuración del clúster o del namespace).
-
Limits (Límites): Es la cantidad máxima de recursos que un contenedor puede consumir. Si un contenedor intenta usar más CPU que su límite, será ralentizado (throttled). Si intenta usar más memoria que su límite, el contenedor será terminado por el sistema operativo (OOMKilled - Out Of Memory Killed).
CPU y Memoria: Unidades de Medida
Antes de configurar requests y limits, es importante entender las unidades de medida:
- CPU: Se mide en unidades de CPU.
1representa una CPU completa.0.5o500m(quinientos milicores) representa la mitad de una CPU.100mson 0.1 CPU. Las solicitudes de CPU son siempre absolutas, no porcentajes. - Memoria: Se mide en bytes. Comúnmente se usan unidades como
Mi(mebibytes) oGi(gibibytes).100Mison 100 mebibytes,2Gison 2 gibibytes.
🛠️ Cómo Configurar Requests y Limits en un Pod
La configuración de requests y limits se realiza en la especificación del contenedor dentro de un manifiesto de Pod (o Deployment, StatefulSet, etc.).
Aquí tienes un ejemplo básico de cómo se vería en un Pod:
apiVersion: v1
kind: Pod
metadata:
name: mi-aplicacion-con-recursos
spec:
containers:
- name: mi-contenedor
image: nginx:latest
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
En este ejemplo:
- El contenedor
mi-contenedorsolicita 64 MiB de memoria y 250 milicores de CPU al programador. El programador solo colocará este Pod en un nodo que tenga, al menos, 64 MiB de memoria y 250 milicores de CPU disponibles para nuevos Pods. - El contenedor puede usar un máximo de 128 MiB de memoria y 500 milicores de CPU. Si intenta usar más memoria, será OOMKilled. Si intenta usar más CPU, será ralentiado.
Impacto de los Requests y Limits
- Planificación (Scheduling): Los requests de CPU y memoria son usados por el planificador de Kubernetes para determinar qué nodo tiene suficiente capacidad para ejecutar un Pod. Si no hay nodos con la capacidad solicitada, el Pod permanecerá en estado
Pending. - Calidad de Servicio (QoS Classes): Kubernetes asigna una clase de Calidad de Servicio (QoS) a cada Pod en función de cómo se configuran sus requests y limits. Esto influye en cómo se manejan los Pods en situaciones de escasez de recursos.
- Guaranteed: Si todos los contenedores en el Pod tienen requests y limits de CPU y memoria establecidos, y estos son iguales, el Pod recibe la clase
Guaranteed. Estos Pods tienen la mayor prioridad y son los últimos en ser terminados en caso de escasez de recursos. Son ideales para aplicaciones críticas. - Burstable: Si los requests y limits son diferentes (o solo se establecen requests), o si solo se establecen requests pero no limits, el Pod es
Burstable. Estos Pods tienen una prioridad intermedia y pueden ser terminados antes que losGuaranteedsi el nodo se queda sin recursos. - BestEffort: Si no se establecen ni requests ni limits para ningún contenedor del Pod, se les asigna la clase
BestEffort. Estos Pods tienen la menor prioridad y son los primeros en ser terminados cuando el sistema necesita liberar recursos. No se recomienda para aplicaciones de producción.
- Guaranteed: Si todos los contenedores en el Pod tienen requests y limits de CPU y memoria establecidos, y estos son iguales, el Pod recibe la clase
Ejemplos Prácticos de Configuración
Pod con QoS Guaranteed
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: mi-contenedor-guaranteed
image: busybox
command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
resources:
requests:
memory: "128Mi"
cpu: "500m"
limits:
memory: "128Mi"
cpu: "500m"
En este caso, tanto requests como limits son idénticos para CPU y memoria. El Pod será Guaranteed.
Pod con QoS Burstable
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: mi-contenedor-burstable
image: busybox
command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "1000m"
Aquí, los limits son mayores que los requests, haciendo que el Pod sea Burstable. Puede "burstear" hasta los límites si hay recursos disponibles en el nodo.
Pod con QoS BestEffort
apiVersion: v1
kind: Pod
metadata:
name: besteffort-pod
spec:
containers:
- name: mi-contenedor-besteffort
image: busybox
command: ["sh", "-c", "echo Hello Kubernetes! && sleep 3600"]
# No se especifican requests ni limits
Al no especificar ningún recurso, el Pod será BestEffort.
📈 Determinando los Valores Correctos para Requests y Limits
Establecer los valores adecuados para requests y limits es más un arte que una ciencia exacta, pero hay metodologías que puedes seguir:
-
Monitorización y Observabilidad: La mejor manera es observar el comportamiento de tus aplicaciones en un entorno representativo (desarrollo, staging o pre-producción). Utiliza herramientas como Prometheus, Grafana, Datadog u otras para monitorear el consumo real de CPU y memoria de tus contenedores.
- Identifica picos: Observa los picos de uso durante las horas de mayor carga o eventos especiales.
- Uso promedio: Analiza el consumo promedio para establecer una base.
-
Pruebas de Carga (Load Testing): Somete tus aplicaciones a pruebas de carga para simular un uso intensivo y medir el consumo de recursos en esas condiciones.
-
Análisis Histórico: Si tienes métricas históricas de tus aplicaciones, úsalas para predecir el comportamiento futuro.
-
Comienza con un Estimado y Ajusta: Es mejor empezar con valores ligeramente conservadores (un poco más altos de lo que crees que necesitará) y luego ir ajustando a la baja a medida que obtienes más datos y confianza. Esto evita OOMKills o estrangulamiento de CPU al principio.
Recomendaciones Generales:
- Requests: Deberían ser la cantidad mínima de recursos que tu aplicación necesita para funcionar de manera estable en condiciones normales. Idealmente, el
requestde memoria debería ser suficiente para que la aplicación no se caiga por falta de memoria. - Limits: Deberían ser la cantidad máxima de recursos que tu aplicación puede utilizar sin afectar negativamente a otras aplicaciones en el mismo nodo. Para la CPU, un limit es útil para evitar que un contenedor "monopolice" el procesador. Para la memoria, es una protección crítica para evitar que un contenedor inestable agote la memoria del nodo y cause un fallo general.
🔗 ResourceQuotas y LimitRanges: Gestión a Nivel de Namespace
Aunque configurar requests y limits en cada Pod es esencial, Kubernetes ofrece herramientas a nivel de Namespace para imponer políticas y valores por defecto:
ResourceQuotas
ResourceQuotas te permiten limitar el consumo total de recursos (CPU, memoria, número de Pods, Services, etc.) dentro de un Namespace. Esto es fundamental para evitar que un solo equipo o aplicación consuma todos los recursos del clúster.
Un ResourceQuota puede, por ejemplo, asegurar que un Namespace no exceda un total de 4 CPUs y 8 GiB de memoria en todos sus Pods.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "20"
En este ejemplo, el namespace team-a no podrá tener Pods que en total soliciten más de 4 CPUs o 8 GiB de memoria, ni que en total tengan límites de más de 8 CPUs o 16 GiB de memoria, y no puede desplegar más de 20 Pods.
LimitRanges
LimitRanges te permiten aplicar valores de requests y limits por defecto a los contenedores si no los especifican en su manifiesto. También pueden imponer límites mínimos y máximos para requests y limits que los usuarios pueden establecer en un Namespace.
Esto es muy útil para asegurar que todos los Pods en un Namespace tengan al menos un request y limit base, evitando los Pods BestEffort por accidente y estableciendo rangos razonables.
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-mem-limit-range
namespace: dev
spec:
limits:
- default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
type: Container
En este LimitRange para el namespace dev:
- Si un contenedor no especifica requests o limits, obtendrá por defecto:
request.cpu=100m,request.memory=128Mi,limit.cpu=500m,limit.memory=256Mi. - Cualquier contenedor en este
namespacedebe tener requests de CPU de al menos50my memoria de al menos64Mi. - Ningún contenedor puede solicitar más de
2CPU o4Gide memoria.
✅ Buenas Prácticas y Estrategias
Para maximizar los beneficios de la gestión de recursos en Kubernetes, considera las siguientes buenas prácticas:
- Monitoriza Continuamente: Los requisitos de recursos de las aplicaciones pueden cambiar con el tiempo. Establece alarmas para detectar si los Pods alcanzan sus límites o si el clúster está bajo presión.
- Itera y Ajusta: No esperes acertar los valores perfectos a la primera. Es un proceso iterativo. Empieza con valores razonables y ajústalos basándote en la observación y las métricas.
- Evita el "Overprovisioning" Excesivo: Aunque es tentador asignar muchos recursos "por si acaso", esto lleva a un desperdicio significativo de recursos y a un aumento innecesario de costos, especialmente en entornos de nube.
- Usa
LimitRangesyResourceQuotas: Impleméntalos en tusNamespacespara aplicar políticas uniformes y prevenir el consumo descontrolado de recursos por parte de los equipos. - Entiende la Calidad de Servicio (QoS): Decide qué clase de QoS es apropiada para cada aplicación. Las aplicaciones críticas deben ser
Guaranteed, mientras que las menos importantes pueden serBurstableo inclusoBestEffort(aunque esto último es raro en producción). - Prueba tus Configs: Antes de desplegar en producción, asegúrate de que tus aplicaciones funcionan correctamente con los requests y limits establecidos en un entorno de staging.
- Considera HPA (Horizontal Pod Autoscaler) y VPA (Vertical Pod Autoscaler): Para cargas de trabajo variables, HPA y VPA pueden ajustar automáticamente el número de Pods o los recursos asignados a un Pod, respectivamente, basándose en la utilización de recursos o métricas personalizadas. Esto complementa la configuración manual.
¿Qué pasa si un Pod `BestEffort` consume muchos recursos?
Si un Pod `BestEffort` comienza a consumir una cantidad significativa de recursos, y el nodo donde se ejecuta empieza a tener escasez, Kubernetes lo terminará antes que a los Pods `Burstable` o `Guaranteed` para liberar recursos críticos. Esto puede llevar a interrupciones en el servicio si la aplicación era importante.Ejercicio Práctico: Desplegar y Observar
Para poner en práctica lo aprendido, puedes realizar el siguiente ejercicio:
- Crea un
Namespace:
kubectl create namespace test-resources
- Aplica un
LimitRangeyResourceQuota(opcional pero recomendado):
# limit-range.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: test-resources
spec:
limits:
- default:
cpu: "300m"
memory: "200Mi"
defaultRequest:
cpu: "100m"
memory: "100Mi"
type: Container
---
# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: test-quota
namespace: test-resources
spec:
hard:
requests.cpu: "1"
requests.memory: "1Gi"
limits.cpu: "2"
limits.memory: "2Gi"
pods: "5"
Aplica ambos:
kubectl apply -f limit-range.yaml -n test-resources
kubectl apply -f resource-quota.yaml -n test-resources
- Despliega un Pod sin requests/limits:
# pod-no-limits.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-no-limits
namespace: test-resources
spec:
containers:
- name: my-container
image: busybox
command: ["sh", "-c", "while true; do echo Hello Kubernetes!; sleep 1; done"]
Aplica:
kubectl apply -f pod-no-limits.yaml -n test-resources
Observa el Pod. ¿Qué requests y limits se le asignaron por el `LimitRange`?
kubectl describe pod my-app-no-limits -n test-resources
Deberías ver los valores por defecto aplicados en la sección `Resources` del contenedor.
4. Despliega un Pod con requests y limits explícitos (y genera carga):
# pod-with-limits.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-with-limits
namespace: test-resources
spec:
containers:
- name: cpu-burner
image: busybox
command: ["sh", "-c", "while true; do dd if=/dev/zero of=/dev/null bs=1M count=100; done"]
resources:
requests:
memory: "50Mi"
cpu: "100m"
limits:
memory: "100Mi"
cpu: "200m"
- name: mem-burner
image: busybox
command: ["sh", "-c", "head -c 200M < /dev/urandom | tail"]
resources:
requests:
memory: "150Mi"
cpu: "50m"
limits:
memory: "300Mi"
cpu: "100m"
Aplica:
kubectl apply -f pod-with-limits.yaml -n test-resources
Este Pod tiene dos contenedores, uno que intenta consumir CPU y otro memoria. Observa el estado del Pod y sus logs. ¿Se produce throttling de CPU? ¿Hay OOMKills en el contenedor de memoria? Puedes usar `kubectl top pod my-app-with-limits -n test-resources` (si Metric Server está instalado) para ver el consumo real.
5. Intenta crear un Pod que exceda el ResourceQuota:
Modifica el pod-with-limits.yaml para solicitar más recursos de los que el ResourceQuota permite para un solo Pod (ej: cpu: 1500m). Intenta aplicarlo y observa el error.
Al finalizar, no olvides limpiar los recursos:
kubectl delete namespace test-resources
Conclusión ✨
La optimización de recursos mediante requests y limits en Kubernetes es una habilidad esencial para cualquier administrador de clúster o desarrollador de aplicaciones. No solo garantiza que tus aplicaciones se ejecuten de manera eficiente y estable, sino que también contribuye a un uso más eficaz y rentable de la infraestructura subyacente. Al combinar estas configuraciones con la monitorización, ResourceQuotas y LimitRanges, puedes construir un entorno Kubernetes robusto y bien gobernado.
Tutoriales relacionados
- Observabilidad Integral en Kubernetes: Monitorización, Logs y Tracing con Prometheus, Grafana y Jaeger 📊intermediate20 min
- Gestión de Configuración en Kubernetes: ConfigMaps y Secrets para Aplicaciones Robustas ⚙️intermediate18 min
- Gestión de Redes en Kubernetes: Servicios, Ingress y Network Policies 🌐intermediate15 min
- Asegurando la Comunicación en Kubernetes: Implementando mTLS con Istio y Cert-Manager 🛡️intermediate25 min
- Despliegues Azules/Verdes en Kubernetes: Estrategias de Actualización sin Interrupciones 🔄intermediate20 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!