Configuración con ConfigMap y Secret
Hasta ahora tus contenedores llevaban todo dentro de la imagen. Pero la misma imagen debe funcionar en desarrollo, en staging y en producción, y lo que cambia entre entornos (URLs, colores de la interfaz, contraseñas) no puede vivir cocido en ella. Kubernetes separa ese material en dos objetos: el ConfigMap para configuración normal y el Secret para datos sensibles.
Es el capítulo de configuración del libro, con una diferencia: aquí vas a comprobar tú mismo qué protege un Secret y qué no.
En esta lección crearás uno de cada y los inyectarás en un Pod de las dos formas posibles: como variables de entorno y como archivo montado. Trabaja desde la pestaña dev-machine.
Paso 1: El ConfigMap
Crea el archivo configmap.yaml. Fíjate en que mezcla dos estilos de clave: un valor simple y un archivo completo.
cat << 'EOF' > configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
API_URL: "http://api:8080"
app.properties: |
server.port=8080
server.timeout=30s
EOF
El YAML, explicado con preguntas y respuestas
¿Dónde está el spec?
No lo hay. Los ConfigMaps no describen un estado deseado que reconciliar, son puro almacenamiento de datos, así que usan un campo data plano de pares clave-valor.
¿Por qué una clave se llama app.properties?
Porque las claves de un ConfigMap pueden ser nombres de archivo, y su valor el contenido completo (el | de YAML conserva los saltos de línea). Cuando montes el ConfigMap como volumen, cada clave se convertirá en un archivo con ese nombre.
¿Hay límite de tamaño?
Sí, 1 MiB por ConfigMap. Es configuración, no almacenamiento de datos; para eso está el módulo siguiente.
Aplícalo:
kubectl apply -f configmap.yaml
kubectl describe configmap web-config
Paso 2: El Secret
Crea secret.yaml:
cat << 'EOF' > secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: tienda
password: s3cr3t0
EOF
El YAML, explicado con preguntas y respuestas
¿Qué significa type: Opaque?
Es el tipo genérico para datos arbitrarios del usuario. Existen tipos especializados (para credenciales de registros de imágenes, certificados TLS...) que la API valida de forma específica.
¿Por qué stringData y no data?
En un Secret, el campo data exige valores codificados en base64. stringData es el atajo de escritura: aceptas texto plano y la API lo codifica por ti al guardarlo. Compruébalo tras aplicar: kubectl get secret db-credentials -o yaml te devolverá el valor ya en base64, dentro de data.
Entonces, ¿base64 protege la contraseña?
No, y es la pregunta más importante de la lección. Base64 es codificación, no cifrado: cualquiera con acceso de lectura al Secret recupera el valor con base64 -d. La protección real de los Secrets viene de otras capas: el control de acceso RBAC (módulo 5) y el cifrado en reposo en etcd. Un Secret es, sobre todo, una etiqueta que dice "esto es sensible, trátalo distinto".
Aplícalo:
kubectl apply -f secret.yaml
kubectl get secret db-credentials -o yaml
Paso 3: El Pod que lo consume todo
Ahora el plato fuerte: un Pod que recibe el ConfigMap y el Secret por las dos vías. Crea pod-web.yaml:
cat << 'EOF' > pod-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: app
image: ghcr.io/iximiuz/labs/nginx:alpine
env:
- name: API_URL
valueFrom:
configMapKeyRef:
name: web-config
key: API_URL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: config-vol
mountPath: /etc/config
readOnly: true
volumes:
- name: config-vol
configMap:
name: web-config
items:
- key: app.properties
path: app.properties
EOF
El YAML, explicado con preguntas y respuestas
¿Qué hace valueFrom frente a un value normal?
Un value fija el dato en el manifiesto del Pod; valueFrom lo referencia desde otro objeto (configMapKeyRef o secretKeyRef). El manifiesto del Pod queda igual en todos los entornos y solo cambia el ConfigMap o Secret de cada uno.
¿Existe una forma de traerse todas las claves de golpe?
Sí, envFrom con configMapRef o secretRef inyecta cada clave como una variable. Es cómodo, pero valueFrom clave a clave documenta mejor qué consume realmente la aplicación.
¿Cómo se relacionan volumes y volumeMounts?
Es un patrón en dos mitades que reaparecerá en la próxima lección: volumes (a nivel de Pod) declara qué volumen existe y de dónde sale su contenido, y volumeMounts (a nivel de contenedor) declara dónde aparece dentro de ese contenedor. Aquí el volumen sale de un ConfigMap, y cada clave listada en items se materializa como un archivo bajo /etc/config.
¿Qué pasa si el ConfigMap referenciado no existiera?
El Pod se queda en CreateContainerConfigError hasta que exista. Las referencias de configuración son dependencias duras del arranque.
Si cambio el ConfigMap, ¿cambia el Pod en caliente?
Depende de la vía. Los archivos montados como volumen se actualizan solos al cabo de un rato; las variables de entorno no, se fijan al crear el contenedor y solo cambian recreando el Pod. Es uno de los motivos por los que muchas aplicaciones prefieren leer archivos de configuración.
Aplícalo y verifica desde dentro, que es donde cuenta:
kubectl apply -f pod-web.yaml
kubectl wait --for=condition=Ready pod/web --timeout=60s
kubectl exec web -- sh -c 'echo $API_URL'
kubectl exec web -- sh -c 'echo $DB_PASSWORD'
kubectl exec web -- cat /etc/config/app.properties
Resumen
- El ConfigMap guarda configuración; el Secret, datos sensibles con tratamiento especial (que no es cifrado por sí solo).
- Dos vías de consumo: variables de entorno (
valueFrom/envFrom) y archivos montados (volumes+volumeMounts). - Los volúmenes de ConfigMap se refrescan en caliente; las variables de entorno, no.
CreateContainerConfigErrorcasi siempre significa una referencia de configuración que no existe.
- Previous lesson
- CoreDNS y el DNS interno
- Next lesson
- Challenge: registro privado