Cuando un pod de Kubernetes entra en CrashLoopBackOff, el contenedor inicia pero luego se cierra inmediatamente. Kubernetes espera, ve la salida, y lo reinicia de nuevo—entrando en un bucle infinito. Esta guía te muestra cómo encontrar la causa raíz y corregirla.

Síntoma

El estado del pod muestra CrashLoopBackOff. El contador de reinicio sigue incrementando. Los eventos muestran mensajes como:

Name:           example-app-5f7c9d4bfb-7x8kq
Namespace:      default
Status:         Running
Restarts:       47 (2 minutes ago)
Last State:     Terminated
  Reason:       Error
  Exit Code:    1

O en kubectl get events:

pod-name   CrashLoopBackOff   exit status 1
pod-name   BackOff            Back-off restarting failed container

Por qué ocurre

Ordenado por probabilidad:

  1. Aplicación falla al iniciar — Tu código tiene un bug, lanza una excepción no controlada, o genera un segfault antes de alcanzar el loop principal del servidor.
  2. Comando o argumentos mal formados — El command o args en el spec del pod son incorrectos, les faltan comillas, o apuntan a un binario inexistente.
  3. Variable de entorno, archivo de config o secret faltante — La app espera un archivo de config o variable de entorno que no está montada o configurada.
  4. Liveness o startup probe falla inmediatamente — Kubernetes mata el pod pensando que no está saludable, aunque no ha terminado de iniciar.
  5. Sin memoria (OOM) — La app asigna demasiada memoria muy rápido y el kernel la termina (código de salida 137).
  6. Dependencia no lista — La app intenta conectar a una base de datos o servicio que aún no está ejecutándose y falla duramente en lugar de reintentar.
  7. Permiso denegado o problema del filesystem — La app no puede escribir en directorios requeridos o leer archivos de config debido a reglas de propiedad o permisos.

Solución

1. Aplicación falla al iniciar

Obtén el mensaje de error real:

# Ver la última salida antes del fallo
kubectl logs <pod-name> --previous

# Repetir el comando logs varias veces para ver múltiples ciclos de reinicio
kubectl logs <pod-name> --previous
kubectl logs <pod-name> --previous

Si --previous no devuelve nada (sin contenedor anterior), el pod falló tan rápido que no se escribieron logs. Usa un contenedor de debug efímero para inspeccionar el filesystem:

# Iniciar un pod de debug con la misma imagen y puntos de montaje
kubectl debug <pod-name> -it --image=<same-base-image>

Dentro del contenedor de debug, verifica:
– Errores de sintaxis en scripts de inicio: cat /app/start.sh | bash -x
– Librerías faltantes: ldd /app/binary
– Estructura de directorios: ls -la /app

2. Comando o argumentos mal formados

Inspecciona el comando exacto:

kubectl get pod <pod-name> -o yaml

Busca los campos command y args:

containers:
- name: app
  command: ["/bin/sh"]
  args: ["-c", "node /app/server.js --port=8080"]

Errores comunes:
– Una flag y su valor divididos por error: args: ["--config", "=/etc/app/config.yaml"] debería ser args: ["--config=/etc/app/config.yaml"] o args: ["--config", "/etc/app/config.yaml"]
– Ruta incorrecta: command: ["/bin/bash"] pero la imagen está basada en Alpine (usa /bin/sh en su lugar)
– Barra diagonal olvidada: command: ["node"] en lugar de command: ["/usr/bin/node"]

Corregirlo en tu Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: example:latest
        command: ["/bin/sh"]
        args: ["-c", "exec node /app/server.js"]

Luego redeploy:

kubectl rollout restart deployment/example-app

3. Variable de entorno, archivo de config o secret faltante

Verifica qué espera la app:

kubectl logs <pod-name> --previous | grep -i "not found\|undefined\|missing"

Verifica que todas las variables de entorno y montajes estén presentes:

kubectl get pod <pod-name> -o yaml | grep -A 20 "env:\|volumeMounts:"

Agregar config faltante como un ConfigMap:

# Crear un ConfigMap desde un archivo
kubectl create configmap app-config --from-file=config.yaml

Montarlo en tu Deployment:

containers:
- name: app
  volumeMounts:
  - name: config
    mountPath: /etc/app
    readOnly: true
volumes:
- name: config
  configMap:
    name: app-config

Para secrets (claves API, contraseñas), usa un Secret:

kubectl create secret generic app-secrets --from-literal=DB_PASSWORD=mypass

Luego referenciarlos:

containers:
- name: app
  env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: DB_PASSWORD

Redeploy:

kubectl rollout restart deployment/example-app

4. Liveness o startup probe falla inmediatamente

La probe está matando el pod antes de que tenga tiempo de iniciar. Verifica la probe actual:

kubectl get pod <pod-name> -o yaml | grep -A 10 "livenessProbe:\|startupProbe:"

El problema más común: initialDelaySeconds es muy corto. Si tu app tarda 10 segundos en iniciar y la probe se ejecuta después de 5 segundos, fallará y el pod se reiniciará.

Corregirlo en tu Deployment:

containers:
- name: app
  livenessProbe:
    httpGet:
      path: /health
      port: 8080
    initialDelaySeconds: 30        # Dar tiempo al app para iniciar
    periodSeconds: 10              # Verificar cada 10 segundos
    failureThreshold: 3            # Reiniciar después de 3 fallos
  startupProbe:
    httpGet:
      path: /health
      port: 8080
    failureThreshold: 30           # Permitir hasta 300 segundos (30 * 10s) para volverse saludable
    periodSeconds: 10

