Dominando las Solicitudes y Límites de Recursos de Kubernetes para un Rendimiento Óptimo
Aprenda las diferencias cruciales entre las Solicitudes y Límites de Recursos de Kubernetes para CPU y Memoria. Esta guía explica cómo estas configuraciones determinan las clases de Calidad de Servicio (QoS) (Garantizado, Explosivo, MejorEsfuerzo), previenen la inestabilidad del nodo y optimizan la eficiencia de la programación del clúster. Incluye ejemplos prácticos de YAML y mejores prácticas para el ajuste del rendimiento.
Dominando las Solicitudes y Límites de Recursos de Kubernetes para un Rendimiento Óptimo
Las solicitudes y límites de recursos de Kubernetes se ven simples en YAML, pero moldean casi todos los comportamientos del día a día en un clúster: dónde aterrizan los pods, qué cargas de trabajo se desalojan primero, si una aplicación sufre estrangulamiento de CPU bajo carga y cuánta capacidad sobrante crees que tienes. Una mala configuración puede hacer que una aplicación saludable parezca rota. Una configuración faltante puede hacer que el programador empaquete pods en un nodo hasta que el primer pico de tráfico se convierta en un incidente de vecino ruidoso.
La parte que atrapa a los equipos es que las solicitudes y los límites son utilizados por diferentes sistemas. Las solicitudes son principalmente una promesa de programación. Los límites son un límite de cumplimiento. Tratarlos como lo mismo conduce a resultados extraños, especialmente con la CPU.
Comprendiendo los Conceptos Clave: Solicitudes vs. Límites
En Kubernetes, un contenedor puede definir el consumo esperado de recursos usando resources.requests y resources.limits. No son técnicamente obligatorios a menos que tu clúster use políticas como LimitRange o controles de admisión, pero las cargas de trabajo de producción deberían definir al menos solicitudes para CPU y memoria. Sin solicitudes, el programador tiene poca información útil y el pod cae en una postura de calidad de servicio más débil.
1. Solicitudes de Recursos (requests)
Las solicitudes representan la cantidad de recursos que un contenedor tiene garantizado recibir al ser programado. Esta es la cantidad mínima de recursos que el kube-scheduler utiliza al decidir en qué nodo colocar un Pod.
- Programación: Un nodo debe tener suficientes recursos asignables disponibles que satisfagan la suma de todas las solicitudes de Pod antes de que un nuevo Pod pueda ser programado allí.
- Prioridad en tiempo de ejecución: Las solicitudes influyen en las participaciones de CPU y las decisiones de desalojo de memoria. No son una reserva mágica que previene cada ralentización, pero le dan a Kubernetes y al kernel mejor información durante la contención.
2. Límites de Recursos (limits)
Los límites definen la cantidad máxima de recursos que un contenedor puede consumir. Exceder estos límites resulta en comportamientos específicos y definidos para CPU y Memoria.
- Límites de CPU: Si un contenedor intenta usar más CPU que su límite, los cgroups del kernel de Linux estrangulan su uso, impidiéndole consumir más ciclos.
- Límites de Memoria: Si un contenedor excede su límite de memoria, el kernel puede terminar un proceso en el contenedor. Kubernetes reporta esto como
OOMKilledcuando esa es la razón de terminación registrada.
Comportamiento de CPU vs. Memoria
Es crucial entender la diferencia cualitativa en cómo Kubernetes aplica los límites de CPU versus Memoria:
| Recurso | Comportamiento al Exceder el Límite | Mecanismo de Cumplimiento |
|---|---|---|
| CPU | Estrangulado (ralentizado) | cgroups (control de ancho de banda de CPU) |
| Memoria | Terminado (OOMKill) | Asesino OOM del Kernel |
Precaución práctica: Los límites de CPU pueden proteger un nodo de un proceso descontrolado, pero también pueden crear problemas de latencia cuando se configuran demasiado bajos. Muchos equipos de plataforma establecen límites de memoria de manera consistente y son más selectivos con los límites de CPU para servicios sensibles a la latencia, dependiendo de su tolerancia al riesgo y la política del clúster.
Definiendo Recursos en Especificaciones de Pod
Los recursos se definen dentro del bloque spec.containers[*].resources. Las cantidades se especifican usando sufijos estándar de Kubernetes (por ejemplo, m para mili-CPU, Mi para Mebibytes).
Definiciones de Unidades de CPU
1unidad de CPU equivale a 1 núcleo completo (o vCPU en proveedores de nube).1000m(mil núcleos) equivale a 1 unidad de CPU.
Definiciones de Unidades de Memoria
Mi(Mebibytes) oGi(Gibibytes) son comunes.1024Mi=1Gi.
Ejemplo de Configuración YAML
Considere un contenedor que requiere un mínimo garantizado de 500m de CPU y 256Mi de memoria, pero nunca debe exceder 1 CPU y 512Mi:
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "1"
Los números deben provenir del comportamiento observado, no de suposiciones. Para una API HTTP pequeña, una solicitud inicial podría basarse en el uso normal p50 o p90 durante el tráfico comercial, luego ajustarse después de las pruebas de carga. Para un servicio JVM, la memoria necesita incluir heap, metaspace, memoria nativa, pilas de hilos, búferes directos y sobrecarga del sidecar. Para un trabajo por lotes, el pico de memoria durante la entrada más grande puede importar más que la memoria promedio.
Clases de Calidad de Servicio (QoS)
La relación entre Solicitudes y Límites determina la clase de Calidad de Servicio (QoS) asignada a un Pod. Esta clase dicta la prioridad del Pod cuando los recursos escasean y el nodo necesita reclamar memoria (desalojo).
Kubernetes define tres clases de QoS:
1. Garantizado
Definición: Todos los contenedores en el Pod deben tener Solicitudes y Límites idénticos y no nulos para ambos, CPU y Memoria.
- Beneficio: Estos Pods son los últimos en ser desalojados durante la presión de recursos, asegurando la máxima estabilidad.
- Caso de Uso: Componentes críticos del sistema o bases de datos que requieren un estricto aislamiento de rendimiento.
2. Explosivo
Definición: Al menos un contenedor en el Pod tiene Solicitudes definidas, pero ya sea que las Solicitudes y Límites no sean iguales para todos los contenedores, o que algunos recursos no estén limitados (aunque establecer límites es altamente recomendado).
- Beneficio: Permite que los contenedores exploten más allá de sus solicitudes, utilizando capacidad no utilizada en el nodo, hasta sus límites definidos.
- Prioridad de Desalojo: Desalojados antes que los Pods MejorEsfuerzo, pero después de los Pods Garantizados.
- Caso de Uso: La mayoría de las aplicaciones stateless estándar donde una ligera variación en la latencia es aceptable.
3. MejorEsfuerzo
Definición: El Pod no tiene Solicitudes ni Límites definidos para ningún contenedor.
- Beneficio: Ninguno, aparte de la simplicidad.
- Riesgo: Estos Pods son los primeros candidatos para el desalojo cuando el nodo experimenta presión de memoria. También pueden competir mal por la CPU porque no se ha declarado ninguna solicitud.
- Caso de Uso: Trabajos por lotes no críticos o agentes de registro que pueden reiniciarse fácilmente.
Estrategias Prácticas de Optimización
La gestión efectiva de recursos requiere medición, iteración y planificación cuidadosa.
Estrategia 1: Medir y Establecer Solicitudes con Precisión
Las solicitudes deben reflejar la cantidad de recurso que la aplicación necesita para funcionar aceptablemente la mayor parte del tiempo. Si estableces solicitudes demasiado altas, desperdicias capacidad del clúster porque el programador trata esa capacidad como ya ocupada. Si las estableces demasiado bajas, el programador puede colocar demasiados pods en un nodo, y la carga de trabajo puede sufrir más durante la contención.
Usa herramientas de monitoreo como Prometheus y Grafana para comparar los valores de solicitud con el uso real. Un punto de partida común es observar varios días de tráfico normal, ignorar incidentes puntuales obvios y establecer solicitudes cerca de un percentil sostenido en lugar del pico más alto único. El percentil exacto es una elección de política; el punto principal es usar datos y revisarlos.
Por ejemplo, si un servicio normalmente usa 180m de CPU, alcanza un pico alrededor de 450m durante el calentamiento de la implementación y tiene picos raros a 900m durante una tarea por lotes conocida, establecer una solicitud de 900m puede desperdiciar capacidad todo el día. Establecer una solicitud de 50m puede hacer que el pod sea barato de programar pero inestable bajo contención. Una solicitud alrededor del rango sostenido normal, más un manejo separado para la ruta por lotes, es a menudo una mejor conversación.
Estrategia 2: Definir Límites Conservadores
Los límites actúan como un límite de seguridad, pero no son gratuitos. Para la memoria, un límite ligeramente por encima del pico de uso medido puede evitar que un contenedor consuma el nodo. Para la CPU, un límite evita que un proceso descontrolado use CPU ilimitada, pero los límites agresivos pueden estrangular un servicio incluso cuando el nodo tiene núcleos inactivos.
Advertencia sobre los Límites de CPU: Establecer límites de CPU por debajo de la demanda real puede causar latencia visible a través del estrangulamiento. La QoS Explosiva es un ajuste razonable para muchos servicios stateless, mientras que la QoS Garantizada es mejor reservarla para cargas de trabajo donde la compensación de aislamiento es intencional.
Estrategia 3: Aprovechar el Autoscaler Vertical de Pods (VPA)
Ajustar manualmente los recursos es difícil y consume tiempo. El Autoscaler Vertical de Pods (VPA) monitorea el uso en tiempo de ejecución y puede recomendar o actualizar las solicitudes de recursos, dependiendo de su modo. En muchas configuraciones, el VPA se usa primero en modo de recomendación para que los equipos puedan revisar las solicitudes sugeridas antes de permitir actualizaciones automáticas.
Ten cuidado al combinar VPA con el Autoscaler Horizontal de Pods. HPA a menudo escala basándose en la utilización en relación con las solicitudes, por lo que cambiar las solicitudes puede cambiar el comportamiento de escalado. Puede funcionar bien, pero debe probarse deliberadamente.
Estrategia 4: Cuotas de Recursos para Namespaces
Para prevenir la acumulación de recursos entre equipos o entornos, los administradores deben usar Cuotas de Recursos a nivel de Namespace. Una ResourceQuota impone límites agregados en la cantidad total de Solicitudes y Límites de CPU/Memoria que pueden existir dentro de ese namespace, asegurando equidad.
Ejemplo de Cuota de Namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: development
spec:
hard:
requests.cpu: "10"
limits.memory: "20Gi"
Esto asegura que la CPU total solicitada en todos los Pods en el namespace development no pueda exceder 10 núcleos, y los límites totales de memoria no puedan exceder 20Gi.
Cómo las Malas Configuraciones se Manifiestan en Clústeres Reales
Los errores de recursos rara vez se anuncian como "errores de recursos". Generalmente parecen incidentes de aplicación.
Si los límites de CPU son demasiado bajos, puedes ver alta latencia de solicitud mientras los gráficos de uso de CPU parecen limitados. El contenedor quiere más CPU, pero el estrangulamiento de cgroup lo retiene. En configuraciones basadas en Prometheus, métricas como container_cpu_cfs_throttled_periods_total y container_cpu_cfs_periods_total pueden ayudar a mostrar si el estrangulamiento es parte de la historia.
Si los límites de memoria son demasiado bajos, el pod puede reiniciarse con Reason: OOMKilled. Los registros de la aplicación pueden terminar abruptamente porque el proceso no tuvo un apagado graceful. kubectl describe pod generalmente dice la verdad más rápido que el registro de la aplicación en este caso.
Si las solicitudes son demasiado altas, los pods pueden permanecer en Pending aunque los paneles muestren que el uso promedio del nodo es bajo. El programador no coloca pods basándose en el uso real promedio; compara los recursos solicitados con los recursos asignables. Es por esto que un clúster puede verse infrautilizado y aún así rechazar un nuevo pod.
Si faltan solicitudes, la carga de trabajo puede verse bien durante períodos tranquilos y luego convertirse en lo primero que se aprieta cuando el nodo está ocupado. Eso puede ser aceptable para trabajos desechables, pero es un mal valor predeterminado para servicios orientados al usuario.
Un Flujo de Trabajo de Ajuste Más Seguro
Un bucle de ajuste práctico se ve así:
- Comienza con solicitudes explícitas de CPU y memoria para cada contenedor de producción, incluidos los sidecars.
- Establece límites de memoria basados en el pico observado más un margen, luego observa reinicios OOMKilled.
- Decide si los límites de CPU son requeridos por política o riesgo de carga de trabajo. Si los usas, monitorea el estrangulamiento.
- Compara la CPU y memoria solicitadas con el uso real semanal o mensualmente, especialmente después de lanzamientos importantes.
- Trata el redimensionamiento como gestión de cambios. Una solicitud más baja puede aumentar la densidad de empaquetado, pero también puede cambiar el comportamiento de fallo durante la presión del nodo.
Los sidecars merecen atención. Un proxy de malla de servicios, un recolector de registros o un agente de seguridad pueden consumir suficiente CPU o memoria para cambiar la huella real del pod. Si solo se ajusta el contenedor de la aplicación principal, el pod aún puede estar mal representado ante el programador.
Ejemplo: Arreglando un Pico de Latencia Causado por Estrangulamiento de CPU
Imagina un contenedor API con esta configuración:
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "200m"
memory: "512Mi"
Durante un evento de venta, la latencia aumenta. El nodo aún tiene CPU inactiva, pero el contenedor está limitado a 200m. Aumentar las réplicas puede ayudar, pero cada pod sigue estando estrangulado individualmente. Una mejor solución podría ser aumentar o eliminar el límite de CPU, aumentar la solicitud para que coincida con la demanda sostenida normal y usar HPA para que el servicio escale antes de que la latencia se vuelva fea.
La lección importante es que el bajo uso de CPU en el gráfico no siempre significa que la aplicación esté inactiva. Puede significar que la aplicación no tiene permitido usar más.
Verificación Final
Las solicitudes le dicen a Kubernetes cómo colocar y priorizar el pod. Los límites le dicen al kernel dónde detenerlo. Los buenos valores provienen de métricas reales, pruebas de carga y una decisión clara sobre qué es más importante para cada carga de trabajo: densidad, aislamiento, latencia o costo. Revisa los valores después de cambios de tráfico, cambios de dependencia y actualizaciones de tiempo de ejecución. Las configuraciones de recursos obsoletas son una de las formas más silenciosas en que un clúster se desvía hacia un rendimiento deficiente.