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: nuestra 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 nos 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 nuestrorequests. 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(revisemos la StorageClass y los PV disponibles).
ImagePullBackOff / ErrImagePull: la imagen no llega
Kubernetes no consigue descargar la imagen. Los eventos del describe nos 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 vemos 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 nuestro 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 endpointslices -l kubernetes.io/service-name=<servicio> # ¿Sin endpoints? 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.
La API
Endpoints(y por tantokubectl get endpoints) está deprecada desde Kubernetes 1.33 en favor de EndpointSlices. Sigue funcionando, pero muestra un aviso; mejor acostumbrarse ya al comando nuevo.
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 endpointslices | 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