Kubernetes Troubleshooting: Health Checks, Readiness, Liveness y Startup Probes

Aprende a solucionar problemas con readiness, liveness y startup probes en Kubernetes, incluyendo timeouts, restart loops, health checks y cascading failures.

Un Pod puede estar en estado Running y aun así no estar disponible para recibir tráfico.

También puede estar ejecutando una aplicación completamente funcional y comenzar a reiniciarse porque un health check está mal configurado.

En Kubernetes existen tres tipos principales de probes:

Startup
Liveness
Readiness

Aunque su configuración es similar, responden preguntas completamente diferentes.

Startup
|
v
¿La aplicación ya terminó de iniciar?
Liveness
|
v
¿La aplicación sigue funcionando
o necesita ser reiniciada?
Readiness
|
v
¿La aplicación está preparada
para recibir tráfico?

Confundir estas responsabilidades puede transformar una falla pequeña en un incidente mucho mayor.

En este cuarto artículo de nuestra serie de Kubernetes Troubleshooting vamos a investigar:

  • readiness probes;

  • liveness probes;

  • startup probes;

  • HTTP, TCP, exec y gRPC probes;

  • timeouts y thresholds;

  • Pods Running pero NotReady;

  • restart loops provocados por probes;

  • aplicaciones que tardan demasiado en iniciar;

  • dependencias externas en health checks;

  • cascading failures;

  • cómo diseñar probes que ayuden en lugar de empeorar un incidente.


Primero: las tres probes no hacen lo mismo

Esta es la diferencia más importante de todo el artículo.

Container
|
+----------------+----------------+
| | |
v v v
Startup Liveness Readiness
| | |
¿Terminó de ¿Sigue vivo? ¿Puede recibir
iniciar? tráfico?
| | |
FAIL FAIL FAIL
| | |
v v v
Restart Restart Remove from
normal Service
traffic

Una readiness failure no debería reiniciar el contenedor.

Una liveness failure sí puede provocar que el kubelet termine el contenedor y aplique su restart policy.

Una startup probe protege aplicaciones que necesitan tiempo adicional para iniciar.


1. Readiness probe

La readiness probe responde:

¿Este contenedor está preparado para recibir tráfico ahora?

Supongamos que tenemos tres Pods:

Service
|
+--------+--------+
| | |
v v v
Pod A Pod B Pod C
Ready Ready NotReady

El tráfico normal del Service debe dirigirse hacia los endpoints considerados ready.

Pod C puede continuar:

Running

pero no estar:

Ready

Esto es completamente válido.


Un ejemplo de readiness

readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3

Conceptualmente:

GET /ready
|
+--- Success
| |
| v
| Ready
|
+--- Failure
|
v
NotReady
|
v
No normal Service traffic

El proceso continúa ejecutándose.


Pod Running pero 0/1 Ready

Un escenario común:

kubectl get pods -n production

muestra:

NAME READY STATUS RESTARTS
api-7bd95fcb85-rx7mw 0/1 Running 0

Esto nos dice algo importante:

Container
|
+--- Running
|
+--- Not Ready

Comenzamos con:

kubectl describe pod \
api-7bd95fcb85-rx7mw \
-n production

Busca Events como:

Readiness probe failed:
HTTP probe failed with statuscode: 503

o:

dial tcp 10.244.2.15:8080:
connect: connection refused

¿Qué está comprobando realmente la readiness probe?

Revisa el workload:

kubectl get deployment api \
-n production \
-o yaml

Busca:

readinessProbe:

Y responde:

¿Path correcto?
¿Port correcto?
¿Protocol correcto?
¿La aplicación ya escucha?
¿El timeout es suficiente?
¿La respuesta HTTP es válida?
¿El endpoint depende de otros sistemas?

También podemos probar el endpoint manualmente.

Desde el Pod:

kubectl exec \
-n production \
<pod> -- \
curl -v http://127.0.0.1:8080/ready

Si la imagen no contiene curl, podemos utilizar un debugging container o Pod.


Readiness y EndpointSlices

Cuando un Pod deja de estar ready, Kubernetes refleja este estado en los backends utilizados por Services.

Podemos investigarlo:

kubectl get endpointslices \
-n production \
-l kubernetes.io/service-name=api-service \
-o yaml

Busca condiciones como:

conditions:
ready: false

Esto conecta dos partes importantes de nuestra serie:

Readiness Probe
|
v
Pod NotReady
|
v
EndpointSlice condition changes
|
v
Normal Service traffic stops

2. Liveness probe

La liveness probe responde una pregunta diferente:

¿La aplicación sigue suficientemente viva como para continuar ejecutándose?

Un ejemplo:

livenessProbe:
httpGet:
path: /live
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3

Si falla repetidamente hasta alcanzar failureThreshold:

Liveness fails
|
v
Failure threshold reached
|
v
kubelet stops container
|
v
Restart policy applies
|
v
Container starts again

Esto puede ser útil para aplicaciones que entran en estados irrecuperables, como determinados deadlocks.

Pero también puede ser peligroso.


Cuando liveness se convierte en el problema

Imaginemos que una aplicación está bajo carga elevada.

Normalmente:

/ live -> 20 ms

Pero durante el pico:

/live -> 1.5 seconds

Nuestra configuración tiene:

timeoutSeconds: 1
failureThreshold: 3

Entonces:

Traffic increases
|
v
Application slows
|
v
Liveness times out
|
v
Pod restarted
|
v
Less capacity
|
v
Remaining Pods receive more traffic
|
v
They become slower
|
v
More liveness failures
|
v
More restarts

Acabamos de crear una cascading failure.

La aplicación estaba degradada.

La configuración de health checks convirtió la degradación en una pérdida progresiva de capacidad.


3. Startup probe

La startup probe existe especialmente para aplicaciones que necesitan tiempo significativo para inicializarse.

Por ejemplo:

Container starts
|
v
Load configuration
|
v
Initialize runtime
|
v
Warm caches
|
v
Application ready

Esto podría tardar:

90 seconds

Si una liveness probe comienza demasiado pronto, Kubernetes podría matar el proceso mientras todavía está iniciando.


Sin startup probe

Container starts
|
v
Application starting...
|
v
Liveness begins
|
v
FAIL
|
v
Restart
|
v
Application starting...
|
v
FAIL

Resultado:

CrashLoopBackOff

aunque la aplicación podría haber iniciado correctamente si hubiera tenido tiempo suficiente.


Con startup probe

startupProbe:
httpGet:
path: /startup
port: 8080
periodSeconds: 10
failureThreshold: 30

Aquí tenemos aproximadamente:

10 seconds × 30 failures = 300 seconds

de ventana antes de considerar que startup falló.

Mientras la startup probe no haya tenido éxito:

Startup probe
|
+--- checking
|
Liveness disabled
Readiness disabled

Una vez que startup pasa:

Startup succeeds
|
+------------+
| |
v v
Liveness Readiness
begins begins

initialDelaySeconds vs startupProbe

Podríamos simplemente configurar:

livenessProbe:
initialDelaySeconds: 120

pero una startup probe expresa mejor nuestra intención.

Con un delay fijo:

Always wait 120 seconds

Con startupProbe:

Check startup
|
+--- ready after 20 seconds
| |
| v
| continue immediately
|
+--- still starting
|
v
keep checking

Esto permite proteger aplicaciones lentas sin necesariamente esperar el máximo en cada inicio.


4. Investigar restart loops provocados por probes

Supongamos:

kubectl get pods -n production

muestra:

NAME READY STATUS RESTARTS
api-6b8cff746c-r27ft 0/1 CrashLoopBackOff 8

No debemos asumir inmediatamente que la aplicación está haciendo crash por sí sola.

Ejecutamos:

kubectl describe pod \
api-6b8cff746c-r27ft \
-n production

y encontramos:

Warning Unhealthy
Liveness probe failed

Ahora nuestra hipótesis cambia.

Revisa:

kubectl logs \
api-6b8cff746c-r27ft \
-n production \
--previous

y la configuración:

kubectl get deployment api \
-n production \
-o yaml

Investiga la timeline

Queremos entender:

Container starts
|
v
When does probe start?
|
v
How long does app need?
|
v
How often does probe run?
|
v
How long until failure?
|
v
Why does endpoint fail?

Una configuración como:

livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 3
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 2

puede ser extremadamente agresiva para determinadas aplicaciones.


5. Los parámetros que debemos entender

initialDelaySeconds

Tiempo antes de comenzar determinadas probes después de iniciar el contenedor.

initialDelaySeconds: 10

No debería utilizarse como sustituto automático de una startup probe para workloads con tiempos de arranque variables.


periodSeconds

Frecuencia de ejecución.

periodSeconds: 10

Una frecuencia demasiado agresiva puede generar overhead innecesario.


timeoutSeconds

Cuánto esperamos por el resultado de una probe.

timeoutSeconds: 2

Un timeout demasiado pequeño puede generar falsos negativos durante períodos de carga.


failureThreshold

Número de fallas consecutivas necesarias para considerar que la probe falló.

failureThreshold: 3

Con:

periodSeconds = 10
failureThreshold = 3

el fallo no ocurre simplemente después de exactamente 30 segundos en todos los casos, pero esos valores nos ayudan a entender la tolerancia aproximada de la configuración.


successThreshold

Número de éxitos consecutivos requeridos para volver al estado exitoso.

Para readiness puede ser mayor que uno.

Para liveness y startup debe permanecer en:

1

6. HTTP probes

Una HTTP probe puede verse así:

readinessProbe:
httpGet:
path: /ready
port: 8080
scheme: HTTP

Es útil cuando nuestra aplicación expone health endpoints específicos.

Podemos reproducir la comprobación:

curl -v http://<pod-ip>:8080/ready

Investiga:

HTTP status
response time
connection errors
TLS
redirect behavior
application logs

7. TCP probes

Una TCP probe comprueba si puede establecerse una conexión al puerto.

livenessProbe:
tcpSocket:
port: 8080

Conceptualmente:

Can kubelet establish TCP connection?
|
+----+----+
| |
YES NO

Pero esto no significa necesariamente que la aplicación esté funcionando correctamente a nivel lógico.

Un proceso puede aceptar TCP:

TCP connection -> OK

pero estar devolviendo:

HTTP 500

a todas las peticiones.

Por eso debemos elegir la probe según lo que realmente queremos comprobar.


8. Exec probes

También podemos ejecutar un comando dentro del contenedor:

livenessProbe:
exec:
command:
- /bin/sh
- -c
- test -f /tmp/healthy

Esto permite comprobaciones específicas del proceso.

Pero tiene trade-offs.

Cada ejecución requiere ejecutar procesos dentro del contenedor, por lo que probes exec demasiado frecuentes pueden generar overhead, especialmente en clusters densos.

Utilízalas cuando realmente aporten valor.


9. gRPC probes

Kubernetes también soporta probes nativas para workloads gRPC.

Por ejemplo:

livenessProbe:
grpc:
port: 50051
initialDelaySeconds: 10

Esto evita tener que crear obligatoriamente un endpoint HTTP únicamente para comprobar un servicio gRPC.

Sin embargo, existen diferencias de configuración respecto a HTTP/TCP probes.

Por ejemplo, las probes gRPC no utilizan named ports de la misma manera que HTTP/TCP.

Siempre debemos validar la configuración correspondiente al protocolo utilizado.


10. ¿Debe liveness comprobar la base de datos?

Este es uno de los errores de diseño más importantes.

Supongamos que:

/live
|
+--- application
+--- PostgreSQL
+--- Redis
+--- payment API

Ahora PostgreSQL tiene un incidente.

PostgreSQL unavailable
|
v
/live fails
|
v
Every application Pod
fails liveness
|
v
Every Pod restarts
|
v
Application capacity collapses

Reiniciar los Pods no repara PostgreSQL.

Estamos utilizando liveness para reaccionar ante una dependencia externa que el restart del proceso no puede solucionar.


Una mejor separación

Podríamos diseñar:

/live
|
+--- Is my process internally healthy?
/ready
|
+--- Can I currently serve useful traffic?

Por ejemplo:

Database unavailable
|
v
Readiness fails
|
v
Pod removed from traffic
|
v
Container remains alive

Pero incluso aquí debemos ser cuidadosos.


11. El peligro de incluir todas las dependencias en readiness

Supongamos que tenemos:

API
|
+-- PostgreSQL
+-- Redis
+-- External Payment API

y readiness requiere que las tres estén disponibles.

El Payment API tiene un pequeño incidente.

Payment API down
|
v
Every API Pod becomes NotReady
|
v
Service has zero ready backends
|
v
Entire API unavailable

Pero quizá:

GET /products
GET /profile
GET /orders

podrían continuar funcionando.

Al diseñar readiness debemos preguntarnos:

¿Esta dependencia realmente hace que todo el Pod sea incapaz de servir tráfico útil?

La respuesta depende de la arquitectura de la aplicación.


12. Probes deben ser baratas

Un health endpoint no debería ejecutar una consulta costosa cada pocos segundos.

Evita algo como:

/readiness
|
v
Complex DB query
|
v
Multiple downstream calls
|
v
Storage operation

multiplicado por:

500 Pods
×
probe every 5 seconds

Podemos terminar creando tráfico significativo simplemente para comprobar la salud del sistema.

Una buena probe debería normalmente ser:

fast
cheap
predictable
purpose-specific

13. Observar métricas de probes

Los Events son un excelente punto de partida:

kubectl get events \
-n production \
--sort-by='.lastTimestamp'

Busca:

Unhealthy
Readiness probe failed
Liveness probe failed
Startup probe failed

Pero los Events no sustituyen métricas históricas.

Queremos correlacionar:

Probe failures
+
Restart count
+
CPU
+
Memory
+
Latency
+
Traffic
+
Deployment

Por ejemplo:

10:00 Deployment
10:03 Traffic increases
10:05 CPU saturation
10:06 Liveness failures
10:07 Restarts increase
10:08 Available replicas decrease
10:09 5xx spike

Eso cuenta una historia mucho más completa.


14. Logs

Cuando una probe falla:

kubectl logs <pod> \
-n production

y si el contenedor reinició:

kubectl logs <pod> \
-n production \
--previous

Correlaciona el timestamp del Event con los logs de aplicación.

Podemos descubrir:

probe timeout
|
v
same timestamp
|
v
long GC pause

o:

readiness 503
|
v
connection pool exhausted

o:

startup failure
|
v
database migration still running

15. Metrics + traces

Las probes nos dicen si una instancia está disponible.

No necesariamente nos dicen por qué está degradada.

Supongamos:

Readiness failures increasing

y las métricas muestran:

CPU normal
Memory normal
Latency high

Un trace podría mostrar:

API 20 ms
|
+-- Inventory 15 ms
|
+-- Database 4.2 sec

Ahora sabemos qué dependencia está degradando la aplicación.

Health checks son una señal.

Logs, métricas y traces proporcionan contexto.


16. Rolling updates y readiness

Readiness es especialmente importante durante deployments.

Old Pods
|
+--- Ready
New Pod starts
|
v
NotReady
|
v
Initialize
|
v
Ready
|
v
Receives traffic

Sin una readiness probe útil, Kubernetes puede considerar disponible una aplicación antes de que realmente esté preparada para procesar requests correctamente.

Esto puede provocar errores únicamente durante deployments.


17. Readiness Gates

En escenarios más avanzados Kubernetes permite añadir condiciones adicionales a la evaluación de readiness mediante:

readinessGates:

Conceptualmente:

Containers Ready
+
Custom condition Ready
|
v
Pod Ready

Esto permite integrar señales externas o controllers dentro de la decisión de readiness.

No es necesario para la mayoría de workloads, pero puede ser útil cuando la disponibilidad depende de algo que una probe dentro del contenedor no representa adecuadamente.


18. Cuando una probe falla solo en un nodo

Supongamos:

node-01
Pods -> probes OK
node-02
Pods -> probes FAIL

Ahora debemos considerar que quizá la aplicación no es la causa.

Compara:

kubectl get pods \
-n production \
-o wide

Después:

kubectl describe node node-02

Posibles causas:

node CPU saturation
networking issue
CNI issue
kubelet problem
disk pressure
packet loss

El blast radius ayuda a identificar la capa.


19. Qué revisar en kubectl describe

Durante troubleshooting:

kubectl describe pod <pod> \
-n <namespace>

revisa:

State
Last State
Ready
Restart Count
Conditions
Liveness
Readiness
Startup
Events

También:

kubectl get pod <pod> \
-n <namespace> \
-o yaml

permite inspeccionar la configuración completa de las probes.


20. Un diseño práctico

Una configuración podría verse así:

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

Pero estos valores no son una receta universal.

Deben elegirse utilizando datos reales sobre:

startup time
normal response time
load behavior
recovery time
dependency behavior
traffic patterns

Copiar una configuración de otro servicio puede ser peor que no entender el problema.


Troubleshooting flow

Podemos resumir el proceso:

Pod problem
|
v
Is container Running?
|
v
Is Pod Ready?
|
+----------+----------+
| |
NO YES
| |
v v
Readiness Events Restarting?
| |
v v
Probe endpoint works? Liveness / Startup?
| |
v v
Path / port / timeout Probe timing
dependency / overload startup duration
| |
+----------+----------+
|
v
Metrics + Logs + Events
|
v
Check dependencies
|
v
Root cause
|
v
Change
|
v
Validate

Command reference

# Pod state
kubectl get pods \
-n <namespace>
kubectl get pods \
-n <namespace> \
-o wide
# Pod details and probe Events
kubectl describe pod <pod> \
-n <namespace>
# Full configuration
kubectl get pod <pod> \
-n <namespace> \
-o yaml
kubectl get deployment <deployment> \
-n <namespace> \
-o yaml
# Logs
kubectl logs <pod> \
-n <namespace>
kubectl logs <pod> \
-n <namespace> \
--previous
# Events
kubectl get events \
-n <namespace> \
--sort-by='.lastTimestamp'
# EndpointSlices
kubectl get endpointslices \
-n <namespace> \
-l kubernetes.io/service-name=<service> \
-o yaml
# Metrics
kubectl top pods \
-n <namespace>
kubectl top pod <pod> \
-n <namespace> \
--containers
# Deployment history
kubectl rollout history \
deployment/<deployment> \
-n <namespace>
# Test endpoint
curl -v http://<pod-ip>:<port>/ready
curl -v http://<pod-ip>:<port>/live
# Debug when the image lacks tools
kubectl debug <pod> \
-n <namespace> \
-it \
--image=ubuntu

Qué no hacer

No respondas automáticamente:

Readiness failing
|
v
Restart Pod

Ni:

Liveness failing
|
v
Increase timeout

Ni:

Slow startup
|
v
Set initialDelaySeconds = 300

Primero debemos entender qué ocurre.

Failure
|
v
Understand probe purpose
|
v
Reproduce probe
|
v
Correlate Events
|
v
Check metrics + logs
|
v
Identify application/dependency issue
|
v
Adjust probe or application
|
v
Validate under real load

Conclusión

Las probes no son simplemente health checks.

Son mecanismos que cambian el comportamiento del cluster.

Readiness failure
|
v
Traffic changes
Liveness failure
|
v
Container lifecycle changes
Startup failure
|
v
Container lifecycle changes

Por eso una probe incorrecta puede ser más peligrosa que no tener una probe bien entendida.

El objetivo es que cada probe responda una pregunta específica:

Startup:
¿terminó de iniciar?
Liveness:
¿reiniciar este proceso puede recuperarlo?
Readiness:
¿puede esta instancia recibir tráfico útil ahora?

Y antes de agregar una dependencia a una probe, debemos preguntarnos:

¿Qué acción ejecutará Kubernetes cuando esta dependencia falle, y esa acción realmente ayuda?

Esta pregunta evita muchos health checks peligrosos.

En el siguiente artículo investigaremos Kubernetes Nodes:

  • NotReady;

  • MemoryPressure;

  • DiskPressure;

  • PIDPressure;

  • kubelet;

  • container runtime;

  • node networking;

  • Pods concentrando fallas en un solo nodo;

  • cómo determinar cuándo el problema dejó de ser del workload y pasó a ser del host.

Comments