ConfigMap et Secret dans Kubernetes : Séparer la config de l'image

1. Vue d’ensemble

Quand vous packagisez une application en image container, vous ne devriez pas « durcir » les valeurs comme l’URL de la base de données ou les clés API directement dans l’image. Cet article vous aide à comprendre ConfigMap et Secret dans Kubernetes — deux objets permettant de séparer la configuration de l’image, comment les créer, comment les charger dans un Pod via des variables d’environnement ou des volumes, et comment les intégrer avec une application Spring Boot. À la fin, vous serez en confiance pour configurer un Deployment qui lit la configuration dynamiquement sans avoir besoin de reconstruire l’image à chaque fois que vous changez une valeur.

2. Qu’est-ce que ConfigMap

ConfigMap est un objet qui stocke des données de configuration en format clé-valeur, non chiffrées, destinées aux données non sensibles : noms de service, feature flags, fichiers properties…

Les données d’une ConfigMap existent indépendamment du Pod — vous pouvez modifier la ConfigMap sans reconstruire l’image, il suffit de redémarrer le Pod (ou d’utiliser un mécanisme de reload si l’application le supporte).

3. Créer une ConfigMap

3.1. À partir d’un fichier 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

Ici, data contient deux types : des clés simples (APP_ENV, LOG_LEVEL) et une clé contenant le contenu complet du fichier (application.properties). Appliquez-le ainsi :

kubectl apply -f order-service-config.yaml

3.2. À partir de littéraux ou de fichiers existants

kubectl create configmap order-service-config \
  --from-literal=APP_ENV=production \
  --from-file=application.properties=./config/application.properties

Cette commande crée une ConfigMap directement depuis la ligne de commande sans avoir besoin d’écrire du YAML, pratique quand on fait du scripting CI/CD.

4. Utiliser ConfigMap dans un Pod

4.1. Charger comme variables d’environnement

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 charge toutes les clés de la ConfigMap en tant que variables d’environnement du même nom. Le container lit APP_ENV, LOG_LEVEL comme des variables d’environnement normales.

Si vous voulez sélectionner seulement une clé spécifique :

      env:
        - name: APP_ENVIRONMENT
          valueFrom:
            configMapKeyRef:
              name: order-service-config
              key: APP_ENV

Cette approche vous permet de renommer la variable d’environnement dans le container (APP_ENVIRONMENT) différemment de la clé d’origine dans la ConfigMap (APP_ENV).

4.2. Charger comme volume

      volumeMounts:
        - name: config-volume
          mountPath: /app/config
  volumes:
    - name: config-volume
      configMap:
        name: order-service-config

Chaque clé dans la ConfigMap devient un fichier dans /app/config (par exemple /app/config/application.properties). Cette approche est appropriée quand l’application a besoin de lire des fichiers de configuration entiers plutôt que des variables d’environnement individuelles.

Note : Quand vous montez sous forme de volume, Kubernetes met automatiquement à jour les fichiers quand la ConfigMap change (avec un délai de quelques dizaines de secondes à quelques minutes selon la période de synchronisation de kubelet). Avec les variables d’environnement (env/envFrom), le Pod ne se recharge pas automatiquement — vous devez redémarrer le Pod.

5. Qu’est-ce que Secret et la différence avec ConfigMap

Secret a une structure similaire à ConfigMap mais est destiné aux données sensibles : mots de passe, tokens, clés privées. Les principales différences :

Critère ConfigMap Secret
Chiffrement Non Base64-encodé (pas du vrai chiffrement)
Stockage dans etcd Texte en clair Peut activer le chiffrement at rest
Affiché via kubectl get Montre les valeurs Masque les valeurs (affiche seulement base64)
Objectif Configuration classique Données sensibles

Note : Secret n’est que du base64-encodé, pas chiffré par défaut. Quiconque ayant la permission get secret dans le namespace peut décoder. Pour une véritable sécurité, vous devez activer le chiffrement [at rest] dans etcd ou utiliser un gestionnaire de secrets externe (Vault, AWS Secrets Manager) intégré via External Secrets Operator.

6. Créer un Secret

6.1. À partir d’un YAML (il faut encoder en base64 soi-même)

echo -n "P@ssw0rd123" | base64
# UEBzc3cwcmQxMjM=
apiVersion: v1
kind: Secret
metadata:
  name: order-service-secret
type: Opaque
data:
  DB_PASSWORD: UEBzc3cwcmQxMjM=

Le champ data nécessite des valeurs déjà encodées en base64. Si vous ne voulez pas encoder vous-même, utilisez stringData :

stringData:
  DB_PASSWORD: "P@ssw0rd123"

Kubernetes encodera automatiquement lors de la sauvegarde. Utilisez stringData pour une meilleure lisibilité quand vous écrivez des manifests manuellement.

6.2. À partir de la ligne de commande

