Solución de problemas: ¿Por qué mi Pod de Kubernetes está atascado en Pending o CrashLoopBackOff?

Los Pods de Kubernetes atascados en `Pending` o `CrashLoopBackOff` pueden detener los despliegues. Esta guía completa desmitifica estos estados comunes, ofreciendo pasos prácticos y detallados para solucionar problemas. Aprende a diagnosticar problemas como restricciones de recursos, errores de extracción de imágenes, fallos de aplicación y configuraciones incorrectas de sondas usando comandos `kubectl`. Poténciate con información práctica y mejores prácticas para resolver rápidamente problemas de Pods y mantener un entorno Kubernetes robusto y confiable, asegurando que tus aplicaciones estén siempre en funcionamiento.

Solución de problemas: ¿Por qué mi Pod de Kubernetes está atascado en Pending o CrashLoopBackOff?

Pending y CrashLoopBackOff se ven similares cuando esperas un despliegue, pero significan cosas muy diferentes. Pending generalmente significa que Kubernetes no ha podido colocar o preparar el pod. CrashLoopBackOff significa que el contenedor se inició, luego salió, y Kubernetes está retrasando el próximo reinicio.

Esa diferencia importa. Un pod pendiente suele ser un problema del scheduler, de la imagen o de almacenamiento. Un pod que falla suele ser un problema de la aplicación, comando, sonda, permisos o memoria. Comienza con esa división y el camino de solución de problemas se acorta mucho.

Entendiendo los Estados de los Pods: Pending vs. CrashLoopBackOff

Antes de sumergirte en la solución de problemas, es esencial entender qué significan estos dos estados.

Estado del Pod: Pending

Un Pod en estado Pending significa que Kubernetes ha aceptado el objeto Pod, pero no se ha movido completamente a un estado de contenedor en ejecución. A veces no se ha programado en un nodo. A veces tiene un nodo asignado, pero la extracción de la imagen, el montaje del volumen o la configuración del sandbox no se han completado.

Estado del Pod: CrashLoopBackOff

Un Pod en estado CrashLoopBackOff significa que un contenedor dentro del Pod se está iniciando, fallando y reiniciando repetidamente. Kubernetes implementa un retraso de retroceso exponencial entre reinicios para evitar abrumar al nodo. Este estado casi siempre apunta a un problema con la aplicación que se ejecuta dentro del contenedor o su entorno inmediato.

Un caso sutil: un contenedor puede salir con código 0 y aún así entrar en un bucle de reinicio si la carga de trabajo se supone que es un servidor de larga duración. Eso sucede a menudo cuando un Deployment ejecuta un comando único por error, como un script de migración o un comando de shell que finaliza inmediatamente.

Solución de Problemas de Pods en Estado Pending

Cuando un Pod está Pending, el primer lugar para mirar es el scheduler y el nodo al que está intentando llegar. Aquí están las causas comunes y los pasos de diagnóstico.

1. Recursos Insuficientes en los Nodos

Una de las razones más frecuentes para que un Pod esté Pending es que ningún nodo en el clúster tiene suficientes recursos disponibles (CPU, memoria) para satisfacer las requests del Pod. El scheduler no puede encontrar un nodo adecuado.

Pasos de Diagnóstico:

  1. Describe el Pod: El comando kubectl describe pod es tu mejor amigo aquí. A menudo mostrará eventos detallando por qué el Pod no puede ser programado.

    kubectl describe pod <nombre-del-pod> -n <namespace>
    

    Busca eventos como "FailedScheduling" y mensajes como "0/3 nodes are available: 3 Insufficient cpu" o "memory".

  2. Verifica los Recursos del Nodo: Mira el uso actual de recursos y la capacidad de tus nodos.

    kubectl get nodes
    kubectl top nodes # (requiere metrics-server)
    

Solución:

  • Aumenta la Capacidad del Clúster: Agrega más nodos a tu clúster de Kubernetes.
  • Ajusta las Solicitudes de Recursos del Pod: Reduce las requests de CPU y memoria en el manifiesto de tu Pod si están configuradas demasiado altas.
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
    
  • Desaloja Otros Pods: Desaloja manualmente Pods de menor prioridad de los nodos para liberar recursos (úsalo con precaución).

2. Errores de Extracción de Imagen

Si Kubernetes puede programar el Pod en un nodo, pero el nodo falla al extraer la imagen del contenedor, el Pod permanecerá Pending.

