ImagePullBackOff y ErrImagePull: las siete causas y cómo distinguirlas

Aquí la aplicación no falló: nunca arrancó. Kubernetes no pudo bajar la imagen, y por eso buscar en los logs no sirve — no hay logs, porque no hay contenedor. El mensaje que necesitas está en los eventos del pod.

ErrImagePull e ImagePullBackOff son el mismo problema en dos momentos

`ErrImagePull` es el primer intento fallando. `ImagePullBackOff` es Kubernetes esperando antes de volver a intentar, con la espera creciendo en cada vuelta.

Si ves alternar los dos estados en `kubectl get pods`, no son dos problemas: es el mismo reintentándose. La causa está en un solo lugar.

Y a diferencia de CrashLoopBackOff, `kubectl logs` no te va a dar nada útil. El contenedor no existe todavía. Confundir los dos estados es lo que hace perder la primera media hora.

El comando que da la respuesta

`kubectl describe pod <nombre>` y bajar a la sección Events. Ahí está el mensaje textual que devolvió el registro, y ese mensaje distingue entre todas las causas de abajo.

«manifest unknown» o «not found» es una etiqueta que no existe. «unauthorized» o «authentication required» es credenciales. «toomanyrequests» es límite de descargas. «no match for platform» es arquitectura.

Vale la pena leer el mensaje completo antes de teorizar: el registro ya dijo qué pasa y casi siempre es literal.

Las causas, de la más frecuente a la más rara

**La etiqueta no existe.** Un error de tipeo, o `:latest` apuntando a algo que se borró, o una etiqueta que el pipeline nunca llegó a publicar porque el build falló y nadie revisó. Se comprueba desde tu máquina con `docker manifest inspect <imagen>:<etiqueta>`.

**Falta el imagePullSecret.** El registro es privado y el pod no tiene credenciales. Aparece como `unauthorized`. Ojo con un detalle: el secreto tiene que estar en el **mismo namespace** que el pod. Copiar el Deployment a un namespace nuevo y olvidar el secreto es la versión más común de esto.

**Límite de descargas del registro público.** Docker Hub limita las descargas anónimas por dirección IP, y en Kubernetes todos los nodos detrás de un NAT comparten la misma. Un cluster mediano lo alcanza sin hacer nada raro. Se ve como `toomanyrequests`. La solución es autenticarse aunque la imagen sea pública, o usar un espejo interno.

**Arquitectura equivocada.** Desde que hay nodos arm64 junto a amd64 —Graviton en AWS, Axion en GCP— una imagen construida solo para amd64 falla en el nodo arm con `no match for platform`. Lo desconcertante es que funciona en unos nodos y no en otros, lo que parece intermitente y no lo es.

**Credenciales vencidas.** Los tokens de registro caducan. Un pod que llevaba meses funcionando falla al reprogramarse, porque el token se renovó en el pipeline pero no en el secreto del cluster.

**El registro no responde.** Corte del proveedor, o una regla de red que bloquea la salida desde los nodos. Se distingue porque falla para todas las imágenes de ese registro a la vez.

**La imagen es enorme y el intento expira.** Poco frecuente, pero pasa con imágenes de varios gigabytes en nodos con disco lento o red saturada.

Por qué `:latest` empeora todo esto

Con una etiqueta móvil como `:latest`, la imagen que se baja hoy no es la que se bajó ayer, y no queda registro de cuál era cuál. Un pod que se reprograma puede traer una versión distinta a la de sus hermanos, y entonces el mismo Deployment corre dos códigos a la vez.

Peor: cuando falla, no puedes saber qué imagen estaba corriendo antes, porque la etiqueta apunta a otra cosa. El rollback pierde su referencia.

Fijar la etiqueta por digest —`imagen@sha256:...`— o al menos por versión inmutable convierte este problema en uno reproducible. Es la práctica que más dolor evita por menos trabajo.

Si antes funcionaba, algo cambió — y aquí es casi seguro

En esta falla el cambio es casi la definición del problema. Una etiqueta nueva que el pipeline no publicó, un secreto rotado, un namespace nuevo sin credenciales, un nodo arm agregado por el autoescalador a un cluster que hasta ayer era todo amd64.

Ese último caso es especialmente esquivo con autoescaladores que eligen el tipo de instancia por precio, como Karpenter: el cluster puede empezar a recibir nodos de una arquitectura que nadie pidió explícitamente, y las imágenes de un solo arco empiezan a fallar de forma aparentemente aleatoria.

El dato que resuelve es qué imagen exacta estaba activa antes del primer fallo, y en qué nodo corren los pods que sí funcionan.

Cómo Moonin acorta esa parte

Moonin mantiene el historial de revisiones de cada servicio con la imagen exacta y el commit, así que la imagen que estaba activa antes del fallo es un dato, no una arqueología del pipeline.

Y mantiene el inventario de recursos del cluster, incluidos los nodos y sus características, de modo que un cluster con arquitecturas mezcladas se ve como tal en vez de descubrirse cuando algo falla.

Lo que no hace: no corrige manifiestos, no crea secretos y no reintenta descargas. Te dice qué imagen cambió y cuándo; el arreglo es tuyo.

Ver el historial de tu propio cluster

La demo está abierta y sin formulario: puedes revisar cómo se ve el historial de imágenes por servicio y el inventario de nodos, antes de instalar nada.

ConócenosLicenciamientoSeguridad y permisosPrograma de partnersvs Datadogvs Prometheus y Grafanavs New Relicvs Dynatracevs ELK y ElasticsearchCrashLoopBackOffOOMKilledPod en PendingDocumentaciónDemoRegístrateContáctanosArguz, la consultora

Preguntas frecuentes

¿Por qué kubectl logs no muestra nada?

Porque el contenedor nunca arrancó: Kubernetes no pudo bajar la imagen, así que no hay proceso que haya escrito logs. El mensaje que necesitas está en `kubectl describe pod <nombre>`, en la sección Events, y ahí el registro dice textualmente qué falló.

¿Cuál es la diferencia entre ErrImagePull e ImagePullBackOff?

Son el mismo problema en dos momentos. ErrImagePull es el intento fallando; ImagePullBackOff es Kubernetes esperando antes de reintentar, con la espera creciendo cada vez. Si los ves alternar, no son dos causas distintas.

¿Por qué me da toomanyrequests si la imagen es pública?

Porque Docker Hub limita las descargas anónimas por dirección IP, y en Kubernetes todos los nodos detrás del mismo NAT comparten una. Un cluster mediano alcanza el límite sin hacer nada inusual. Autenticarse aunque la imagen sea pública sube el límite, y un espejo interno lo elimina.

Funciona en algunos nodos y falla en otros, ¿por qué?

Casi siempre arquitectura. Si tienes nodos arm64 junto a amd64 —Graviton en AWS, Axion en GCP— una imagen construida solo para amd64 falla en los nodos arm con `no match for platform`. Parece intermitente pero es determinista: depende de en qué nodo cayó el pod. La solución es publicar la imagen para las dos arquitecturas.

¿Por qué mi pod falla solo en un namespace nuevo?

Porque los imagePullSecrets viven en el namespace. Copiar un Deployment a otro namespace sin copiar el secreto produce exactamente este error, con mensaje `unauthorized`, aunque el mismo Deployment funcione perfecto en el namespace original.