Introducción

Los controladores de Kubernetes son el corazón de la automatización del cluster. Su trabajo es simple: observar el estado actual, compararlo con el estado deseado y actuar para corregir diferencias. Sin embargo, cuando el caché interno de un controlador se desincroniza con la realidad del cluster, el resultado es impredecible. En producción, esto puede manifestarse como:

  • Un Deployment que no escala pods porque el controlador cree que ya hay suficientes (aunque el schedulador los haya eliminado por falta de recursos).
  • Un ReplicaSet que elimina pods críticos porque no detectó que el usuario los había marcado como «no terminables».
  • Un DaemonSet que no despliega nodos nuevos porque su caché no recibió el evento de creación de un Node.

En Kubernetes 1.36, el equipo de SIG API Machinery aborda esta problemática con dos frentes:

  1. Mitigación de obsolescencia: Mecanismos para evitar que los controladores actúen con datos desactualizados.
  2. Observabilidad: Métricas y herramientas para detectar y diagnosticar estos problemas antes de que escalen a incidentes en producción.

Estas mejoras no son teóricas: están implementadas en componentes críticos como kube-controller-manager y disponibles para cualquier desarrollador que use client-go.

Qué ocurrió

El problema de fondo: el caché de controladores y la ventana de obsolescencia

Los controladores de Kubernetes usan un patrón común:

  1. Informer: Escucha eventos del API Server (creaciones, actualizaciones, eliminaciones) y actualiza un caché local (store).
  2. Cola de trabajo: Los eventos se encolan para su procesamiento asincrónico.
  3. Reconciliación: El controlador lee el caché, compara con el estado deseado, y aplica cambios.

El riesgo aparece cuando el caché queda desincronizado:

  • Restart del controlador: Al reiniciarse, el informer debe reconstruir su caché haciendo un list inicial de todos los objetos relevantes. Durante este proceso (que puede durar segundos o minutos), el caché está vacío o incompleto.
  • Caídas del API Server: Si el etcd o el API Server fallan, el caché no se actualiza hasta que se restablece la comunicación.
  • Eventos fuera de orden: Si el API Server envía eventos en un orden distinto al real (por ejemplo, por latencia en la red o fallos intermitentes), el caché puede registrar un estado inconsistente.

En versiones anteriores a 1.36, estos escenarios podían llevar a que los controladores:

  • Ignoraran cambios críticos (por ejemplo, un Pod que se marca como Unschedulable pero el controlador no lo detecta porque su caché está desactualizada).
  • Tomaran acciones incorrectas (como eliminar pods que el usuario marcó como Terminating pero el caché no reflejó).

La solución en Kubernetes 1.36

El release 1.36 introduce dos componentes clave:

1. AtomicFIFO en client-go

El client-go (la biblioteca base para interactuar con el API Server) ahora soporta colas FIFO atómicas para procesar eventos en lotes. Esto evita inconsistencias cuando eventos llegan fuera de orden.

  • Versión afectada: client-go v0.32.0+ (incluido en Kubernetes 1.36).
  • Feature Gate: AtomicFIFO (activo por defecto).
  • Implementación: Las colas ahora usan un algoritmo que garantiza que, al procesar un lote de eventos (como los iniciales de un list), se apliquen en un orden consistente con el resourceVersion del API Server.

Ejemplo de cómo verificar la versión actual del caché:

import (
    "k8s.io/client-go/tools/cache"
)

store := informer.GetStore()
latestRV := store.LastStoreSyncResourceVersion()
fmt.Printf("Última versión de recurso sincronizada: %s\n", latestRV)

2. Consistencia de controladores en kube-controller-manager

Cuatro controladores críticos ahora incorporan lógica para evitar acciones basadas en datos obsoletos:

ControladorFeature GateDescripción
BLOCK13BLOCK14Verifica si el caché está al día antes de escalar pods.
BLOCK15BLOCK16Evita escalar/desescalar si el caché de pods está desincronizado.
BLOCK17BLOCK18Detecta si el estado de pods en el caché coincide con el *API Server* antes de marcar un *Job* como completado.
BLOCK19BLOCK20Similar al *ReplicaSet*, pero para el controlador legacy.
Cómo funciona:
  1. Antes de actuar, el controlador compara:
resourceVersion del objeto en el caché (storeRV).

