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 secret en 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 yaml muestra claramente los valores. Siempre usa Secret para datos sensibles.
  • Olvidar codificar base64 al escribir data en YAML de Secret: resulta en error illegal base64 data. Usa stringData si no quieres codificar tú mismo.
  • Cambiar ConfigMap pero el Pod no recibe los nuevos valores: porque se cargó mediante env/envFrom que no se recarga automáticamente — debes usar kubectl 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.

kubernetesconfigmapsecretspring-bootdevops