Introducción

Preguntale a cualquier SRE cuál es el tamaño correcto para un servicio como checkout-api y vas a obtener respuestas contradictorias. Mientras los valores de Helm declaran replicas: 3, cpu: 1000m y memory: 2Gi, el estado real del clúster cuenta una historia distinta. El HPA escaló la aplicación a 11 réplicas debido a la carga de CPU; el VPA insiste en que está sobreaprovisionada, recomendando cpu: 350m; KEDA exige 24 réplicas por un pico de latencia en Kafka; y Karpenter ha provisionado dos nodos nuevos basándose en las solicitudes declaradas, no en el uso real.

Este escenario no es una anomalía, es el resultado arquitectónico esperado de utilizar herramientas modulares desacopladas. Kubernetes, siguiendo la filosofía Unix, ofrece componentes que hacen una cosa bien. Pero a diferencia de herramientas como grep o awk que procesan flujos secuenciales, los autoscalers ejecutan bucles de reconciliación concurrentes sobre los mismos campos de la API (spec.replicas, resources.requests). La falta de visibilidad cruzada entre estos controladores transforma la eficiencia modular en inestabilidad sistémica.

Qué ocurrió

El problema central radica en que el dimensionamiento de la carga de trabajo es una única ecuación matemática acoplada, no un conjunto de variables independientes. Cuando los equipos operacionales despliegan HPA (Horizontal Pod Autoscaler), VPA (Vertical Pod Autoscaler), KEDA y autoscalers de nodos como Karpenter o Cluster Autoscaler, cada uno asume un control exclusivo sobre su respectiva variable objetivo sin considerar las mutaciones de los demás.

Estos controladores actúan como reflejos biológicos: miden una señal de telemetría aislada y disparan una respuesta predefinida. El HPA reacciona al porcentaje de CPU, el VPA al uso histórico de memoria, KEDA a la profundidad de la cola de eventos y Karpenter a las solicitudes de recursos pendientes. Al no existir un «sistema nervioso central» que coordine estas acciones, los bucles de retroalimentación se desestabilizan. El resultado es un estado de thrashing, donde el sistema oscila violentamente entre estados de sobre- y sub-provisionamiento, degradando el rendimiento y aumentando los costos sin una lógica unificada de optimización.

Impacto para DevOps / Infraestructura / Cloud / Seguridad

La consecuencia operativa más directa es la degradación de los SLOs (Service Level Objectives) durante picos de tráfico. En entornos con GitOps (como Argo CD o Flux), la lucha entre autoscalers crea conflictos de estado. Si el HPA escala un despliegue a 20 réplicas para manejar una carga súbita, Argo CD puede marcar la aplicación como OutOfSync porque la definición en Git especifica 3 réplicas. Si la sincronización automática está habilitada, el motor de GitOps revierte el cambio, eliminando la capacidad crítica justo cuando el sistema más la necesita.

Desde la perspectiva de costos, el impacto es severo. Los autoscalers de nodos como Karpenter toman decisiones de colocación basándose estrictamente en las requests declaradas en los contenedores, no en el consumo real. Si un contenedor solicita 1000m de CPU pero solo consume 150m, el planificador reserva el núcleo completo. Esto multiplica la ineficiencia de dimensionamiento hacia la infraestructura física o virtualizada subyacente. Según datos reportados en flotas de producción, la utilización promedio de CPU en clústeres Kubernetes oscila alrededor del 8%, a pesar de un aprovisionamiento agresivo de nodos, generando desperdicios financieros significativos en proveedores como AWS, Azure o GCP.

Detalles técnicos