resourceVersion del último write hecho por el controlador (writtenRV).

  1. Si storeRV < writtenRV, el controlador omite la reconciliación y registra el evento en stale_sync_skips_total.

Ejemplo de métrica expuesta:

# Consulta a la API de métricas de kube-controller-manager
curl -s http://localhost:10252/metrics | grep stale_sync_skips_total
# Salida:
# stale_sync_skips_total{controller="daemonset",subsystem="daemonset"} 42

3. Métricas de observabilidad

Kubernetes 1.36 expone tres métricas alpha para monitorear la obsolescencia:

MétricaDescripciónEjemplo de uso
BLOCK27Cantidad de reconciliaciones omitidas por datos obsoletos.Alertar si crece en un *Deployment*.
BLOCK28Último *resourceVersion* sincronizado por cada *informer*.Comparar con BLOCK29.
BLOCK30 (nuevo)Tiempo transcurrido desde que el *informer* recibió el último evento.Identificar *informers* lentos.
Cómo habilitarlas:
# En los argumentos de kube-controller-manager
--feature-gates=StaleControllerConsistencyDaemonSet=true,StaleControllerConsistencyReplicaSet=true
--controllers=daemonset,replicaset,job,replicationcontroller

Impacto para DevOps / Infraestructura / Cloud / Seguridad

DevOps y SRE

  • Estabilidad operativa: Los equipos dejarán de ver comportamientos erráticos en controladores bajo alta carga (ej.: Deployment que no escala en clusters con >1000 pods).
  • Reducción de incidentes: Según datos internos de Kubernetes SIG API Machinery, en clusters con alta contención (ej.: DaemonSets desplegando en nodos de tipo GPU), la métrica stale_sync_skips_total puede alcanzar hasta un 15% de las reconciliaciones en versiones pre-1.36. Con las mejoras, este valor se reduce a ~0% en escenarios controlados.

Infraestructura y Cloud

  • Clusters grandes: En entornos con API Server bajo presión (ej.: 1000+ Pods por Namespace), los informers que usan AtomicFIFO reducen la ventana de obsolescencia de ~30 segundos (en versiones anteriores) a <5 segundos (en 1.36).
  • Alta disponibilidad: Los feature gates permiten deshabilitar la consistencia por controlador (ej.: si un equipo prefiere el comportamiento legacy de un Job en su entorno).

Seguridad

  • Consistencia de datos: Los controladores ya no actuarán con información desactualizada, lo que reduce riesgos como:
– Eliminación de pods críticos por un ReplicaSet que no detectó un cambio en el PodDisruptionBudget.

– Escalados incorrectos en Deployments afectados por un HorizontalPodAutoscaler con datos obsoletos.

  • Auditoría: Las métricas stale_sync_skips_total permiten detectar drift entre el estado del cluster y la lógica de los controladores, facilitando forensic post-incidente.

Detalles técnicos

Componentes afectados y versiones

ComponenteVersión mínimaCambios clave
BLOCK341.36.0Nuevos *feature gates* para consistencia en 4 controladores.
BLOCK35v0.32.0Colas FIFO atómicas y BLOCK36.
BLOCK371.36.0Compatible con los nuevos *feature gates*; no requiere cambios.
BLOCK381.36.0No afectado directamente, pero se beneficia de controladores más consistentes.
### Vectores de ataque o falla cubiertos
  1. Restart de controladores:
Antes: El controlador podía actuar con un caché vacío durante minutos.

Ahora: El informer usa AtomicFIFO para procesar el list inicial en orden consistente.

  1. Latencia en el API Server:
Antes: Eventos fuera de orden podían llevar a estados inconsistentes (ej.: un Pod marcado como Running antes de que su PodStatus se actualizara).

Ahora: El ConsistencyStore verifica que storeRV >= writtenRV antes de actuar.

  1. Escalabilidad:
Antes: En clusters con >5000 objetos, el API Server podía tardar hasta 45 segundos en enviar el list inicial a un informer (tiempo de obsolescencia).

Ahora: Con AtomicFIFO, este tiempo se reduce a <10 segundos en la mayoría de los casos.

Ejemplo práctico: ReplicaSet

  1. Un usuario actualiza un ReplicaSet para escalar de 3 a 5 pods.
  2. El controlador recibe el evento y lo encola.
  3. Antes de 1.36:
– El informer procesa el evento de escalado antes de recibir los eventos de los Pods.

– El controlador escala a 5 pods, pero el API Server aún reporta solo 3 pods creados.

