Troubleshooting de aplicaciones en Kubernetes
Si el troubleshooting del cluster era el pan de cada día del administrador, este capítulo es el del desarrollador: tu aplicación no arranca, se reinicia en bucle o no responde, y el cluster está perfectamente sano. Vamos a construir un método sistemático para no depurar a ciegas.
El método: estado → describe → logs
Ante cualquier problema, siempre la misma secuencia:
kubectl get pods # 1. ¿En qué estado está?
kubectl describe pod <pod> # 2. ¿Qué dicen los eventos?
kubectl logs <pod> [--previous] # 3. ¿Qué dice la aplicación?
El campo STATUS de kubectl get pods ya te dice dónde mirar. Vamos estado por estado.
Pending: el pod no encuentra sitio
El pod está aceptado pero el scheduler no consigue asignarle nodo. kubectl describe pod mostrará un evento FailedScheduling con el motivo exacto:
Insufficient cpu/memory: ningún nodo tiene los recursos que pide turequests. Solución: bajar requests, escalar el cluster o liberar carga.- Taints sin tolerar:
node(s) had untolerated taint...(capítulo 123). - Selectores imposibles: un
nodeSelectoro affinity que ningún nodo cumple (capítulo 122). - PVC sin resolver: el pod monta un PVC en
Pending(revisa la StorageClass y los PV disponibles).
ImagePullBackOff / ErrImagePull: la imagen no llega
Kubernetes no consigue descargar la imagen. Los eventos del describe te dan la causa:
- Nombre o tag mal escritos:
nginx:lastesten lugar delatest. Más común de lo que parece. - Imagen privada sin credenciales: falta el
imagePullSecretscon un secret de tipo docker-registry. - Registry inaccesible desde los nodos (red, proxy, TLS).
kubectl describe pod <pod> | grep -A5 Events
# Failed to pull image "myregistry.io/app:v2": ... unauthorized
Arreglo rápido habitual en examen: kubectl set image deployment/web app=nginx:1.27 o editar el deployment.
CrashLoopBackOff: arranca, muere, repite
El contenedor arranca y termina (con error) una y otra vez; Kubernetes espera cada vez más entre reintentos (el backoff). Las causas viven casi siempre dentro de la app, así que:
# Los logs del intento anterior (el contenedor actual quizá ni ha arrancado)
kubectl logs <pod> --previous
Causas típicas:
- Error de la aplicación al arrancar: configuración que falta (revisa env, ConfigMaps y Secrets referenciados), conexión a base de datos imposible...
- El comando termina: si el contenedor ejecuta algo que acaba (un script, un comando mal puesto en
command), Kubernetes lo considera muerto y lo reinicia. Un contenedor "de servicio" debe quedarse en primer plano. - Liveness probe demasiado agresiva: la app tarda en arrancar más que el
initialDelaySecondsy la probe la mata en bucle. El describe lo delata (Liveness probe failed). Solución: ajustar tiempos o añadir una startup probe.
OOMKilled: el caso especial
Si en el describe ves Last State: Terminated, Reason: OOMKilled, el contenedor superó su límite de memoria y el kernel lo mató. No habrá nada en los logs: la app no "falló", la fulminaron. Solución: subir el limits.memory (o arreglar la fuga de memoria, pero eso ya es tu código).
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState}'
Running pero no funciona
El pod está Running y 1/1 Ready... y aun así la aplicación no responde. Ahora toca entrar:
# Shell dentro del contenedor
kubectl exec -it <pod> -- sh
# ¿Responde la app en local?
kubectl exec <pod> -- wget -qO- localhost:8080/health
# Probar desde fuera sin exponer nada
kubectl port-forward pod/<pod> 8080:8080
curl localhost:8080
kubectl debug: cuando no hay ni shell
Las imágenes modernas (distroless, scratch) no traen shell ni utilidades. Para eso existen los ephemeral containers: kubectl debug añade un contenedor temporal con herramientas al pod en ejecución, compartiendo su red (y su filesystem si se lo pides):
# Contenedor efímero con utilidades, unido al pod problemático
kubectl debug -it <pod> --image=busybox:1.36 --target=<contenedor>
# O una copia del pod para experimentar sin tocar el original
kubectl debug <pod> -it --copy-to=debug-copy --container=app -- sh
Es la herramienta moderna de depuración y ya aparece en los exámenes.
El service no llega a los pods
La app funciona con port-forward pero no a través del service. El 90% de las veces es una de estas tres cosas, en este orden:
- El selector no casa con los labels de los pods:
kubectl get endpoints <servicio> # ¿Está vacío? Selector mal
kubectl get pods --show-labels
- El targetPort no es el puerto de la app: el service apunta al 80 pero la app escucha en el 8080.
- Los pods no están Ready: una readiness probe fallando saca al pod de los endpoints silenciosamente.
Y si el service tiene endpoints pero el nombre no resuelve, vuelve al capítulo de DNS.
Chuleta de diagnóstico
| Estado/Síntoma | Dónde mirar | Causa típica |
|---|---|---|
Pending | describe → eventos | Recursos, taints, selectores, PVC |
ImagePullBackOff | describe → eventos | Tag erróneo, registry privado |
CrashLoopBackOff | logs --previous | App falla al arrancar, liveness agresiva |
OOMKilled | describe → Last State | Límite de memoria insuficiente |
Init:X/Y colgado | logs -c <init> | Init container esperando dependencia |
| Running pero KO | exec, port-forward, debug | Bug de la app, config errónea |
| Service no responde | get endpoints | Selector, targetPort, readiness |
Resumen
- Método fijo: estado →
describe(eventos) →logs(con--previoussi se reinicia). - Cada STATUS apunta a una familia de causas: Pending = scheduling, ImagePull = imagen, CrashLoop = la app o sus probes, OOMKilled = memoria.
kubectl debugcon ephemeral containers es la vía para imágenes sin shell.- En problemas de service, los endpoints son la primera comprobación: selector, targetPort y readiness.
- Lista de vídeos en Youtube: Curso Kubernetes