Cuando una aplicación falla en Kubernetes, ver un Pod en CrashLoopBackOff, ImagePullBackOff o Pending no nos dice necesariamente cuál es el problema.
Nos dice dónde comenzar a investigar.
Uno de los errores más comunes durante troubleshooting es intentar solucionar inmediatamente el estado que muestra kubectl.
Por ejemplo:
CrashLoopBackOff
no es la causa raíz.
Significa que un contenedor está fallando repetidamente y Kubernetes está aplicando un retraso progresivo antes de intentar iniciarlo nuevamente.
La causa real podría estar en la aplicación, configuración, recursos, health checks o alguna dependencia externa.
En este artículo vamos a construir un proceso sistemático para investigar cuatro de los estados más comunes:
CrashLoopBackOffImagePullBackOffPods en
PendingPods en
Terminating
Antes de comenzar: no reinicies el Pod inmediatamente
Cuando ocurre un incidente, puede ser tentador ejecutar:
kubectl delete pod <pod>
y esperar que Kubernetes cree uno nuevo.
En algunos casos puede recuperar temporalmente el servicio.
Pero también podemos perder información importante para entender la causa del problema.
Antes de modificar nada, conviene revisar:
Estado del Pod
|
+--- Events
|
+--- Logs
|
+--- Estado anterior del contenedor
|
+--- Recursos
|
+--- Configuración
Una buena investigación empieza preservando evidencia.
Un flujo básico de troubleshooting
Antes de investigar un error específico podemos obtener una vista general del clúster.
kubectl get pods -A -o wide
Esto nos permite identificar rápidamente:
namespace;
estado;
número de reinicios;
dirección IP;
nodo donde corre el Pod;
tiempo desde su creación.
Después podemos revisar los eventos recientes:
kubectl get events -A \
--sort-by='.lastTimestamp'
Los Events son particularmente útiles cuando Kubernetes está teniendo problemas con:
Scheduling
Image pulls
Volumes
Health checks
Nodes
Evictions
Si ya conocemos el Pod afectado:
kubectl describe pod <pod> -n <namespace>
kubectl describe combina mucha de la información necesaria para una primera investigación:
Containers
State
Last State
Restart Count
Conditions
Volumes
Node
Events
Después podemos revisar logs:
kubectl logs <pod> -n <namespace>
Si el contenedor ya reinició, existe un comando especialmente importante:
kubectl logs <pod> \
-n <namespace> \
--previous
Esto obtiene los logs de la instancia anterior del contenedor.
1. CrashLoopBackOff
Supongamos que encontramos:
NAME READY STATUS RESTARTS
api-xyz 0/1 CrashLoopBackOff 7
CrashLoopBackOff significa que el contenedor está entrando en un ciclo similar a este:
Container starts
|
v
Container fails
|
v
Kubernetes restarts it
|
v
Container fails again
|
v
Backoff
|
+----------------+
|
v
Retry again
Kubernetes incrementa progresivamente el tiempo entre intentos para evitar reiniciar continuamente un contenedor que sigue fallando.
La pregunta importante es:
¿Por qué termina el proceso dentro del contenedor?
Paso 1: revisar el Pod
kubectl describe pod api-xyz -n production
Busca especialmente:
State:
Last State:
Reason:
Exit Code:
Restart Count:
Events:
Podríamos encontrar algo como:
Last State: Terminated
Reason: Error
Exit Code: 1
Eso indica que el proceso terminó con error.
Pero también podríamos encontrar:
Reason: OOMKilled
Exit Code: 137
En ese caso la investigación cambia completamente hacia memoria.
Paso 2: revisar los logs anteriores
Primero:
kubectl logs api-xyz -n production
Pero si el contenedor reinicia rápidamente:
kubectl logs api-xyz \
-n production \
--previous
Este comando muchas veces contiene la pista más importante.
Por ejemplo:
ERROR: DATABASE_URL environment variable is missing
Ahora tenemos una hipótesis mucho más específica.
Paso 3: revisar la configuración
Podemos ver el Pod completo:
kubectl get pod api-xyz \
-n production \
-o yaml
Y, si pertenece a un Deployment:
kubectl get deployment api \
-n production \
-o yaml
Revisa:
environment variables
Secrets
ConfigMaps
command
args
resources
volume mounts
probes
image
Una configuración incorrecta puede permitir que el contenedor sea creado correctamente, pero hacer que la aplicación falle apenas comienza.
Paso 4: revisar health checks
También podemos encontrar Events como:
Liveness probe failed
o:
Startup probe failed
En ese caso debemos revisar:
livenessProbe:
startupProbe:
readinessProbe:
Una aplicación que tarda 40 segundos en iniciar con un health check que comienza demasiado pronto puede terminar en un ciclo de reinicios aunque la aplicación no tenga ningún bug.
Causas comunes de CrashLoopBackOff
CrashLoopBackOff
|
+--- Application crash
|
+--- Missing environment variable
|
+--- Invalid Secret / ConfigMap
|
+--- Dependency unavailable
|
+--- OOMKilled
|
+--- Liveness probe
|
+--- Startup probe
|
+--- Invalid command / arguments
Por eso CrashLoopBackOff debe tratarse como un síntoma, no como una causa raíz.
2. ImagePullBackOff
Ahora encontramos:
NAME READY STATUS RESTARTS
api-abc 0/1 ImagePullBackOff 0
Aquí el contenedor todavía no llegó a ejecutar nuestra aplicación.
El problema ocurre antes:
Pod
|
v
kubelet
|
v
Container runtime
|
v
Registry
|
+--- Image
+--- Authentication
+--- DNS
+--- Network
+--- TLS
Comenzamos nuevamente con:
kubectl describe pod api-abc \
-n production
Los Events pueden mostrar errores como:
manifest unknown
pull access denied
unauthorized
x509: certificate signed by unknown authority
o:
no such host
Cada error apunta hacia una investigación distinta.
Confirmar la imagen
kubectl get pod api-abc \
-n production \
-o jsonpath='{.spec.containers[*].image}'
Por ejemplo:
registry.example.com/platform/api:2.4.1
Confirma:
registry correcto;
repositorio correcto;
nombre de imagen correcto;
tag existente.
Un typo puede ser suficiente para producir ImagePullBackOff.
Registry privado
Si utilizamos un registry privado debemos revisar:
kubectl get secrets -n production
Y comprobar qué imagePullSecrets utiliza el Pod:
kubectl get pod api-abc \
-n production \
-o jsonpath='{.spec.imagePullSecrets}'
También debemos recordar algo importante:
Que nuestra laptop pueda hacer
docker pullno significa que el nodo Kubernetes pueda hacerlo.
Es el kubelet/container runtime del nodo quien necesita acceder al registry.
Por eso también pueden existir problemas de:
DNS
Routing
Firewall
Proxy
TLS
Registry availability
3. Pods en Pending
Otro escenario frecuente:
NAME READY STATUS RESTARTS
api-123 0/1 Pending 0
Pending requiere primero responder una pregunta:
¿El scheduler pudo asignar el Pod a un nodo?
La forma más rápida de saberlo es:
kubectl describe pod api-123 \
-n production
Podemos encontrar:
Warning FailedScheduling
seguido por algo como:
0/3 nodes are available:
2 Insufficient cpu,
1 node(s) had untolerated taint
Esta información es mucho más útil que simplemente observar STATUS=Pending.
Recursos insuficientes
Revisa los requests del Pod:
kubectl get pod api-123 \
-n production \
-o yaml
Por ejemplo:
resources:
requests:
cpu: "2"
memory: "4Gi"
Después revisa los nodos:
kubectl describe nodes
y las métricas:
kubectl top nodes
Pero existe una diferencia importante.
El scheduler toma decisiones principalmente utilizando los resource requests, no simplemente el consumo instantáneo mostrado por kubectl top.
Podríamos tener:
Node CPU actual: 30%
CPU requested: 95%
y nuevos Pods podrían no encontrar capacidad suficiente para sus requests.
Otros motivos de FailedScheduling
No todo es CPU y memoria.
También debemos investigar:
nodeSelector
nodeAffinity
podAffinity
podAntiAffinity
taints
tolerations
topologySpreadConstraints
PVC
ResourceQuota
Por ejemplo:
kubectl get nodes --show-labels
y:
kubectl describe node <node>
pueden ayudarnos a encontrar problemas relacionados con labels y taints.
4. Pods en Terminating
Ahora imaginemos:
NAME READY STATUS AGE
api-456 1/1 Terminating 2d
Los Pods no desaparecen instantáneamente cuando los eliminamos.
Kubernetes les permite realizar un graceful shutdown antes de eliminarlos.
Podemos comenzar con:
kubectl describe pod api-456 \
-n production
y:
kubectl get pod api-456 \
-n production \
-o yaml
Busca especialmente:
deletionTimestamp:
finalizers:
terminationGracePeriodSeconds:
Causas comunes
Un Pod puede permanecer en Terminating debido a:
Application not handling SIGTERM
|
preStop hook blocked
|
Volume cannot unmount
|
CSI problem
|
Finalizer
|
Node unavailable
Podemos revisar el nodo:
kubectl get pod api-456 \
-n production \
-o wide
seguido por:
kubectl get node <node>
Si existen volúmenes:
kubectl describe pod api-456 \
-n production
y:
kubectl get pvc -n production
pueden ayudarnos a comprobar problemas relacionados con storage.
¿Y force delete?
Existe:
kubectl delete pod api-456 \
-n production \
--grace-period=0 \
--force
Pero debería ser una medida deliberada, no el primer paso del troubleshooting.
Forzar la eliminación elimina la representación del Pod sin necesariamente garantizar que todos los procesos o recursos asociados hayan terminado correctamente.
Con workloads stateful esto merece todavía más cuidado.
Primero debemos entender por qué el Pod no termina normalmente.
Cuando kubectl exec no es suficiente
Muchas imágenes modernas son minimalistas o incluso distroless.
Es posible intentar:
kubectl exec -it api-xyz -- sh
y descubrir que no existe:
sh
curl
dig
tcpdump
ps
Eso no significa que debamos agregar permanentemente herramientas de debugging a nuestra imagen de producción.
Podemos utilizar un ephemeral container:
kubectl debug api-xyz \
-n production \
-it \
--image=ubuntu
kubectl debug permite agregar herramientas de troubleshooting sin modificar la imagen original de nuestra aplicación.
También puede utilizarse para investigar nodos:
kubectl debug node/worker-01 \
-it \
--image=ubuntu
En ese caso podemos inspeccionar el entorno del nodo desde un Pod de debugging.
Metrics, Logs y Events responden preguntas diferentes
Uno de los puntos más importantes al investigar Kubernetes es no utilizar una sola fuente de información.
Problem
|
+-----------+-----------+
| | |
Events Metrics Logs
| | |
v v v
What did K8s? How much? What happened?
Events
Útiles para:
FailedScheduling
FailedMount
FailedAttachVolume
FailedPull
BackOff
Unhealthy
Evicted
Comando:
kubectl get events -A \
--sort-by='.lastTimestamp'
Metrics
Útiles para:
CPU
Memory
Node utilization
Container utilization
Por ejemplo:
kubectl top pods -A
kubectl top nodes
En entornos con Prometheus podemos investigar históricos y correlacionar el incidente con cambios de tráfico o utilización.
Logs
Útiles para encontrar lo que ocurrió dentro de la aplicación:
kubectl logs <pod>
o:
kubectl logs <pod> --previous
En producción normalmente estos logs también deberían centralizarse utilizando plataformas como Loki, OpenSearch, Elasticsearch, Splunk, Datadog o soluciones equivalentes.
Un proceso que podemos reutilizar
Para estos y otros problemas podemos mantener un flujo consistente:
Symptom
|
v
Define blast radius
|
v
Check Pod state
|
v
Events
|
v
Logs / Metrics
|
v
Configuration
|
v
Dependencies / Infrastructure
|
v
Root cause
|
v
Fix
|
v
Validate
|
v
Prevent
Antes de cambiar algo, intenta responder:
¿Qué está fallando?
¿Cuándo comenzó?
¿Qué cambió?
¿Afecta un Pod o varios?
¿Afecta un nodo específico?
¿Qué dicen los Events?
¿Qué muestran los logs?
¿Qué muestran las métricas?
Este proceso reduce el troubleshooting por prueba y error.
Comandos de referencia
# Pods
kubectl get pods -A -o wide
# Details and Events
kubectl describe pod <pod> -n <namespace>
# Logs
kubectl logs <pod> -n <namespace>
kubectl logs <pod> -n <namespace> --previous
# Events
kubectl get events -A --sort-by='.lastTimestamp'
# Resources
kubectl top pods -A
kubectl top nodes
# Configuration
kubectl get pod <pod> -n <namespace> -o yaml
kubectl get deployment <deployment> -n <namespace> -o yaml
# Nodes
kubectl get nodes -o wide
kubectl describe node <node>
# Debug container
kubectl debug <pod> -n <namespace> -it --image=ubuntu
# Node debugging
kubectl debug node/<node> -it --image=ubuntu
Conclusión
Estados como CrashLoopBackOff, ImagePullBackOff, Pending y Terminating son puntos de entrada al troubleshooting, no diagnósticos completos.
La clave está en conectar diferentes señales:
Kubernetes State
+
Events
+
Logs
+
Metrics
|
v
Root Cause
En el siguiente artículo vamos a continuar con uno de los problemas más comunes en clusters de producción:
CPU, memoria, OOMKilled, throttling y Pod evictions.
Ahí veremos por qué un Pod puede tener problemas de performance incluso cuando el nodo parece tener recursos disponibles.
Comments