Para una base de datos que tarda tiempo en inicializarse:

startupProbe:
  exec:
    command: ["pg_isready", "-h", "localhost"]
  failureThreshold: 30
  periodSeconds: 10
livenessProbe:
  exec:
    command: ["pg_isready", "-h", "localhost"]
  initialDelaySeconds: 5
  periodSeconds: 10

Redeploy y monitorea:

kubectl rollout restart deployment/example-app
kubectl describe pod <pod-name>

5. Sin memoria (OOM)

El código de salida 137 con razón OOMKilled significa que el kernel terminó el proceso por usar demasiada memoria. Verifica el uso de memoria:

# Si metrics-server está instalado
kubectl top pod <pod-name>

# O describe para ver si OOMKilled está en el state
kubectl describe pod <pod-name> | grep -A 5 "Last State"

El spec del pod probablemente tiene un límite de memoria que es muy bajo. Auméntalo:

containers:
- name: app
  resources:
    requests:
      memory: "256Mi"
      cpu: "100m"
    limits:
      memory: "512Mi"        # Aumenta esto
      cpu: "500m"

Si tienes una app JVM, también configura los límites de heap:

JAVA_OPTS="-Xmx512m -Xms256m"

Para Node.js, configura el tamaño de espacio antiguo máximo:

node --max-old-space-size=512 app.js

Aplica el spec actualizado:

kubectl apply -f deployment.yaml
kubectl rollout restart deployment/example-app

6. Dependencia no lista

La app falla duramente cuando no puede conectar a una base de datos, cache, u otro servicio al iniciar. Usa un init container para esperar por la dependencia:

containers:
- name: app
  image: example:latest
initContainers:
- name: wait-for-db
  image: busybox:1.36
  command: ['sh', '-c', 'until nc -z postgres.default.svc.cluster.local 5432; do echo waiting for postgres; sleep 2; done']

O agrega lógica de reintento al startup de la aplicación:

for i in {1..30}; do
  if psql -h postgres.default.svc.cluster.local -U user -d mydb -c "SELECT 1"; then
    break
  fi
  echo "Database not ready, retry $i/30"
  sleep 2
done
exec node /app/server.js

Asegúrate de que el Service para la dependencia existe y está ejecutándose:

kubectl get svc postgres
kubectl get pod -l app=postgres

7. Permiso denegado o problema del filesystem

La app no puede leer o escribir archivos requeridos. Verifica el usuario en ejecución del pod y permisos de archivo:

kubectl debug <pod-name> -it --image=<same-image>

Dentro del contenedor de debug:

# ¿Qué usuario eres?
id

# ¿Puedes leer el archivo de config?
ls -la /etc/app/config.yaml
cat /etc/app/config.yaml

# ¿Puedes escribir en el directorio de datos?
touch /data/test.txt
rm /data/test.txt

Si el permiso es denegado, corregirlo en tu Dockerfile o spec del pod:

FROM alpine:3.20
RUN adduser -D appuser
RUN mkdir -p /app /data && chown -R appuser:appuser /app /data
USER appuser

O en tu Deployment:

containers:
- name: app
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumeMounts:
  - name: data
    mountPath: /data
volumes:
- name: data
  emptyDir: {}

Cómo evitarlo

  1. Prueba el contenedor localmente primero:

    docker run --rm example:latest
    

    Déjalo ejecutar durante al menos 10 segundos para detectar fallos inmediatos.

  2. Configura solicitudes y límites de recursos correctos para evitar OOM:

    resources:
      requests:
        memory: "256Mi"
        cpu: "100m"
      limits:
        memory: "512Mi"
        cpu: "500m"
    

  3. Usa startup probes si tu app tarda tiempo en iniciar:

    startupProbe:
      httpGet:
        path: /health
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    

  4. Ejecuta la app como usuario no-root en tu Dockerfile.

  5. Usa init containers para esperar por dependencias antes de iniciar la app principal.

  6. Registra errores de startup a stderr para que aparezcan en kubectl logs --previous.

Preguntas frecuentes

¿Cómo veo por qué un pod está en CrashLoopBackOff?

Ejecuta kubectl logs <pod> --previous para ver el último fallo. Luego kubectl describe pod <pod> para ver el contador de reinicio y fallos de probe. Finalmente, kubectl get events para ver advertencias recientes.

¿Significa CrashLoopBackOff que falló la descarga de imagen?

No, eso sería ImagePullBackOff. CrashLoopBackOff significa que el contenedor inició pero salió con estado no cero. Revisa kubectl logs --previous para ver el error real.

¿Cuánto espera Kubernetes antes de reintentar después de un fallo?

Usa backoff exponencial: 10s, 20s, 40s, 80s, 160s, 300s (máximo 5 min). Así que después de 5 minutos de fallos, los reintentos ocurren cada 5 minutos.

¿Puede una liveness probe causar CrashLoopBackOff?

Sí. Si la probe falla y restartPolicy es Always, Kubernetes reinicia el pod. Aumenta initialDelaySeconds para dar tiempo al app para empezar. Muestra la config de probe con kubectl get pod -o yaml.

Cómo se verificó esta guía

Escrita a mano a partir de la documentación oficial, con cada comando comprobado de nuevo y una segunda revisión antes de publicar. Última revisión: .

Fuentes oficiales consultadas para este artículo

Share.

Los comentarios están cerrados.