Causas Comunes:

  • Nombre/Etiqueta de Imagen Incorrectos: Errores tipográficos en el nombre de la imagen o uso de una etiqueta inexistente.
  • Autenticación de Registro Privado: Faltan o son incorrectos los ImagePullSecrets para registros privados.
  • Problemas de Red: El nodo no puede alcanzar el registro de imágenes.

Pasos de Diagnóstico:

  1. Describe el Pod: Nuevamente, kubectl describe pod es clave. Busca eventos como "Failed" o "ErrImagePull" o "ImagePullBackOff".

    kubectl describe pod <nombre-del-pod> -n <namespace>
    

    Ejemplo de evento de salida: Failed to pull image "my-private-registry/my-app:v1.0": rpc error: code = Unknown desc = Error response from daemon: pull access denied for my-private-registry/my-app, repository does not exist or may require 'docker login'

  2. Verifica ImagePullSecrets: Asegúrate de que imagePullSecrets estén configurados correctamente en tu Pod o ServiceAccount.

    kubectl get secret <tu-secreto-de-extraccion-de-imagen> -o yaml -n <namespace>
    

Solución:

  • Corrige el Nombre/Etiqueta de la Imagen: Vuelve a verificar el nombre y la etiqueta de la imagen en tu manifiesto de despliegue.
  • Configura ImagePullSecrets: Asegúrate de haber creado un secreto docker-registry y lo hayas vinculado a tu Pod o ServiceAccount.
    kubectl create secret docker-registry my-registry-secret \n      --docker-server=tu-registro.com \n      --docker-username=tu-usuario \n      --docker-password=tu-contraseña \n      --docker-email=tu-email -n <namespace>
    
    Luego, agrégalo a la especificación de tu Pod:
    spec:
      imagePullSecrets:
      - name: my-registry-secret
      containers:
      ...
    
  • Conectividad de Red: Verifica la conectividad de red desde el nodo al registro de imágenes.

Si estás usando un registro privado, verifica también la ServiceAccount. Muchos equipos adjuntan imagePullSecrets a la ServiceAccount predeterminada del namespace en lugar de a cada Deployment:

kubectl get serviceaccount default -n <namespace> -o yaml

Si el secreto existe pero la extracción aún falla, confirma que el nombre de host del registro en el secreto coincida exactamente con el nombre de host en la referencia de la imagen. registry.example.com/app:v1 y https://registry.example.com/app:v1 no son la misma referencia.

3. Problemas Relacionados con Volúmenes

Si tu Pod requiere un PersistentVolumeClaim (PVC) y el PersistentVolume (PV) correspondiente no se puede aprovisionar o vincular, el Pod permanecerá Pending.

Pasos de Diagnóstico:

  1. Describe el Pod: Busca eventos relacionados con volúmenes.

    kubectl describe pod <nombre-del-pod> -n <namespace>
    

    Los eventos podrían mostrar FailedAttachVolume, FailedMount o mensajes similares.

  2. Verifica el Estado del PVC y PV: Inspecciona el estado del PVC y PV.

    kubectl get pvc <nombre-del-pvc> -n <namespace>
    kubectl get pv
    

    Busca PVCs atascados en Pending o PVs no vinculados.

Solución:

  • Asegura StorageClass: Asegúrate de que un StorageClass esté definido y disponible, especialmente si se usa aprovisionamiento dinámico.
  • Verifica la Disponibilidad del PV: Si se usa aprovisionamiento estático, asegúrate de que el PV exista y coincida con los criterios del PVC.
  • Verifica los Modos de Acceso: Asegúrate de que los modos de acceso (por ejemplo, ReadWriteOnce, ReadWriteMany) sean compatibles.

También verifica si el pod está programado en una zona donde el volumen se pueda adjuntar. En clústeres en la nube, un disco creado en una zona de disponibilidad puede no adjuntarse a un nodo en otra. El evento generalmente menciona la afinidad del nodo del volumen o un error de adjuntar. En ese caso, la solución puede ser restricciones de programación, un StorageClass diferente o recrear el volumen en la zona correcta.

4. Taints, Tolerations y Node Selectors

Un pod puede permanecer Pending incluso cuando el clúster tiene mucha CPU y memoria. El scheduler también debe respetar las reglas de colocación.

