CreateContainerConfigError en Kubernetes: el ConfigMap o el Secret que no está

Aquí no falló tu aplicación ni el planificador: el contenedor nunca llegó a configurarse. El kubelet intentó armar su configuración, encontró una referencia a algo que no existe y se detuvo antes de crear nada. Por eso `kubectl logs` no devuelve nada útil.

Qué significa exactamente, y por qué no hay logs

El pod se programó bien: ya tiene un nodo asignado. El kubelet de ese nodo empezó a preparar el contenedor y necesitó resolver todo lo que el manifiesto referencia —variables de entorno desde un ConfigMap, credenciales desde un Secret— antes de entregárselo al runtime.

Una de esas referencias no se pudo resolver. El kubelet no crea un contenedor a medio configurar: aborta y deja el contenedor en `Waiting` con `reason: CreateContainerConfigError`, y el pod se queda en `Pending`.

Como no hay contenedor, no hay proceso, y sin proceso no hay salida. `kubectl logs` te va a decir que el contenedor no ha arrancado. Es el mismo malentendido que con ImagePullBackOff y cuesta la misma media hora: la respuesta no está en los logs, está en los eventos.

El comando que da la respuesta

`kubectl describe pod <nombre>` y bajar a Events. El kubelet escribe ahí el motivo textual, y ese texto casi siempre nombra el objeto exacto que falta.

`configmap "mi-config" not found` es un ConfigMap que no existe con ese nombre en ese namespace. `secret "mis-credenciales" not found` es lo mismo con un Secret.

`couldn't find key API_TOKEN in ConfigMap default/mi-config` es distinto y más engañoso: el objeto SÍ existe, pero la clave que pides no está dentro. Pasa cuando alguien renombró una clave y actualizó unos manifiestos y no otros.

Vale leer el mensaje completo antes de teorizar. A diferencia de otros errores de Kubernetes, este es literal: dice qué objeto y en qué namespace.

`CreateContainerConfigError` y `CreateContainerError` no son el mismo error

Se parecen en el nombre y casi todas las guías los tratan juntos, pero apuntan a capas distintas y eso cambia dónde buscar.

`CreateContainerConfigError` es de **configuración**: falta un ConfigMap, un Secret o una clave. El problema está en tus manifiestos o en un objeto que no se creó, y se arregla con `kubectl apply`.

`CreateContainerError` es de **creación**: la configuración estaba completa, pero el runtime no pudo crear el contenedor. Un volumen que no se puede montar, un nombre de contenedor ya en uso, un problema del propio nodo. Ahí no revisas el YAML de entrada, revisas el nodo.

Confundirlos te manda al lugar equivocado: revisar manifiestos cuando el problema es del nodo, o drenar un nodo cuando faltaba un Secret.

El namespace es la causa que más tiempo cuesta

La documentación de Kubernetes es explícita: el pod y el ConfigMap **deben estar en el mismo namespace**. No hay forma de referenciar un ConfigMap de otro namespace desde el `spec` de un pod.

Eso convierte una operación aparentemente inocente en una falla garantizada: copiar un Deployment que funciona de `staging` a `production` y aplicar solo el Deployment. El manifiesto es correcto, el nombre del ConfigMap es correcto, y el objeto existe — en el otro namespace.

Es la causa que más tiempo cuesta precisamente porque todo se ve bien. `kubectl get configmap mi-config` devuelve el objeto si estás en el namespace de siempre. La comprobación útil lleva el namespace explícito: `kubectl get configmap mi-config -n production`.

Lo mismo aplica a los Secret, incluidos los de registro. Un `imagePullSecret` que vive en otro namespace no existe para este pod.

Por qué el error aparece días después del cambio que lo causó

Esta es la parte que descoloca, y tiene una explicación precisa: las variables de entorno se inyectan **una sola vez, cuando se crea el contenedor**. La documentación de Kubernetes lo dice al revés pero es el mismo hecho: los ConfigMap consumidos como variables de entorno no se actualizan solos y requieren reiniciar el pod.

La consecuencia es que borrar o renombrar un ConfigMap **no rompe nada de inmediato**. Los pods que ya están corriendo tienen sus variables inyectadas y siguen funcionando sin enterarse. El cluster se ve sano.

El error aparece cuando algo obliga a crear un contenedor nuevo: un drenaje de nodo, una subida de réplicas del autoescalador, un reinicio por sonda fallida, un nodo que el proveedor recicla. Puede ser horas o días después del cambio, y a esa altura nadie relaciona las dos cosas.

Un detalle que agrava esto: los ConfigMap montados como **volumen** sí se actualizan solos, con el retraso del ciclo de sincronización del kubelet. Así que el mismo ConfigMap puede estar al día en un contenedor que lo monta y desactualizado en otro que lo lee por variables de entorno. Y si el montaje usa `subPath`, tampoco recibe actualizaciones.

La variante silenciosa: el pod arranca y la configuración no llega

Hay un caso peor que el error, porque no da error. La documentación de Kubernetes lo declara: los nombres de variable de entorno tienen un rango de caracteres restringido, y si una clave del ConfigMap no cumple esas reglas, esa clave **no se le entrega al contenedor aunque el pod sí arranca**.

