Saltar al contenido principal

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 tu requests. Solución: bajar requests, escalar el cluster o liberar carga.
  • Taints sin tolerar: node(s) had untolerated taint... (capítulo 123).
  • Selectores imposibles: un nodeSelector o 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:lastest en lugar de latest. Más común de lo que parece.
  • Imagen privada sin credenciales: falta el imagePullSecrets con 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 initialDelaySeconds y 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:

  1. El selector no casa con los labels de los pods:
kubectl get endpoints <servicio> # ¿Está vacío? Selector mal
kubectl get pods --show-labels
  1. El targetPort no es el puerto de la app: el service apunta al 80 pero la app escucha en el 8080.
  2. 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íntomaDónde mirarCausa típica
Pendingdescribe → eventosRecursos, taints, selectores, PVC
ImagePullBackOffdescribe → eventosTag erróneo, registry privado
CrashLoopBackOfflogs --previousApp falla al arrancar, liveness agresiva
OOMKilleddescribe → Last StateLímite de memoria insuficiente
Init:X/Y colgadologs -c <init>Init container esperando dependencia
Running pero KOexec, port-forward, debugBug de la app, config errónea
Service no respondeget endpointsSelector, targetPort, readiness

Resumen

  • Método fijo: estado → describe (eventos) → logs (con --previous si se reinicia).
  • Cada STATUS apunta a una familia de causas: Pending = scheduling, ImagePull = imagen, CrashLoop = la app o sus probes, OOMKilled = memoria.
  • kubectl debug con 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.

Volver al índice