Ejemplos comunes:

  • El pod tiene un nodeSelector que no coincide con ningún nodo.
  • El pod requiere una afinidad de nodo demasiado estricta.
  • Los únicos nodos que coinciden tienen taints, y el pod no tiene toleration coincidente.
  • El namespace tiene una cuota que bloquea los recursos solicitados.

Primero verifica los eventos de programación:

kubectl describe pod <nombre-del-pod> -n <namespace>

Luego compara las reglas de colocación del pod con las etiquetas del nodo:

kubectl get pod <nombre-del-pod> -n <namespace> -o yaml
kubectl get nodes --show-labels
kubectl describe node <nombre-del-nodo>

Si el evento dice que un taint no fue tolerado, programa el pod en otro lugar o agrega una toleration solo si esa carga de trabajo realmente pertenece a esos nodos. No toleres ciegamente cada taint. Los taints a menudo protegen nodos especiales, nodos GPU, nodos de infraestructura o nodos bajo presión.

Solución de Problemas de Pods en Estado CrashLoopBackOff

Un estado CrashLoopBackOff indica un problema a nivel de aplicación. El contenedor se inició con éxito pero luego salió con un error, lo que provoca que Kubernetes lo reinicie repetidamente.

1. Errores de Aplicación

La causa más común es que la aplicación en sí misma no pueda iniciarse o encuentre un error fatal poco después de iniciarse.

Causas Comunes:

  • Faltan Dependencias/Configuración: La aplicación no puede encontrar archivos de configuración críticos, variables de entorno o servicios externos de los que depende.
  • Comando/Argumentos Incorrectos: El command o args especificados en la especificación del contenedor son incorrectos o llevan a una salida inmediata.
  • Errores de Lógica de la Aplicación: Errores en el código de la aplicación que causan que falle al iniciar.

Pasos de Diagnóstico:

  1. Ver los Logs del Pod: Este es el paso más crítico. Los logs a menudo mostrarán el mensaje de error exacto que causó que la aplicación fallara.

    kubectl logs <nombre-del-pod> -n <namespace>
    

    Si el Pod está fallando repetidamente, los logs podrían mostrar la salida del intento fallido más reciente. Para ver los logs de una instancia anterior de un contenedor que falla, usa la bandera -p (previous):

    kubectl logs <nombre-del-pod> -p -n <namespace>
    
  2. Describe el Pod: Busca Restart Count en la sección Containers, que indica cuántas veces ha fallado el contenedor. También, verifica Last State para Exit Code.

    kubectl describe pod <nombre-del-pod> -n <namespace>
    

    Un código de salida de 0 generalmente significa un apagado graceful, pero cualquier código de salida distinto de cero significa un error. Los códigos de salida comunes distintos de cero incluyen 1 (error general), 137 (SIGKILL, a menudo OOMKilled), 139 (SIGSEGV, fallo de segmentación).

Solución:

  • Revisa los Logs de la Aplicación: Basado en los logs, depura el código de tu aplicación o configuración. Asegúrate de que todas las variables de entorno, ConfigMaps y Secrets requeridos estén montados/inyectados correctamente.
  • Prueba Localmente: Intenta ejecutar la imagen del contenedor localmente con las mismas variables de entorno y comandos para reproducir y depurar el problema.

Si el pod tiene múltiples contenedores, siempre especifica el nombre del contenedor:

kubectl logs <nombre-del-pod> -c <nombre-del-contenedor> -n <namespace>
kubectl logs <nombre-del-pod> -c <nombre-del-contenedor> -p -n <namespace>

Sin -c, podrías estar leyendo los logs del sidecar mientras la aplicación principal es la que está fallando.

2. Fallo de las Sondas de Liveness y Readiness

Kubernetes usa sondas de Liveness y Readiness para determinar la salud y disponibilidad de tu aplicación. Si una sonda de liveness falla continuamente, Kubernetes reiniciará el contenedor, llevando a CrashLoopBackOff.

Pasos de Diagnóstico:

  1. Describe el Pod: Verifica las definiciones de las sondas Liveness y Readiness y su Last State en la sección Containers.

    kubectl describe pod <nombre-del-pod> -n <namespace>
    

    Busca mensajes que indiquen fallos de sonda, como "Liveness probe failed: HTTP probe failed with statuscode: 500".

  2. Revisa los Logs de la Aplicación: A veces los logs de la aplicación proporcionarán contexto sobre por qué el endpoint de la sonda está fallando.