kubectl create secret generic order-service-secret \
  --from-literal=DB_PASSWORD=P@ssw0rd123 \
  --from-literal=DB_USERNAME=order_app

7. Utiliser Secret dans un Pod

Le chargement est exactement similaire à ConfigMap :

      envFrom:
        - secretRef:
            name: order-service-secret

ou monter un volume :

      volumeMounts:
        - name: secret-volume
          mountPath: /app/secret
          readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: order-service-secret

Monter un volume pour un Secret devrait toujours avoir readOnly: true et vous pouvez restreindre les permissions d’accès aux fichiers avec defaultMode si nécessaire :

      secret:
        secretName: order-service-secret
        defaultMode: 0400

8. Intégration avec Spring Boot

Spring Boot lit les variables d’environnement selon la norme de relaxed binding, donc vous n’avez pas besoin de code spécial — il suffit de nommer les variables correctement selon la convention.

      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 mappe automatiquement SPRING_DATASOURCE_USERNAME → spring.datasource.username. Dans le code, vous avez juste besoin de @ConfigurationProperties normale :

@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 "Connexion en tant que %s".formatted(dataSourceProperties.username());
    }
}

PaymentService ne sait rien de Kubernetes — il reçoit simplement des valeurs déjà liées par Spring Boot à partir de variables d’environnement fournies par ConfigMap/Secret.

8.1. Tests indépendants du cluster

Vous testez la logique en définissant des variables d’environnement simulées via @TestPropertySource, sans avoir besoin d’un vrai Kubernetes :

@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("Connexion en tant que order_app");
    }
}

Ce test confirme que DataSourceProperties est correctement liée et que PaymentService utilise les bonnes valeurs — complètement découplé de la question de savoir d’où proviennent réellement les valeurs (ConfigMap ou Secret sur le cluster).

PaymentServiceTest > shouldDescribeConnectionUsingBoundProperties() PASSED

9. Mise à jour dynamique sans redémarrage du Pod

Si vous montez ConfigMap/Secret sous forme de volume, les fichiers se mettront à jour automatiquement mais l’application Spring Boot ne relira pas automatiquement sauf si :

  • Utilisez Spring Cloud Kubernetes (spring-cloud-starter-kubernetes-client-config) pour faire du polling sur la ConfigMap et rafraîchir les beans @RefreshScope.
  • Ou implémentez vous-même un file-watcher qui relit les fichiers montés.
<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

Cette configuration active la fonctionnalité Spring Cloud Kubernetes qui interroge automatiquement la ConfigMap selon un cycle régulier et rafraîchit les beans marqués @RefreshScope, sans avoir besoin de redémarrer manuellement le Pod.

Note : Un polling continu consomme des ressources d’API server. Pour les grands clusters, envisagez d’utiliser spring.cloud.kubernetes.reload.mode=event (watch API) à la place du polling.

10. Erreurs courantes et quand ne pas utiliser

  • Mettre des mots de passe dans ConfigMap : ConfigMap n’est pas chiffrée, kubectl get configmap -o yaml affiche les valeurs. Utilisez toujours Secret pour les données sensibles.
  • Oublier de base64-encoder quand vous écrivez data dans un Secret YAML : cela mène à l’erreur illegal base64 data. Utilisez stringData si vous ne voulez pas encoder vous-même.
  • Changer la ConfigMap mais le Pod ne reçoit pas les nouvelles valeurs : parce que charger via env/envFrom ne se recharge pas automatiquement — vous devez faire kubectl rollout restart deployment.
  • Monter un volume Secret mais oublier readOnly: true : le container pourrait écraser les fichiers, causant de la confusion lors du débogage.
  • Utiliser Secret comme un stockage de secrets à long terme : Secret dans Kubernetes est approprié pour la configuration runtime, ne remplace pas un gestionnaire de secrets dédié (Vault, AWS Secrets Manager) quand vous avez besoin de logs d’audit, de rotation automatique, ou d’un versioning strict.
  • ConfigMap/Secret trop volumineux : Kubernetes limite la taille des objets à environ 1 MiB (à cause des limites d’etcd). Si vous avez besoin de fichiers de configuration volumineux, envisagez de monter un PersistentVolume ou d’utiliser un init container qui charge depuis du stockage objet.

11. Conclusion

ConfigMap et Secret vous aident à séparer la configuration de l’image, à déployer la même image dans plusieurs environnements (dev, staging, production) en changeant simplement les objets de configuration. Utilisez ConfigMap pour les données publiques, Secret pour les données sensibles — mais n’oubliez pas que Secret n’est encodé que par défaut, pas vraiment chiffré. Avec Spring Boot, vous tirez parti de la relaxed binding pour charger les valeurs via des variables d’environnement sans changer le code métier, et vous pouvez activer Spring Cloud Kubernetes si vous avez besoin de recharger la configuration dynamiquement sans redémarrer le Pod.

kubernetesconfigmapsecretspring-bootdevops