Cloud · Kubernetes|12 min de lectura|

Cómo Reducir Downtime en Kubernetes

Kubernetes promete alta disponibilidad por diseño, pero la realidad operacional es más compleja. La mayoría de los incidentes de producción no vienen del plano de control ni de fallas de etcd — vienen de configuraciones incorrectas en los Deployments que se ven bien en staging pero explotan bajo carga real o durante una actualización. Esta guía documenta las prácticas que aplicamos en proyectos reales para llevar clusters a SLOs de 99.9% o superiores.

El downtime en Kubernetes no ocurre donde esperas

Antes de hablar de soluciones, hay que nombrar las causas reales. Hemos documentado las fuentes de incidentes en clusters de producción que atendemos: el 62% de los eventos de degradación de servicio se originan en configuraciones incorrectas de Deployments (probes, resource limits, rolling update strategy). Solo el 15% tiene origen en fallas de infraestructura subyacente.

Los problemas más comunes: rolling updates que dejan el servicio sin réplicas disponibles, pods que entran en CrashLoopBackOff durante deploys por probes demasiado agresivas, y nodos sobrecomprometidos que activan el OOM Killer del sistema operativo en el peor momento posible.

Pod Disruption Budgets: la primera línea de defensa

El error más crítico que encontramos en clusters de producción es ejecutar rolling updates o mantenimiento de nodos sin definir PodDisruptionBudgets (PDB). Sin un PDB, Kubernetes puede terminar tantos pods simultáneamente como necesite durante una operación de drain o un rolling update, dejando el servicio sin réplicas disponibles.

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
  namespace: production
spec:
  minAvailable: 2   # siempre mínimo 2 pods activos
  selector:
    matchLabels:
      app: api

La regla operacional: todo Deployment con más de una réplica que reciba tráfico de producción debe tener un PDB. Sin excepción. Con 3 réplicas y sin PDB, el Cluster Autoscaler puede drenar 2 nodos simultáneamente y dejar una sola réplica activa — exactamente cuando no quieres eso.

El PDB protege contra disrupciones voluntarias (drain de nodo, rolling update, Cluster Autoscaler), no contra fallas involuntarias de hardware. Para resiliencia total, necesitas también anti-affinity y distribución multizona.

Rolling Update Strategy: maxSurge y maxUnavailable importan

La configuración default de rolling update en Kubernetes (maxUnavailable: 25%, maxSurge: 25%) puede dejar tu servicio con solo el 75% de capacidad durante un update. Para un Deployment con 4 réplicas y tráfico alto, eso puede ser suficiente para saturar las réplicas restantes.

yaml
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # nunca reducir réplicas durante el update

Con maxUnavailable: 0, Kubernetes primero levanta el pod nuevo (maxSurge: 1), espera que pase la readiness probe y solo entonces termina el pod antiguo. Esto garantiza que durante el update siempre tenemos al menos el número de réplicas deseadas.

Readiness y Liveness Probes: configuración que determina el uptime

Las probes mal configuradas son el killer silencioso del uptime. El escenario más frecuente: initialDelaySeconds demasiado bajo para la aplicación, la readiness probe pasa antes de que el pool de conexiones a la base de datos esté listo, el pod recibe tráfico, el primer request real falla, y si la liveness probe es también agresiva, el proceso entra en CrashLoopBackOff.

El /health/ready y /health/live deben ser endpoints semánticamente distintos. Ready verifica si el pod puede servir tráfico ahora (conexión a DB activa, dependencias disponibles). Live solo verifica si el proceso está vivo y sin deadlock.

yaml
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 10
  failureThreshold: 3
  successThreshold: 1
livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 20
  failureThreshold: 4
La liveness probe debe ser conservadora con failureThreshold. Un pod que mata su proceso durante un pico de latencia momentáneo, y luego hace cold start completo, genera más downtime que uno que responde lento por unos segundos.

Resource Requests y Limits: el equilibrio entre throttling y OOM

Sin resource requests definidos, el scheduler no tiene información para colocar pods óptimamente. Sin limits, un pod puede consumir toda la memoria del nodo y activar el OOM Killer del OS — que no distingue entre tu aplicación y componentes del sistema Kubernetes.

La estrategia que usamos en producción: medir durante al menos dos semanas con container_memory_working_set_bytes y container_cpu_usage_seconds_total en Prometheus. Requests = p50. Limits de memoria = p99.5 con 20% de margen.

yaml
resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"
    cpu: "500m"

Advertencia: limits de CPU agresivos generan throttling silencioso que se manifiesta como latencia elevada sin errores visibles. Monitorear container_cpu_cfs_throttled_seconds_total para detectarlo antes de que afecte SLAs.

Anti-Affinity y Distribución Multizona