– Resultado: El controlador escala a 8 pods (5 nuevos + 3 antiguos).

  1. Con 1.36:
– El controlador verifica storeRV < writtenRV (el ReplicaSet tiene un resourceVersion mayor en el caché que en el API Server).

– Omite la reconciliación y registra stale_sync_skips_total{controller="replicaset"} 1.

– Espera a que el informer sincronice los eventos de los Pods antes de actuar.

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

1. Actualizar a Kubernetes 1.36.0

# Usando kubeadm (ejemplo para clusters en Ubuntu 22.04)
sudo apt update && sudo apt install -y kubelet=1.36.0-00 kubeadm=1.36.0-00 kubectl=1.36.0-00
sudo systemctl daemon-reload
sudo systemctl restart kubelet
Verificación:
kubectl version --short | grep Server
# Kubernetes v1.36.0

2. Habilitar los feature gates en kube-controller-manager

Editar el manifest de kube-controller-manager (ejemplo para systemd):

# /etc/kubernetes/manifests/kube-controller-manager.yaml
spec:
  containers:
  - command:
    - kube-controller-manager
    - --feature-gates=StaleControllerConsistencyDaemonSet=true,StaleControllerConsistencyReplicaSet=true,StaleControllerConsistencyJob=true,StaleControllerConsistencyRC=true
    - --controllers=*,bootstrapsigner,tokencleaner,daemonset,replicaset,job,replicationcontroller
Reiniciar el pod:
kubectl -n kube-system delete pod kube-controller-manager-<nodo>

3. Monitorear las nuevas métricas

Configurar Prometheus para alertar sobre obsolescencia:

# rules.yml
groups:
- name: kubernetes.staleness
  rules:
  - alert: ControllerStalenessDetected
    expr: increase(stale_sync_skips_total[5m]) > 0
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Controlador {{ $labels.controller }} detectó obsolescencia en el caché"
      description: "El controlador {{ $labels.controller }} omitió {{ $value }} reconciliaciones por datos obsoletos."

4. Actualizar código de controladores personalizados

Si desarrollas controladores con client-go:

import (
    "k8s.io/client-go/tools/cache"
    "k8s.io/apimachinery/pkg/runtime"
)

// Usar ConsistencyStore para evitar acciones con datos obsoletos
store := informer.GetStore()
consistencyStore := cache.NewConsistencyStore(store)

// Antes de reconciliar, verificar si el caché está al día
objMeta, exists, err := consistencyStore.GetByKey(key)
if err != nil {
    return err
}
if !exists {
    return fmt.Errorf("objeto no encontrado")
}

// Comparar resourceVersion
if consistencyStore.IsStale(objMeta) {
    log.Info("Caché desactualizado, omitiendo reconciliación", "resourceVersion", objMeta.GetResourceVersion())
    return nil
}

5. Validar en entornos de staging

  1. Simular alta contención:
   # Crear 1000 pods en un Namespace
   seq 1 1000 | xargs -I {} kubectl run busybox-{} --image=busybox --restart=Never -- sleep 3600
   
  1. Forzar un restart del controlador:
   kubectl -n kube-system delete pod kube-controller-manager-<nodo>
   
  1. Verificar métricas:
   kubectl -n kube-system exec -it kube-controller-manager-<nodo> -- curl -s localhost:10252/metrics | grep stale_sync_skips_total
   

Conclusión

Kubernetes 1.36 aborda un problema sistémico pero poco visible: la obsolescencia en el caché de los controladores. Las mejoras en client-go (colas atómicas) y kube-controller-manager (consistencia por feature gate) reducen riesgos operativos en clusters grandes y de alta contención. Además, las nuevas métricas permiten detectar estos problemas antes de que escalen a incidentes.

Recomendación final:
  1. Actualizar a Kubernetes 1.36.0 en entornos de producción antes de que la carga aumente (ej.: antes de un evento de Black Friday).
  2. Habilitar los feature gates en controladores críticos (DaemonSet, ReplicaSet).
  3. Configurar alertas basadas en stale_sync_skips_total y store_resource_version.

Estas mejoras no son panaceas, pero reducen la ventana de error en un orden de magnitud. En clusters donde un Pod mal escalado puede costar miles de dólares en recursos desperdiciados o SLA violados, la consistencia de datos ya no es un lujo: es una necesidad operativa.

FIN

Deja una respuesta

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