Con `envFrom` esto es fácil de provocar sin darse cuenta. Un ConfigMap pensado para montarse como archivos suele tener claves como `application.properties` o `nginx.conf`. Consumido con `envFrom`, esas claves se descartan en silencio: el pod queda `Ready`, la aplicación arranca con valores por omisión y falla más tarde por una razón que no se parece a un problema de configuración.

El otro camino silencioso es deliberado: `optional: true` en la referencia. Le dice a Kubernetes que tolere la ausencia del objeto y arranque igual. Es útil para configuración de verdad opcional, y es una trampa si alguien lo puso para «que deje de fallar el despliegue» — convierte un error visible en un comportamiento raro sin causa aparente.

La comprobación directa: `kubectl exec <pod> -- env | sort` y verificar que las variables que esperabas están de verdad ahí. Un pod `Ready` no es un pod configurado.

El orden de aplicación, que es donde Helm y ArgoCD lo producen solos

Un Deployment y su ConfigMap suelen vivir en el mismo chart o en el mismo directorio, y nada garantiza que el ConfigMap llegue primero. Si el Deployment se aplica antes, sus pods entran en `CreateContainerConfigError` hasta que aparece el ConfigMap.

La mayoría de las veces se resuelve solo: el kubelet reintenta y el pod arranca cuando el objeto existe. Eso lo hace fácil de ignorar, y esconde el caso donde no se resuelve nunca porque el ConfigMap quedó en otra fase de sincronización o su plantilla falló.

En Helm el orden entre recursos del mismo hook no está garantizado más allá del orden de instalación por tipo; en ArgoCD se controla con ondas de sincronización. Si el patrón se repite en cada despliegue, es señal de que hay que declarar la dependencia en vez de confiar en la suerte.

Y un caso aparte: el `spec` de un pod estático no puede referenciar un ConfigMap ni ningún otro objeto del API. Si estás depurando un componente que corre como pod estático en el nodo, la referencia simplemente no es válida.

Cómo Moonin acorta esa parte

El diagnóstico de este error es corto cuando sabes qué comando correr. Lo que cuesta es la pregunta anterior: por qué ese ConfigMap dejó de estar, quién lo cambió y cuándo. Y como el síntoma puede aparecer días después de la causa, reconstruirlo desde `kubectl` no funciona: ahí no queda el historial.

Moonin mantiene el historial de revisiones de cada servicio y correlaciona los eventos del cluster con lo que cambió antes de ellos. Cuando un pod entra en `CreateContainerConfigError`, la pregunta «qué cambió» ya tiene respuesta en lugar de empezar una investigación.

También hace visible el patrón que se resuelve solo y por eso nadie ve: pods que entran en este estado en cada despliegue y arrancan treinta segundos después. No es una caída, así que no dispara alertas, pero es una dependencia sin declarar esperando el día en que el ConfigMap no llegue.

Ver qué cambió en tu propio cluster

La demo está abierta y sin formulario: puedes revisar cómo queda el historial de revisiones de un servicio con los cambios de configuración asociados, que es lo que hace corta la pregunta «desde cuándo».

Ver el historial de revisiones en la demo abierta · Cómo se cobra Moonin por Unidad de Cómputo

Preguntas frecuentes

¿Por qué `kubectl logs` no muestra nada con CreateContainerConfigError?

Porque no hay contenedor todavía. El kubelet se detuvo antes de crearlo, al no poder resolver una referencia del manifiesto, así que no existe ningún proceso que haya escrito una sola línea. El motivo está en los eventos del pod: `kubectl describe pod <nombre>` y leer la sección Events.

¿Cuál es la diferencia entre CreateContainerConfigError y CreateContainerError?

El primero es un problema de configuración: falta un ConfigMap, un Secret o una clave que el manifiesto referencia, y se arregla aplicando el objeto que falta. El segundo es un problema de creación: la configuración estaba completa pero el runtime no pudo crear el contenedor, por ejemplo un volumen que no se monta o un nombre de contenedor ya en uso. Uno te manda a los manifiestos y el otro al nodo.

¿Puede un pod usar un ConfigMap de otro namespace?

No. La documentación de Kubernetes es explícita: el pod y el ConfigMap deben estar en el mismo namespace, y el `spec` de un pod no tiene forma de referenciar uno ajeno. Por eso copiar un Deployment de un namespace a otro sin copiar sus ConfigMap y Secret falla siempre, aunque el manifiesto sea idéntico y correcto.

¿Por qué el error apareció días después de borrar el ConfigMap?

Porque las variables de entorno se inyectan una sola vez, al crear el contenedor. Los pods que ya corrían siguieron funcionando con los valores que recibieron y el cluster se veía sano. El error aparece cuando algo fuerza un contenedor nuevo: un drenaje de nodo, una subida de réplicas, un reinicio por sonda o un nodo reciclado por el proveedor.

¿Sirve poner `optional: true` para que el pod arranque?

Arranca, pero rara vez es lo que quieres. `optional: true` le dice a Kubernetes que tolere que el objeto no exista, así que la aplicación parte sin esa configuración y falla más tarde por una razón que no se parece a un problema de configuración. Es correcto para configuración genuinamente opcional y es una trampa cuando se usa para silenciar un despliegue que falla.