ConfigMap y Secret en Kubernetes: Separar la configuración de la imagen
1. Descripción general
Cuando empaquetas tu aplicación como imagen de contenedor, no debes “hornear” valores como URLs de base de datos, claves API directamente en la imagen. Este artículo te ayuda a entender ConfigMap y Secret en Kubernetes — dos objetos que sirven para separar la configuración de la imagen, cómo crearlos, cómo inyectarlos en Pods mediante variables de entorno o volúmenes, y cómo integrarlos con aplicaciones Spring Boot. Después de leerlo, podrás configurar con confianza un Deployment que lea configuración dinámicamente sin necesidad de reconstruir la imagen cada vez que cambies un valor.
2. ¿Qué es ConfigMap?
ConfigMap es un objeto que almacena datos de configuración en forma de pares clave-valor, sin cifrar, útil para datos no sensibles: nombres de servicios, feature flags, archivos de propiedades…
Los datos en ConfigMap existen de forma independiente del Pod — puedes modificar ConfigMap sin reconstruir la imagen, solo necesitas reiniciar el Pod (o usar un mecanismo de recarga si la aplicación lo soporta).
3. Crear ConfigMap
3.1. Desde un archivo YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
APP_ENV: "production"
LOG_LEVEL: "INFO"
application.properties: |
server.port=8080
spring.jpa.hibernate.ddl-auto=validate
Aquí data contiene dos tipos de contenido: claves simples (APP_ENV, LOG_LEVEL) y una clave que contiene el contenido de un archivo completo (application.properties). Aplícalo con:
kubectl apply -f order-service-config.yaml
3.2. Desde literal o archivo existente
kubectl create configmap order-service-config \
--from-literal=APP_ENV=production \
--from-file=application.properties=./config/application.properties
Este comando crea ConfigMap directamente desde la línea de comandos sin necesidad de escribir YAML, lo cual es útil en scripts de CI/CD.
4. Usar ConfigMap en Pod
4.1. Cargar como variables de entorno
apiVersion: v1
kind: Pod
metadata:
name: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
envFrom:
- configMapRef:
name: order-service-config
envFrom carga todas las claves en ConfigMap como variables de entorno con el mismo nombre. El contenedor lee APP_ENV, LOG_LEVEL igual que variables de entorno normales.
Si solo quieres seleccionar una clave específica:
env:
- name: APP_ENVIRONMENT
valueFrom:
configMapKeyRef:
name: order-service-config
key: APP_ENV
Este enfoque permite cambiar el nombre de la variable de entorno en el contenedor (APP_ENVIRONMENT) diferente a la clave original en ConfigMap (APP_ENV).
4.2. Cargar como volumen
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: order-service-config
Cada clave en ConfigMap se convierte en un archivo en /app/config (por ejemplo /app/config/application.properties). Este enfoque es útil cuando la aplicación necesita leer archivos de configuración completos en lugar de variables de entorno individuales.
Nota: Cuando montas como volumen, Kubernetes actualiza automáticamente los archivos cuando ConfigMap cambia (con un retraso de decenas de segundos a varios minutos según el período de sincronización de kubelet). Con variables de entorno (
env/envFrom), el Pod no se recarga automáticamente — debes reiniciar el Pod.
5. ¿Qué es Secret y cuál es la diferencia con ConfigMap?
Secret tiene una estructura similar a ConfigMap pero está diseñado para datos sensibles: contraseñas, tokens, claves privadas. Las diferencias principales son:
| Criterio | ConfigMap | Secret |
|---|---|---|
| Cifrado | No | Codificado en Base64 (no es cifrado real) |
| Almacenado en etcd | Texto plano | Puede habilitarse cifrado en reposo |
Visualización con kubectl get |
Muestra valores | Oculta valores (solo base64) |
| Propósito | Config normal | Datos sensibles |
Nota: Secret solo codifica base64, no cifra por defecto. Cualquiera con permisos
get secreten el namespace puede decodificarlo. Para verdadera seguridad, necesitas habilitar [cifrado en reposo] en etcd o usar un gestor externo de secretos (Vault, AWS Secrets Manager) integrado a través de External Secrets Operator.
6. Crear Secret
6.1. Desde YAML (debe codificar base64 manualmente)
echo -n "P@ssw0rd123" | base64
# UEBzc3cwcmQxMjM=
apiVersion: v1
kind: Secret
metadata:
name: order-service-secret
type: Opaque
data:
DB_PASSWORD: UEBzc3cwcmQxMjM=
El campo data requiere valores ya codificados en base64. Si no quieres codificar tú mismo, usa stringData:
stringData:
DB_PASSWORD: "P@ssw0rd123"
Kubernetes codificará automáticamente en base64 al guardar. Usa stringData si prefieres que el manifest sea más legible al escribirlo manualmente.
6.2. Desde línea de comandos
kubectl create secret generic order-service-secret \
--from-literal=DB_PASSWORD=P@ssw0rd123 \
--from-literal=DB_USERNAME=order_app
7. Usar Secret en Pod
La forma de inyectar es exactamente igual a ConfigMap:
envFrom:
- secretRef:
name: order-service-secret
o montar como volumen:
volumeMounts:
- name: secret-volume
mountPath: /app/secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: order-service-secret
Cuando montes Secret como volumen, siempre debes establecer readOnly: true y restringir los permisos del archivo con defaultMode si es necesario:
secret:
secretName: order-service-secret
defaultMode: 0400
8. Integración con Spring Boot
Spring Boot lee variables de entorno siguiendo el estándar de relaxed binding, así que no necesitas código especial — solo tienes que usar el nombre correcto de la variable siguiendo la convención.
envFrom:
- configMapRef:
name: order-service-config
- secretRef:
name: order-service-secret
env:
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: order-service-secret
key: DB_USERNAME
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: order-service-secret
key: DB_PASSWORD
Spring Boot mapea automáticamente SPRING_DATASOURCE_USERNAME → spring.datasource.username. En el código, solo necesitas @ConfigurationProperties normal:
@ConfigurationProperties(prefix = "spring.datasource")
public record DataSourceProperties(String url, String username, String password) {
}
@Service
public class PaymentService {
private final DataSourceProperties dataSourceProperties;
public PaymentService(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
public String describeConnection() {
return "Connecting as %s".formatted(dataSourceProperties.username());
}
}
PaymentService no sabe nada de Kubernetes — solo recibe valores ya vinculados por Spring Boot desde variables de entorno proporcionadas por ConfigMap/Secret.
8.1. Test independiente del cluster
Puedes probar tu lógica estableciendo variables de entorno simuladas mediante @TestPropertySource, sin necesidad de un cluster Kubernetes real:
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.username=order_app",
"spring.datasource.password=test-pass"
})
class PaymentServiceTest {
@Autowired
private PaymentService paymentService;
@Test
void shouldDescribeConnectionUsingBoundProperties() {
assertThat(paymentService.describeConnection())
.isEqualTo("Connecting as order_app");
}
}
Este test verifica que DataSourceProperties se vincula correctamente y que PaymentService usa los valores correctos — completamente separado de si esos valores provienen de ConfigMap o Secret real en el cluster.
PaymentServiceTest > shouldDescribeConnectionUsingBoundProperties() PASSED
9. Actualización dinámica sin reiniciar el Pod
Si montas ConfigMap/Secret como volumen, los archivos se actualizarán automáticamente pero Spring Boot no los releerá a menos que:
- Uses Spring Cloud Kubernetes (
spring-cloud-starter-kubernetes-client-config) para hacer polling de ConfigMap y refrescar beans con@RefreshScope. - O implementes tú mismo un file-watcher para releer el archivo montado.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring.cloud.kubernetes.config.enabled=true
spring.cloud.kubernetes.reload.enabled=true
spring.cloud.kubernetes.reload.mode=polling
Esta configuración habilita Spring Cloud Kubernetes para hacer polling automático de ConfigMap en ciclos regulares y refrescar beans anotados con @RefreshScope, sin necesidad de reiniciar manualmente el Pod.
Nota: El polling continuo consume recursos del servidor API. En clusters grandes, considera usar
spring.cloud.kubernetes.reload.mode=event(watch API) en lugar de polling.
10. Errores comunes y cuándo no usar
- Guardar contraseñas en ConfigMap: ConfigMap no está cifrado,
kubectl get configmap -o yamlmuestra claramente los valores. Siempre usa Secret para datos sensibles. - Olvidar codificar base64 al escribir
dataen YAML de Secret: resulta en errorillegal base64 data. UsastringDatasi no quieres codificar tú mismo. - Cambiar ConfigMap pero el Pod no recibe los nuevos valores: porque se cargó mediante
env/envFromque no se recarga automáticamente — debes usarkubectl rollout restart deployment. - Montar volumen de Secret pero olvidar
readOnly: true: el contenedor podría sobrescribir el archivo, causando confusión al depurar. - Usar Secret como almacén de secretos a largo plazo: Secret en Kubernetes es útil para configuración de runtime, pero no reemplaza un gestor de secretos dedicado (Vault, AWS Secrets Manager) cuando necesitas audit logs, rotación automática o versionamiento estricto.
- ConfigMap/Secret demasiado grandes: Kubernetes limita el tamaño de los objetos a aproximadamente 1 MiB (por limitación de etcd). Si necesitas archivos de configuración grandes, considera montar desde PersistentVolume o usar un init container para descargar desde almacenamiento de objetos.
11. Conclusión
ConfigMap y Secret te permiten separar la configuración de la imagen, desplegar la misma imagen en múltiples entornos (dev, staging, production) solo cambiando los objetos de configuración. Usa ConfigMap para datos públicos y Secret para datos sensibles — pero recuerda que Secret por defecto solo codifica, no cifra verdaderamente. Con Spring Boot, aprovechas relaxed binding para inyectar valores mediante variables de entorno sin cambiar el código de negocio, y puedes habilitar Spring Cloud Kubernetes si necesitas recargar configuración dinámicamente sin reiniciar el Pod.