Email copiado — support@tuurt.com
Cargando experiencia
kubernetes · 30 de septiembre de 2026 · 6 min

Liveness y readiness en Kubernetes: cuando la probe mata mas pods de los que salva

Una liveness probe que depende de la base de datos puede reiniciar pods sanos bajo carga. Vemos como separamos liveness de readiness y por que bajar el failureThreshold empeoro las cosas.

Por Equipo Tuurt

Liveness y readiness en Kubernetes: cuando la probe mata más pods de los que salva

La primera vez que configuramos probes en un deployment, la tentación casi siempre es la misma: copiar un ejemplo de la documentación, apuntar livenessProbe y readinessProbe al mismo endpoint de salud, ajustar un par de segundos, y darlo por resuelto. Funciona bien en desarrollo, donde el servicio nunca está bajo presión real. Falla en producción de una forma muy específica: bajo carga, el mismo pod que estaba procesando peticiones normalmente empieza a responder más lento, la liveness probe interpreta esa lentitud como un fallo, y el kubelet reinicia un contenedor que no estaba caído, solo ocupado. El reinicio no arregla nada porque no había nada roto; simplemente tira el trabajo en curso, vacía cachés en memoria y añade un pod menos a la capacidad disponible justo en el momento en que más se necesitaba.

Dos preguntas distintas que se contestan con el mismo endpoint

Liveness y readiness responden preguntas diferentes, aunque el reflejo natural sea tratarlas como una sola. La liveness probe contesta "¿este proceso sigue vivo y debería seguir corriendo, o está en un estado del que nunca se va a recuperar por sí solo?". La readiness probe contesta "¿este pod está en condiciones de recibir tráfico ahora mismo?". La primera pregunta, cuando la respuesta es no, tiene una sola solución posible: reiniciar el contenedor. La segunda, cuando la respuesta es no, tiene una solución mucho más barata: sacarlo temporalmente de los endpoints del Service hasta que se recupere, sin tocar el proceso.

El error que vemos con más frecuencia es una liveness probe que hace lo mismo que la readiness: golpear un endpoint que depende de la base de datos, de una cola, o de cualquier servicio externo.

# Configuración que castiga la lentitud como si fuera un cuelgue
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  periodSeconds: 5
  timeoutSeconds: 1
  failureThreshold: 2

Si /health consulta la base de datos y esta responde lenta porque hay una ráfaga de tráfico legítimo, la probe falla dos veces seguidas en diez segundos y el kubelet reinicia el contenedor. El proceso de la aplicación nunca dejó de responder: solo tardó más de lo que la probe estaba dispuesta a esperar. El reinicio no resuelve la lentitud de la base de datos, elimina un pod que sí estaba contribuyendo a atender la carga, y en un evento de tráfico alto puede desencadenar un ciclo donde cada reinicio reduce la capacidad, lo que aumenta la carga sobre los pods restantes, lo que hace que sus propias liveness probes empiecen a fallar también.

Qué debería mirar cada probe

La regla que usamos para separar responsabilidades es simple de enunciar y fácil de olvidar bajo presión de entrega: la liveness probe solo debe comprobar que el proceso en sí no está en un estado irrecuperable —un deadlock, un loop infinito, memoria corrupta que el propio proceso no puede limpiar— y no debe depender de ninguna llamada de red a un servicio externo. La readiness probe sí puede, y debe, verificar esas dependencias, porque su función es exactamente decidir si el pod puede atender tráfico dado el estado actual de sus dependencias.

livenessProbe:
  httpGet:
    path: /healthz/live
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /healthz/ready
    port: 8080
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 3

/healthz/live responde 200 mientras el proceso pueda aceptar y despachar una petición HTTP básica, sin tocar la base de datos ni ninguna dependencia externa. /healthz/ready sí consulta esas dependencias, y cuando alguna no responde, el pod sale de los endpoints del Service sin que el kubelet lo toque. Si la base de datos se recupera treinta segundos después, la siguiente ejecución de la readiness probe lo vuelve a marcar como listo y el tráfico vuelve solo, sin haber perdido el proceso, sus conexiones en caliente, ni el trabajo que tuviera en memoria.

El caso donde el arranque lento se confunde con estar caído

Un segundo patrón que produce el mismo síntoma —pods sanos reiniciados de forma innecesaria— aparece en el arranque, no en operación normal. Un servicio que tarda en precalentar una caché, cargar un modelo en memoria, o esperar una migración de esquema, puede tardar más en estar listo de lo que la liveness probe está dispuesta a tolerar antes de su primera comprobación, y termina reiniciado en un ciclo donde nunca llega a completar el arranque porque cada intento es interrumpido antes de terminar.

startupProbe existe específicamente para este caso: mientras la startup probe no reporte éxito, la liveness y la readiness probes no se ejecutan en absoluto, así que el proceso de arranque tiene el tiempo que necesite sin competir contra los umbrales pensados para operación en régimen normal.

startupProbe:
  httpGet:
    path: /healthz/live
    port: 8080
  periodSeconds: 5
  failureThreshold: 30
livenessProbe:
  httpGet:
    path: /healthz/live
    port: 8080
  periodSeconds: 10
  failureThreshold: 3

Con periodSeconds: 5 y failureThreshold: 30, el pod tiene hasta dos minutos y medio para completar el arranque antes de que Kubernetes lo dé por fallido. Pasado ese umbral, el control pasa a la liveness probe normal, que ya puede usar tiempos más ajustados porque el proceso está corriendo en régimen estable, no arrancando.

Lo que no funcionó: bajar el failureThreshold para "reaccionar más rápido"

En un servicio con latencias ocasionales bajo carga, la primera respuesta al ver reinicios de pods sanos no fue separar liveness de readiness, sino bajar failureThreshold en la liveness probe de tres a uno, con la idea de que un reinicio más rápido ante un problema real limitaría mejor el impacto. El resultado fue el opuesto: con un solo fallo tolerado, cualquier pico de latencia transitorio —una recolección de basura del runtime, un segundo de contención de CPU compartida en el nodo— bastaba para disparar un reinicio. Los reinicios se volvieron más frecuentes, no menos, y cada uno costaba más de lo que costaba la lentitud original que se quería evitar, porque un contenedor recién reiniciado tarda en volver a calentar sus propias conexiones y cachés, generando una nueva ventana de lentitud justo después del reinicio pensado para eliminarla.

Revertimos ese cambio y, en su lugar, movimos la comprobación de dependencias externas de la liveness a la readiness probe, que sí está diseñada para tolerar y comunicar ese tipo de degradación sin matar el proceso. Ajustar umbrales en la probe equivocada no soluciona un problema de diseño; solo cambia la velocidad a la que el problema se manifiesta.

Qué revisamos antes de dar por buena una configuración de probes

Antes de considerar cerrada la configuración de probes de un deployment, verificamos que la liveness probe no dependa de ninguna llamada de red a un servicio externo, que la readiness probe sí cubra esas dependencias y sea la única responsable de sacar un pod del balanceo, que exista una startup probe cuando el tiempo de arranque puede superar el periodSeconds × failureThreshold de la liveness, y que los umbrales de la liveness sean lo bastante generosos para absorber picos transitorios de latencia sin dejar de detectar un proceso realmente colgado. Cuando esas cuatro cosas están resueltas, un pico de tráfico deja de ser un evento que Kubernetes agrava por su cuenta, y vuelve a ser exactamente lo que debería ser: una señal para escalar horizontalmente, no una razón para reiniciar lo que ya estaba funcionando.

kubernetes devops probes sre
← Volver al blog