Tres réplicas en el mismo nodo es alta disponibilidad aparente pero single point of failure real. Si el nodo falla, pierdes el 100% de la capacidad. Con topologySpreadConstraints puedes distribuir pods entre nodos y zonas de disponibilidad simultáneamente.

yaml
spec:
  template:
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: api
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: api

topologySpreadConstraints es más flexible que podAntiAffinity. maxSkew: 1 significa que la diferencia máxima entre el número de pods en cualquier topología no puede superar 1. whenUnsatisfiable: DoNotSchedule hace que el pod espere hasta poder ubicarse en una topología equilibrada.

Graceful Shutdown: el cierre limpio que nadie configura

Cuando Kubernetes termina un pod, envía SIGTERM. La aplicación tiene terminationGracePeriodSeconds para cerrarse limpiamente. Si no maneja SIGTERM correctamente, las requests en vuelo se cortan abruptamente. El problema menos obvio: entre que Kubernetes envía SIGTERM y que el load balancer deja de enviar tráfico al pod, pueden pasar varios segundos.

yaml
spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: api
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 10"]

El preStop con sleep 10 le da tiempo al kube-proxy para actualizar las iptables rules y dejar de enrutar tráfico al pod antes de que inicie el shutdown. Este patrón es crítico en servicios con requests de larga duración o conexiones WebSocket persistentes.

Monitoreo Proactivo: alertar antes del downtime

Las alertas que se disparan cuando ya hay CrashLoopBackOff son demasiado tardías. Para un SLO de 99.9%, la detección de degradación debe ocurrir antes del outage. Las alertas de Prometheus/Alertmanager que activamos en producción:

  • PodRestartRateHigh — más de 3 restarts en 15 minutos (alerta antes del CrashLoopBackOff oficial de Kubernetes)
  • DeploymentReplicasMismatch — réplicas disponibles < deseadas por más de 5 minutos
  • ContainerCPUThrottling > 30% — throttling silencioso antes de que afecte latencia medible
  • PodPendingTooLong — pod en Pending más de 5 minutos (señal de problema de scheduling o capacity)
  • NodeNotReady — nodo no disponible (permite acción preventiva antes de que el scheduler redistribuya pods)

Preguntas frecuentes

¿Cuántas réplicas mínimas necesito para zero downtime en Kubernetes?
Mínimo 3 réplicas distribuidas en nodos distintos, combinadas con PDB de minAvailable: 2. Con 2 réplicas, durante una disrupción planificada solo queda 1 activa — cualquier fallo adicional genera downtime. Con 3 réplicas y PDB correcto, siempre tienes margen de seguridad incluso con un nodo en mantenimiento.
¿Qué hace exactamente el Pod Disruption Budget?
El PDB limita cuántos pods de un Deployment pueden estar no disponibles simultáneamente durante disrupciones voluntarias: drain de nodo, rolling updates, Cluster Autoscaler. No protege contra fallos involuntarios de hardware. Es la pieza que conecta la alta disponibilidad teórica con la operacional.
¿Por qué mis pods entran en CrashLoopBackOff después de un rollout?
Las causas más frecuentes en orden de probabilidad: (1) readiness probe demasiado agresiva que mata el pod durante startup, (2) insuficiente memoria disponible (OOM en cold start), (3) error de configuración en variables de entorno o ConfigMaps, (4) imagen Docker incorrecta o con bug de regresión. Siempre comenzar con kubectl describe pod <name> y kubectl logs <name> --previous para ver el estado antes del crash.
¿Cuándo usar HPA vs. KEDA para autoescalado?
HPA es suficiente para servicios donde la carga se correlaciona con CPU/memoria. KEDA es necesario cuando el trabajo está en colas (Kafka, SQS, RabbitMQ) o cuando la carga no se refleja directamente en métricas de sistema. Para APIs HTTP, HPA con target de CPU del 60-70% funciona bien. Para workers de procesamiento batch o event-driven, KEDA con métricas de cola da resultados significativamente mejores.
¿Cómo detecto si tengo CPU throttling en mis pods?
Consulta la métrica container_cpu_cfs_throttled_seconds_total en Prometheus. Si el ratio entre throttled_seconds y total_seconds supera el 25% de forma consistente, los limits de CPU son demasiado bajos para el workload. Este throttling genera latencia de percentil alto (p99) sin afectar el p50, lo que hace que sea difícil de detectar sin las métricas correctas.

¿Tienes un cluster de Kubernetes en producción con incidentes recurrentes? Nuestro equipo puede hacer un audit técnico e identificar los riesgos específicos de tu infraestructura.

Habla con el equipo

Artículos relacionados

IQS

Equipo de Ingeniería — IQS

Ingenieros de software, cloud y DevOps con experiencia en proyectos empresariales.