Solución:

  • Ajusta la Configuración de la Sonda: Corrige el path, port, command, initialDelaySeconds, periodSeconds o failureThreshold de la sonda.
  • Asegura la Salud del Endpoint de la Sonda: Verifica que el endpoint de la aplicación al que apunta la sonda esté realmente saludable y responda como se espera. La aplicación podría estar tomando demasiado tiempo para iniciar, requiriendo un initialDelaySeconds más grande.

Para aplicaciones de inicio lento, considera un startupProbe. Le da a la aplicación más tiempo para inicializarse antes de que la sonda de liveness comience a juzgarla. Esto es más limpio que establecer un initialDelaySeconds de liveness enorme para cada reinicio.

3. Límites de Recursos Excedidos

Si un contenedor intenta consistentemente usar más memoria que su memory.limit o tiene la CPU estrangulada debido a exceder su cpu.limit, el kernel podría terminar el proceso, a menudo con un evento OOMKilled (Out Of Memory Killed).

Pasos de Diagnóstico:

  1. Describe el Pod: Busca OOMKilled en la sección Last State o Events. Un Exit Code: 137 a menudo indica un evento OOMKilled.

    kubectl describe pod <nombre-del-pod> -n <namespace>
    
  2. Verifica kubectl top: Si metrics-server está instalado, usa kubectl top pod para ver el uso real de recursos de tus Pods.

    kubectl top pod <nombre-del-pod> -n <namespace>
    

Solución:

  • Aumenta los Límites de Recursos: Si tu aplicación realmente necesita más recursos, aumenta los limits de memory y/o cpu en el manifiesto de tu Pod. Esto podría requerir más capacidad en tus nodos.
    resources:
      requests:
        memory: "256Mi"
        cpu: "500m"
      limits:
        memory: "512Mi" # Aumenta esto
        cpu: "1000m"   # Aumenta esto
    
  • Optimiza la Aplicación: Perfila tu aplicación para identificar y reducir su consumo de recursos.

4. Problemas de Permisos

Los contenedores podrían fallar si carecen de los permisos necesarios para acceder a archivos, directorios o recursos de red que requieren.

Pasos de Diagnóstico:

  1. Revisa los Logs: Los logs de la aplicación podrían mostrar errores de permiso denegado (EACCES).
  2. Describe el Pod: Verifica el ServiceAccount que se está usando y cualquier configuración de securityContext montada.

Solución:

  • Ajusta securityContext: Configura runAsUser, fsGroup o allowPrivilegeEscalation según sea necesario.
  • Permisos de ServiceAccount: Asegúrate de que el ServiceAccount asociado con el Pod tenga los Roles y ClusterRoles necesarios vinculados a través de RoleBindings y ClusterRoleBindings.
  • Permisos de Volumen: Asegúrate de que los volúmenes montados (por ejemplo, emptyDir, hostPath, ConfigMap, Secret) tengan los permisos correctos para el usuario del contenedor.

Un Árbol de Decisión Rápido

Cuando alguien dice "el pod está roto", ejecuta estos en orden:

kubectl get pod <nombre-del-pod> -n <namespace> -o wide
kubectl describe pod <nombre-del-pod> -n <namespace>
kubectl logs <nombre-del-pod> -n <namespace> --all-containers=true --tail=100
kubectl logs <nombre-del-pod> -n <namespace> --all-containers=true -p --tail=100

Luego bifurca:

  • Si no hay nodo en kubectl get pod -o wide, concéntrate en la programación: requests, taints, afinidad, cuota y disponibilidad del nodo.
  • Si hay un nodo pero el evento menciona extracción de imagen, concéntrate en el nombre de la imagen, etiqueta, autenticación del registro y acceso de red del nodo al registro.
  • Si el evento menciona montaje o adjuntar, concéntrate en PVCs, PVs, StorageClass, modos de acceso y ubicación de zona.
  • Si el pod se inicia y luego se reinicia, concéntrate en logs, código de salida, sondas, comando/args, configuración, secretos y límites de memoria.

Este orden evita un error común: leer logs de aplicación para un pod que nunca inició un contenedor de aplicación.

Leyendo Códigos de Salida Sin Sobrerreaccionar