El mecanismo de fallo se manifiesta en cuatro modos de falla comunes derivados de la concurrencia de controladores:

  • Espiral de muerte HPA/VPA: El HPA calcula las réplicas objetivo utilizando el uso de CPU como porcentaje de las requests declaradas. Si el VPA reduce las requests de CPU para eliminar el desperdicio, el denominador de la fórmula del HPA disminuye. Un incremento menor en el tráfico puede superar el umbral de porcentaje de CPU, disparando una escalada horizontal agresiva. Al distribuirse el tráfico entre más pods, el uso por pod cae, lo que lleva al VPA a reducir aún más las requests, creando un ciclo infinito de contenedores microscópicos.
  • Conflicto KEDA/HPA: KEDA escala basándose en métricas externas (como lag de Kafka) mientras que el HPA observa métricas internas (CPU/Memory). Si los pods son intensivos en I/O pero ligeros en CPU, el HPA puede interpretar el bajo uso de CPU como sobreaprovisionamiento y escalar hacia abajo, contrarrestando la escalada hacia arriba de KEDA. El API server registra eventos de cambio alternados mientras el throughput real se estanca.
  • Inversión de GitOps: Los motores GitOps consideran el repositorio como la fuente de verdad. Las mutaciones dinámicas de spec.replicas realizadas por autoscalers rompen esta premisa. Ignorar las diferencias de réplicas en la configuración de Argo CD (ignoreDifferences) soluciona el síntoma pero oculta el problema: Git deja de reflejar la realidad de producción, y los despliegues futuros pueden restaurar silenciosamente bases obsoletas.
  • Desajuste de Karpenter/Nodos: Karpenter evalúa la necesidad de nuevos nodos basándose en las requests acumuladas de los pods pendientes. Si los autoscalers de pods han inflado artificialmente el número de réplicas con requests conservadoras pero bajo uso real, Karpenter provisionará capacidad física innecesaria para satisfacer una demanda fantasma.
  • Para diagnosticar la lucha entre autoscalers, los ingenieros pueden monitorear la frecuencia de eventos de HorizontalPodAutoscaler y VerticalPodAutoscaler en los logs del API server, o usar herramientas de observabilidad como Prometheus para correlacionar métricas de kube_horizontalpodautoscaler_status_desired_replicas con kube_verticalpodautoscaler_recommendation.

    Qué deberían hacer los administradores y equipos técnicos

    La solución inmediata no es eliminar herramientas, sino establecer jerarquías explícitas y desactivar conflictos conocidos.

  • Desactivar VPA en modo Auto para recursos críticos: A menos que tengas una estrategia de re-despliegue automatizada muy robusta, configura VPA en modo Recommend o Off para servicios que ya usan HPA. Esto previene la espiral de muerte de requests.
  • Configurar exclusiones en GitOps: Si usas Argo CD, configura ignoreDifferences para spec.replicas en los objetos que gestionan autoscalers. Sin embargo, documenta esto como una deuda técnica: tu repositorio Git ya no es la fuente de verdad para la capacidad.
  • ignoreDifferences:
    – group: apps
    kind: Deployment
    jsonPointers:
    – /spec/replicas

  • Unificar estrategias de escalado: Para cargas de trabajo con picos impredecibles y dependientes de colas, considera reemplazar HPA por KEDA y desactivar HPA para esos despliegues específicos. Evita tener ambos activos simultáneamente gestionando el mismo subrecurso /scale.
  • Ajustar requests basadas en uso real: Revisa periódicamente los informes de VPA (Recommend) y actualiza los valores de Helm o Kustomize manualmente si la desviación entre request y uso real supera el 30-40%. Esto alivia la presión sobre los autoscalers de nodos.
  • Evaluar plataformas de dimensionamiento autónomo: A largo plazo, considera soluciones que ofrezcan una capa de decisión unificada (como Azure AKS Vertical Pod Autoscaler con integración nativa, o herramientas de terceros como Spot.io o Cast AI) que coordinen las decisiones de HPA, VPA y escala de nodos en un único algoritmo de optimización de costo-rendimiento.
  • Conclusión

    La automatización en Kubernetes no es lo mismo que la autonomía. Hoy en día, nuestros clústeres poseen actuadores eficientes (HPA, VPA, Karpenter) pero carecen de una inteligencia centralizada que entienda el panorama completo. La arquitectura actual fuerza a los equipos de SRE a actuar como coordinadores humanos de bucles de control que no se hablan entre sí.

    El futuro del dimensionamiento de cargas de trabajo en la nube exige pasar de la automatización reactiva a la autonomía cerrada. Necesitamos sistemas que no solo ejecuten mutaciones de API, sino que comprendan los compromisos entre rendimiento, costo y riesgo, asumiendo la responsabilidad de fin a fin. Hasta que existan estas capas de orquestación inteligente nativas, la mitigación depende de una configuración consciente de los conflictos y una observabilidad profunda de las interacciones entre controladores.

    Fuentes

    https://thenewstack.io/why-kubernetes-autoscalers-fight/

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *