Módulo 3: Configuration Secrets Health And Autoscaling
2. ConfigMaps: separar configuración de imagen
Descripción
app.py —el mismo archivo, sin ningún cambio, desde aws-serverless-and-containers-guide, Módulo 6— lee tres variables de entorno para saber a qué base de datos conectarse: DYNAMODB_ENDPOINT_URL, AWS_REGION, SHIPMENTS_TABLE_NAME. Hoy, el Deployment de esta guía no define ninguna de las tres — es exactamente por eso que /shipments/<id> sigue fallando desde el Módulo 2. Esta lección explica dónde deberían vivir esas tres variables, y por qué la respuesta correcta nunca es "dentro del Dockerfile".
Conexión con el módulo
Esta es la primera de las tres piezas que la lección 1 prometió. La lección 3 agrega la variante sensible del mismo problema (Secret), y la lección 4 monta ambas, de verdad, en andes-cargo-status-api — sin reconstruir la imagen ni una sola vez.
El problema exacto: una imagen construida una vez, un endpoint que cambia por entorno
El Dockerfile que esta guía hereda —línea por línea, sin reescribir nada— se construyó una vez, en el Módulo 1 de esta guía (kind load docker-image, reutilizando la imagen de aws-serverless-and-containers-guide). Esa misma imagen, en principio, podría correr en varios lugares distintos: este clúster kind de laboratorio, un clúster de staging, un EKS real de producción. En cada uno de esos lugares, DYNAMODB_ENDPOINT_URL necesitaría un valor distinto —porque en cada uno, "la base de datos" es un servicio distinto, en una dirección distinta.
Si ese valor viviera hardcodeado dentro del Dockerfile (por ejemplo, ENV DYNAMODB_ENDPOINT_URL=http://localstack.localstack.svc.cluster.local:4566), pasaría algo insostenible: cada vez que el endpoint cambiara —de laboratorio a staging, de staging a producción—, haría falta reconstruir la imagen entera, solo para cambiar un valor de texto que no tiene absolutamente nada que ver con el código de la aplicación. Eso rompe el principio que ya viste nombrado en aws-serverless-and-containers-guide, Módulo 6: "build once, deploy many" — construir una imagen una sola vez, y que esa misma imagen, sin recompilar nada, sirva para cualquier entorno donde la despliegues, cambiando solo lo que rodea al contenedor, nunca lo que hay dentro.
LA PREGUNTA QUE UN ConfigMap RESPONDE
Sin ConfigMap Con ConfigMap
(mal — configuración (bien — configuración
hardcodeada en la imagen) externa, fuera de la imagen)
Dockerfile Dockerfile
ENV DYNAMODB_ENDPOINT_URL=... (sin ningún ENV de config)
│ │
▼ ▼
Cambiar el endpoint Cambiar el endpoint
= reconstruir la imagen = kubectl apply de un YAML nuevo,
entera, en cada entorno la misma imagen sin tocar
Qué es un ConfigMap
Un ConfigMap es un objeto de Kubernetes —igual de "real" que un Pod o un Deployment, con su propio apiVersion/kind/metadata— cuyo único propósito es guardar pares clave-valor de configuración no sensible: endpoints, nombres de tabla, banderas de comportamiento, cualquier cosa que un Pod necesite saber pero que no sea un secreto que proteger (eso es el tema de la lección 3). Vive en un namespace, igual que el Deployment y el Service que ya conoces, y se referencia desde el Pod que lo necesita — nunca se copia dentro de la imagen.
Demo: crear un ConfigMap de forma imperativa
Antes de construir el ConfigMap real de andes-cargo-status-api (eso es la lección 4), confirma el mecanismo con un ejemplo genérico, desechable, en el mismo namespace andes-cargo:
kubectl create configmap demo-config -n andes-cargo \
--from-literal=GREETING=hello \
--from-literal=EXAMPLE_MODE=demo
Qué esperar (literal, ejecutado):
configmap/demo-config created
Inspecciona lo que acabas de crear:
kubectl describe configmap demo-config -n andes-cargo
Qué esperar (literal):
Name: demo-config
Namespace: andes-cargo
Labels: <none>
Annotations: <none>
Data
====
EXAMPLE_MODE:
----
demo
GREETING:
----
hello
BinaryData
====
Events: <none>
Y en formato YAML, la representación completa del objeto que Kubernetes guardó:
kubectl get configmap demo-config -n andes-cargo -o yaml
Qué esperar (literal — creationTimestamp/resourceVersion/uid son tus valores variables):
apiVersion: v1
data:
EXAMPLE_MODE: demo
GREETING: hello
kind: ConfigMap
metadata:
creationTimestamp: "2026-08-14T19:55:20Z"
name: demo-config
namespace: andes-cargo
resourceVersion: "4033"
uid: 9e51d1a5-239f-49ff-b193-da8bcf940c54
Fíjate en algo importante para la lección 3: los valores (hello, demo) aparecen en texto plano, legibles a simple vista con un kubectl get -o yaml. Un ConfigMap nunca oculta ni protege nada — es, literalmente, una tabla de texto. Eso está perfectamente bien para un endpoint o un nombre de tabla, y sería un problema serio para una credencial (el tema exacto de la siguiente lección).
Limpia el objeto de demostración, ya cumplió su propósito:
kubectl delete configmap demo-config -n andes-cargo
Qué esperar:
configmap "demo-config" deleted from andes-cargo namespace
Dos formas de montar un ConfigMap en un Pod
Un ConfigMap existe como objeto independiente, pero no hace nada por sí solo hasta que un Pod lo referencia. Hay dos mecanismos, y esta guía usa el primero:
| Mecanismo | Cómo se ve dentro del contenedor | Cuándo usarlo |
|---|---|---|
envFrom/env (el que usa esta guía) | Cada clave del ConfigMap se convierte en una variable de entorno | Cuando la aplicación ya lee configuración de os.environ (como app.py), sin ningún cambio de código |
volumeMounts | Cada clave se convierte en un archivo dentro de un directorio montado | Cuando la aplicación espera un archivo de configuración (.ini, .json, un certificado) en disco |
app.py lee os.environ.get("DYNAMODB_ENDPOINT_URL") — exactamente el patrón que pide envFrom. La lección 4 va a usar envFrom con dos referencias, una al ConfigMap y otra al Secret, para que todas las claves de ambos objetos aparezcan como variables de entorno dentro del contenedor, sin declarar cada una por separado.
# forma abreviada — se ve completa en la lección 4
envFrom:
- configMapRef:
name: andes-cargo-status-api-config
Errores comunes
Poner una credencial dentro de un ConfigMap "porque es más simple que crear dos objetos" (de disciplina, el error más peligroso de esta lección). Qué pasa: alguien, apurado, mete AWS_ACCESS_KEY_ID junto con DYNAMODB_ENDPOINT_URL en el mismo ConfigMap, razonando que ambos terminan siendo variables de entorno de todas formas. Por qué pasa: técnicamente funciona —un ConfigMap puede contener cualquier texto—, así que no hay ningún error inmediato que lo detenga. Cómo detectarlo: si corres kubectl get configmap <nombre> -o yaml y ves algo que se parece a una credencial en texto plano en la salida. Cómo corregirlo: la lección 3 explica exactamente por qué esto es un antipatrón —un ConfigMap no tiene ninguna de las protecciones adicionales de un Secret (que tampoco son cifrado, pero sí una separación de intención y de control de acceso vía RBAC)—. La regla dura de esta guía: si el valor es sensible, va en un Secret, sin excepción, aunque sea solo test/test.
Esperar que editar un ConfigMap existente actualice, solo, las variables de entorno de los Pods que ya corren (conceptual, muy común). Qué pasa: alguien corre kubectl edit configmap sobre un ConfigMap que un Deployment ya está usando vía envFrom, espera ver el cambio reflejado de inmediato dentro de los Pods, y no pasa nada. Por qué pasa: las variables de entorno se inyectan una sola vez, en el momento exacto en que el contenedor arranca — a diferencia de un ConfigMap montado como archivo (volumeMounts), que sí se actualiza eventualmente sin reiniciar el Pod, una variable de entorno ya inyectada no cambia mientras el proceso siga corriendo. Cómo detectarlo: si cambias un ConfigMap y el comportamiento del Pod no cambia hasta que lo reinicias. Cómo corregirlo: para que un cambio de ConfigMap (montado como variable de entorno) se refleje, hace falta recrear los Pods que lo usan — normalmente, con un kubectl rollout restart deployment/<nombre>. Vas a confirmar esto exactamente en la lección 4, cuando el Deployment haga un RollingUpdate real después de agregar el envFrom.
Olvidar -n andes-cargo y crear el ConfigMap en el namespace equivocado (de configuración, el mismo patrón del Módulo 2). Qué pasa: alguien corre kubectl create configmap sin la bandera -n, el objeto se crea en default, y el Deployment de andes-cargo —que busca en su propio namespace— nunca lo encuentra. Cómo detectarlo: kubectl get configmap -n andes-cargo no muestra el objeto que acabas de crear, pero kubectl get configmap (sin bandera) sí. Cómo corregirlo: un ConfigMap, igual que un Deployment o un Service, es un recurso con alcance de namespace — solo lo puede referenciar un Pod que viva en el mismo namespace. La lección 4 aplica esta regla con el andes-cargo-status-api-config real.
Ejercicios
Ejercicio 1 — Explica el problema sin usar la palabra "ConfigMap". En dos o tres frases, explica a un colega por qué hardcodear DYNAMODB_ENDPOINT_URL dentro del Dockerfile sería un problema real, sin nombrar ningún objeto específico de Kubernetes.
Ver solución
Una respuesta razonable: "La misma imagen Docker debería poder correr en distintos entornos —tu laboratorio, staging, producción— sin reconstruirse. Si el endpoint de la base de datos estuviera fijo dentro de la imagen, cambiar de entorno significaría reconstruir la imagen entera solo para cambiar un valor de texto, algo que no tiene ninguna relación con el código de la aplicación en sí."
Ejercicio 2 — Elige envFrom o volumeMounts. Para cada uno de estos dos casos, indica cuál de los dos mecanismos de montaje usarías: (a) una aplicación Python que lee os.environ.get("API_URL"); (b) una aplicación que espera un archivo config.json en /etc/app/config.json.
Ver solución
(a) envFrom/env — la aplicación ya espera variables de entorno, el mecanismo correcto es convertir las claves del ConfigMap directamente en variables. (b) volumeMounts — la aplicación espera un archivo real en una ruta específica, así que el ConfigMap debe montarse como volumen, donde cada clave se convierte en un archivo dentro del directorio montado.
Ejercicio 3 — Predice qué pasa si editas un ConfigMap montado como envFrom. Un Deployment ya tiene tres Pods corriendo, con un ConfigMap montado vía envFrom. Editas el ConfigMap y cambias un valor. ¿Los tres Pods existentes ven el nuevo valor de inmediato? Si no, ¿qué hace falta para que lo vean?
Ver solución
No, los tres Pods existentes no ven el cambio de inmediato — las variables de entorno se inyectan una sola vez, en el momento en que el contenedor arranca, y no se actualizan mientras el proceso sigue corriendo. Para que el cambio se refleje, hace falta que los Pods se recreen —normalmente con kubectl rollout restart deployment/<nombre>, que dispara un RollingUpdate y crea Pods nuevos que sí leen el valor actualizado del ConfigMap al arrancar.
Resumen y siguiente paso
Esta lección resolvió el problema exacto del Deployment heredado del Módulo 2: DYNAMODB_ENDPOINT_URL, AWS_REGION y SHIPMENTS_TABLE_NAME no deberían vivir dentro del Dockerfile, porque eso rompería "build once, deploy many" — cada cambio de entorno exigiría reconstruir la imagen. Un ConfigMap resuelve esto: un objeto independiente, de texto plano, que se monta en el Pod vía envFrom (el mecanismo que usa esta guía, porque app.py ya lee variables de entorno) o volumeMounts, y que se puede cambiar sin tocar la imagen.
Antes de avanzar deberías poder: explicar por qué una imagen construida una vez debería poder correr en varios entornos sin reconstruirse; distinguir cuándo usar envFrom frente a volumeMounts; y explicar por qué una credencial nunca debería vivir en un ConfigMap.
Siguiente lección: Secret, por qué una credencial nunca vive en la imagen. Ahí retomas exactamente la advertencia de "Errores comunes" de esta lección, con el objeto correcto para el caso sensible: AWS_ACCESS_KEY_ID y AWS_SECRET_ACCESS_KEY.
Recursos
- Kubernetes — ConfigMaps — referencia oficial completa, incluidos los dos mecanismos de montaje de esta lección.
- Kubernetes — Configure a Pod to Use a ConfigMap — tutorial oficial paso a paso, con ejemplos de
envFromyvolumeMountslado a lado. aws-serverless-and-containers-guide(NIEVA), Módulo 6, lección 5 — elapp.pyoriginal, con las tres variables de entorno que esteConfigMapva a proveer en la lección 4.