Los códigos de salida son pistas, no explicaciones completas.

  • 1 generalmente significa que la aplicación devolvió un error general. Los logs importan más que el número.
  • 2 puede apuntar a errores de uso de línea de comandos en muchos programas.
  • 126 a menudo significa que el comando existe pero no puede ejecutarse.
  • 127 a menudo significa que el comando no fue encontrado.
  • 137 aparece comúnmente cuando el proceso recibe SIGKILL; en Kubernetes eso a menudo, pero no siempre, está conectado a OOMKilled.
  • 143 significa que el proceso recibió SIGTERM, lo que puede ocurrir durante la terminación normal.

Si el código de salida es 137, verifica el Last State del pod y los eventos antes de asumir una fuga de memoria. Un drenaje de nodo, desalojo o eliminación manual también pueden terminar un contenedor.

Pasos de Diagnóstico Generales y Herramientas

Aquí hay una lista de verificación rápida de comandos para ejecutar cuando enfrentes problemas de Pods:

  • Obtén una Visión General Rápida: Verifica el estado de tus Pods.
    kubectl get pods -n <namespace>
    kubectl get pods -n <namespace> -o wide
    
  • Información Detallada del Pod: El comando más crucial para entender eventos, estados y condiciones del Pod.
    kubectl describe pod <nombre-del-pod> -n <namespace>
    
  • Logs del Contenedor: Mira lo que tu aplicación está reportando.
    kubectl logs <nombre-del-pod> -n <namespace>
    kubectl logs <nombre-del-pod> -p -n <namespace> # Instancia anterior
    kubectl logs <nombre-del-pod> -f -n <namespace> # Seguir logs
    
  • Eventos a Nivel de Clúster: A veces el problema no es con un Pod específico sino un evento a nivel de clúster (por ejemplo, presión en el nodo).
    kubectl get events -n <namespace>
    
  • Depuración Interactiva: Si tu contenedor se inicia pero falla rápidamente, podrías poder hacer exec en él por un breve momento o en un contenedor de depuración separado si está configurado.
    kubectl exec -it <nombre-del-pod> -n <namespace> -- bash
    
    (Nota: Esto funciona solo si el contenedor permanece vivo el tiempo suficiente para adjuntarse.)

Mejores Prácticas para Evitar Problemas de Pods

La prevención siempre es mejor que la cura. Seguir estas mejores prácticas puede reducir significativamente los incidentes de Pending y CrashLoopBackOff:

  • Establece Solicitudes y Límites de Recursos Realistas: Comienza con requests y limits razonables, luego ajústalos basándote en la creación de perfiles y monitoreo de la aplicación.
  • Usa Etiquetas de Imagen Específicas: Evita las etiquetas latest en producción. Usa etiquetas inmutables (por ejemplo, v1.2.3, commit-sha) para la reproducibilidad.
  • Implementa Sondas Robusta: Configura sondas liveness y readiness que reflejen con precisión la salud de tu aplicación. Ten en cuenta los tiempos de inicio con initialDelaySeconds.
  • Registro y Monitoreo Centralizados: Usa herramientas como Prometheus, Grafana, ELK stack o servicios de registro nativos de la nube para recopilar y analizar logs y métricas de Pods.
  • Control de Versiones para Manifiestos: Almacena tus manifiestos de Kubernetes en un sistema de control de versiones (por ejemplo, Git) para rastrear cambios y facilitar reversiones.
  • Pruebas Exhaustivas: Prueba tus imágenes de contenedor y despliegues de Kubernetes en entornos de desarrollo y staging antes de desplegar en producción.
  • Apagados Graceful: Asegúrate de que tus aplicaciones manejen las señales SIGTERM para apagados graceful, permitiéndoles liberar recursos antes de la terminación.

Lo Que Generalmente lo Soluciona Más Rápido

Para Pending, kubectl describe pod es generalmente el camino más rápido porque los eventos del scheduler y kubelet explican lo que Kubernetes no pudo hacer. Para CrashLoopBackOff, los logs anteriores son generalmente el camino más rápido porque el contenedor actual puede ser demasiado nuevo para mostrar el fallo que causó el bucle.

Después de arreglar el problema inmediato, busca el paso de prevención: requests del tamaño adecuado, mejores etiquetas de imagen, una sonda de inicio, una verificación de secreto faltante en CI o un runbook más claro. El mejor incidente de pod es aquel que se vuelve más fácil de reconocer la próxima vez.