CrashLoopBackOff no es un error: es el estado en que Kubernetes te avisa que se rindió de reintentar tan seguido. El contenedor arrancó, se murió, y el kubelet lo volvió a levantar hasta que decidió esperar más entre intentos.
El kubelet arranca tu contenedor. El contenedor termina —con error o sin él—. El kubelet lo reinicia. Si vuelve a terminar, espera antes del siguiente intento, y esa espera se duplica: 10 segundos, 20, 40, 80, hasta un techo de cinco minutos.
Ese «esperar más cada vez» es el BackOff del nombre. Cuando lo ves en `kubectl get pods`, el pod puede llevar minutos sin intentar nada, así que la última línea de log que veas puede ser de hace rato.
Un detalle que confunde: un contenedor que termina con éxito también entra en este estado. Si tu comando hace su trabajo y sale con código 0, Kubernetes lo reinicia igual, porque un Deployment espera un proceso que no termine. Eso no es una falla de la aplicación, es un contenedor mal diseñado para el objeto que lo corre.
Primero los eventos y el estado anterior. `kubectl describe pod <nombre>` te muestra al final la sección Events y, más arriba, `Last State` con el código de salida y la razón. Eso solo suele bastar.
Después, y esta es la parte que la mayoría se salta: `kubectl logs <nombre> --previous`. Sin `--previous` estás leyendo el contenedor actual, que probablemente todavía no arrancó o ya está muerto. Con `--previous` lees los logs del intento que falló, que es donde está la causa.
Si el pod tiene varios contenedores, agrega `-c <contenedor>`. Y si necesitas ver la secuencia completa, `kubectl get events --sort-by=.lastTimestamp` en el namespace ordena todo lo que pasó.
**137** significa que el proceso recibió SIGKILL. En la enorme mayoría de los casos es el kernel matándolo por memoria: el contenedor pasó su límite. `kubectl describe` lo confirma con `Reason: OOMKilled`. Es un problema distinto y tiene su propia página.
**143** es SIGTERM: alguien pidió que terminara ordenadamente. Suele ser una sonda de liveness que declaró al contenedor no sano y provocó el reinicio, no una falla de la aplicación en sí.
**1** o **2** es la aplicación fallando por su cuenta: una variable de entorno que falta, un secreto que no está montado, una base de datos que no responde en el arranque, un archivo de configuración inválido. Los logs con `--previous` lo dicen.
**0** es el caso del contenedor que terminó bien y no debía terminar. Revisa el `command` y el `entrypoint`.
**127** es «comando no encontrado», casi siempre un `command` mal escrito o un binario que no existe en esa imagen.
Una causa muy frecuente y difícil de ver: la aplicación está bien, pero tarda más en arrancar que lo que la sonda de liveness tolera. La sonda falla, el kubelet mata el contenedor, y el ciclo se repite para siempre.
Se reconoce porque los logs no muestran ningún error de la aplicación y el código de salida es 143. En `describe` aparecen eventos de `Liveness probe failed`.
El arreglo correcto no es alargar la liveness: es usar una `startupProbe`, que existe justamente para dar tiempo de arranque sin aflojar la vigilancia posterior. Subir el `initialDelaySeconds` de la liveness funciona pero deja al servicio sin vigilancia durante todo ese rato.
Y conviene no confundir las tres sondas, porque hacen cosas distintas: la de readiness decide si el pod recibe tráfico, la de liveness decide si lo matan, y la de startup suspende a las otras dos hasta que el arranque termine. Una readiness que falla saca al pod del balanceador sin reiniciarlo, así que un servicio que no recibe tráfico pero tampoco reinicia es un problema de readiness, no de liveness.
Esta es la parte que la mayoría de las guías omite. Cuando un servicio que llevaba semanas estable entra en CrashLoopBackOff, la aplicación no se corrompió sola: algo cambió. Una imagen nueva, un ConfigMap editado, un Secret rotado, un límite de memoria bajado en un commit de la semana pasada, una variable renombrada.
El problema práctico es que esa información está repartida. La imagen está en el Deployment, el commit en el repositorio, el ConfigMap en otro objeto, y el momento del despliegue en el historial de tu herramienta de CI — si la retención no lo borró.
Reconstruir esa secuencia a mano en medio de un incidente es lo que estira el tiempo de recuperación, el MTTR. No porque el diagnóstico sea difícil, sino porque juntar los datos toma más que resolverlo.
Moonin mantiene el historial de revisiones de cada servicio: qué imagen, qué commit, qué cambió respecto de la revisión anterior y a qué hora se desplegó. Cuando un pod entra en CrashLoopBackOff, la pregunta «qué cambió antes de esto» tiene una respuesta en una pantalla en vez de en cuatro.
También registra los reinicios y los relaciona con la revisión que estaba activa, así que un servicio que reinicia desde el martes se distingue de uno que empezó hace diez minutos.
Se instala como un solo Helm chart por cluster y no requiere instrumentar código: las trazas entre servicios se obtienen con eBPF en el kernel del nodo. Ese agente necesita privilegios en el nodo para leer el tráfico del kernel; el acceso al API de Kubernetes es de solo lectura.
Lo que no hace, para que no haya sorpresa: no reinicia nada, no revierte despliegues y no toma acciones por su cuenta. Te dice qué cambió; el rollback lo haces tú.
Si el pod entra en CrashLoopBackOff solo en algunos nodos, mira el nodo y no el despliegue: presión de memoria, disco lleno, una versión distinta del runtime de contenedores, o arquitectura distinta si tienes nodos mezclados arm64 y amd64.
Si todos los servicios que dependen de una misma base de datos entran en el estado a la vez, el problema está en la dependencia externa y ningún historial de despliegues lo va a mostrar.
Y si el pod nunca llegó a arrancar, revisa que el estado sea CrashLoopBackOff y no ImagePullBackOff o Pending: son problemas distintos con causas distintas, y confundirlos hace perder la primera media hora.
La demo está abierta y sin formulario, sobre un cluster real con datos de ejemplo: puedes revisar cómo se ve el historial de revisiones y los reinicios antes de instalar nada.
ConócenosLicenciamientoSeguridad y permisosPrograma de partnersvs Datadogvs Prometheus y Grafanavs New Relicvs Dynatracevs ELK y ElasticsearchOOMKilledImagePullBackOffPod en PendingDocumentaciónDemoRegístrateContáctanosArguz, la consultora
La espera se duplica en cada intento: 10 segundos, 20, 40, 80, y así hasta un techo de cinco minutos, donde se queda. Por eso un pod en CrashLoopBackOff puede llevar rato sin hacer nada, y la última línea de log que ves puede ser de varios minutos antes.
Porque estás leyendo el contenedor actual, que probablemente aún no arrancó o ya murió. Usa `kubectl logs <pod> --previous`, que lee los logs del intento anterior — el que falló. Si el pod tiene varios contenedores, agrega `-c <contenedor>`.
Que el proceso recibió SIGKILL, y casi siempre es el kernel matándolo por memoria: el contenedor superó su límite. `kubectl describe pod` lo confirma con `Reason: OOMKilled`. Subir el límite desbloquea, pero si la causa es una fuga de memoria solo aplaza el problema.
No. OOMKilled es una de las razones por las que un contenedor muere; CrashLoopBackOff es el estado del pod después de que murió varias veces. Un pod puede estar en CrashLoopBackOff con código 137 (fue OOMKilled) o con código 1 (la aplicación falló sola), y el diagnóstico es distinto.
Necesitas el historial de despliegues: qué imagen y qué commit estaban activos antes y después, y a qué hora. `kubectl rollout history` te da las revisiones del Deployment pero no el commit ni la hora exacta, y la retención suele ser corta. Moonin mantiene ese historial por servicio y relaciona los reinicios con la revisión que estaba activa.