Aller au contenu
English
Conteneurs & Orchestration medium

ConfigMaps Kubernetes : gérer la configuration de vos applications

60 min de lecture

logo kubernetes

Un ConfigMap stocke la configuration non sensible de vos applications (variables, fichiers de config) séparément du code. Vous pouvez ainsi réutiliser la même image dans plusieurs environnements et faire évoluer la configuration sans reconstruire l'image. Selon le mode d'injection choisi, la mise à jour peut être automatique ou nécessiter un redémarrage des Pods.

Ce guide vous montre comment créer des ConfigMaps, les injecter dans vos Pods, et éviter les pièges courants lors des mises à jour.

Prérequis : un cluster Kubernetes avec kubectl configuré.

  • Créer un ConfigMap (littéraux, fichiers, YAML)
  • Injecter la configuration via variables d'environnement ou fichiers montés
  • Comprendre les différences entre env, envFrom et volumes
  • Éviter les pièges de mise à jour (env, subPath, propagation)
  • Utiliser les ConfigMaps immutables en production

Un ConfigMap est une ressource Kubernetes qui stocke des paires clé-valeur non sensibles. Il permet de :

  • Externaliser la configuration du code
  • Réutiliser la même image dans différents environnements
  • Modifier la configuration sans redéployer l'application
RessourceUsage
ConfigMapConfiguration non sensible (variables, fichiers de config)
SecretDonnées sensibles (mots de passe, tokens, certificats)

Dans la quasi-totalité des cas, c'est le champ data que vous utiliserez. Kubernetes accepte aussi binaryData, pour des données binaires encodées en base64.

Quatre sources possibles, et le choix n'est pas cosmétique : il détermine combien de clés vous obtenez. Des valeurs littérales ou un fichier .env donnent une clé par variable, ce qu'attend une injection en variables d'environnement. Un fichier entier donne une seule clé portant tout le contenu, ce qu'attend une application qui lit un fichier de configuration.

La méthode la plus rapide pour quelques variables :

Fenêtre de terminal
kubectl create configmap app-settings \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100

Vérifiez la création :

Fenêtre de terminal
kubectl get configmap app-settings
Sortie
NAME DATA AGE
app-settings 3 5s

Pour un fichier de configuration complet :

Créer le fichier
cat > nginx.conf << 'EOF'
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
}
}
EOF
Créer le ConfigMap
kubectl create configmap nginx-config --from-file=nginx.conf

Le nom du fichier devient la clé, son contenu la valeur :

Fenêtre de terminal
kubectl get configmap nginx-config -o yaml
Sortie
apiVersion: v1
kind: ConfigMap
data:
nginx.conf: |
server {
listen 80;
server_name localhost;
...

Un fichier .env contient déjà des paires CLE=valeur, une par ligne. L'option --from-env-file le découpe pour en faire autant de clés distinctes, là où --from-file en aurait fait une seule clé contenant tout le fichier.

app.env
APP_ENV=production
LOG_LEVEL=info
MAX_CONNECTIONS=100
Fenêtre de terminal
kubectl create configmap depuis-env --from-env-file=app.env

Le contraste entre les deux options se lit dans la colonne DATA, sur le même fichier source :

Sortie
NAME DATA AGE
depuis-env 3 0s # --from-env-file : une clé par ligne
depuis-fichier 1 0s # --from-file : une clé, tout le fichier dedans

C'est --from-env-file qu'il faut quand ces valeurs deviendront des variables d'environnement, et --from-file quand l'application attend un vrai fichier.

Pour un contrôle total et la gestion en GitOps :

app-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-settings
data:
APP_ENV: production
LOG_LEVEL: info
MAX_CONNECTIONS: "100"
Fenêtre de terminal
kubectl apply -f app-config.yaml

Notez les guillemets autour de "100". Toutes les valeurs d'un ConfigMap sont des chaînes de caractères, sans exception. Un nombre écrit nu est interprété par YAML comme un entier, et l'API rejette le manifeste entier avec un message qui parle de Go plutôt que de YAML :

Sortie
Error from server (BadRequest): error when creating "app-config.yaml": ConfigMap
in version "v1" cannot be handled as a ConfigMap: json: cannot unmarshal number
into Go struct field ConfigMap.data of type string

Le piège vaut aussi pour les booléens, et il est plus sournois parce que DEBUG: true a l'air parfaitement normal. Le message ne change que d'un mot, cannot unmarshal bool. La règle est simple : dans le bloc data, tout se met entre guillemets dès que la valeur n'est pas du texte évident.

Trois commandes, trois niveaux de détail. get dit combien de clés existent, describe dit lesquelles et avec quelles valeurs, -o yaml donne la ressource brute telle que Kubernetes la stocke. En diagnostic, on les enchaîne dans cet ordre.

kubectl get configmap liste les ConfigMaps du namespace courant. La colonne DATA est celle à regarder : elle compte le nombre de clés, pas la taille. Un DATA à 0 sur un ConfigMap que vous venez de créer signale presque toujours une erreur dans les options --from-literal ou --from-file. Vous verrez souvent une entrée kube-root-ca.crt que vous n'avez pas créée : Kubernetes l'injecte automatiquement dans chaque namespace, c'est normal.

Fenêtre de terminal
kubectl get configmap
Sortie
NAME DATA AGE
app-settings 3 2m
kube-root-ca.crt 1 10d
nginx-config 1 1m

kubectl describe affiche chaque clé suivie de sa valeur, ce qui suffit pour vérifier rapidement ce que contient un ConfigMap. C'est la commande à réflexe quand une variable arrive vide ou fausse dans un Pod : vous confirmez ici l'orthographe exacte de la clé et sa valeur réelle, avant de suspecter le Deployment. Attention toutefois, describe montre les valeurs en clair, ne l'utilisez jamais sur un Secret dans un terminal partagé.

Fenêtre de terminal
kubectl describe configmap app-settings
Sortie
Name: app-settings
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
APP_ENV:
----
production
LOG_LEVEL:
----
info
MAX_CONNECTIONS:
----
100
BinaryData
====
Events: <none>

La section BinaryData reste vide tant que vous n'avez stocké que du texte. Elle se remplit quand une clé contient des octets non imprimables, cas d'un certificat binaire ou d'une archive : describe ne peut alors plus afficher la valeur, seulement sa présence.

La sortie -o yaml donne la ressource telle que Kubernetes la stocke, champs système compris (creationTimestamp, resourceVersion, uid). C'est le format utile pour deux cas : récupérer un ConfigMap existant afin de le versionner dans Git, ou vérifier la présence du champ binaryData pour des valeurs binaires. Avant de recommiter cette sortie, retirez les champs générés sous metadata, ils n'ont pas leur place dans un fichier source.

Fenêtre de terminal
kubectl get configmap app-settings -o yaml

Trois méthodes principales, chacune avec ses cas d'usage :

MéthodeCas d'usageMise à jour auto
env + valueFromVariables spécifiquesNon
envFromToutes les variables d'un coupNon
VolumeFichiers de configurationOui, après propagation

Injectez une ou plusieurs clés comme variables :

deployment-env.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-settings
key: APP_ENV
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-settings
key: LOG_LEVEL

La variable APP_ENV dans le conteneur prend la valeur de la clé APP_ENV du ConfigMap.

Injectez automatiquement toutes les clés du ConfigMap :

deployment-envfrom.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de
envFrom:
- configMapRef:
name: app-settings

Chaque clé du ConfigMap devient une variable d'environnement :

Fenêtre de terminal
kubectl exec deploy/myapp -- env | grep -E 'APP_|LOG_|MAX_'
Sortie
APP_ENV=production
LOG_LEVEL=info
MAX_CONNECTIONS=100

Rien n'interdit de combiner les deux, et c'est même utile : envFrom charge le lot commun, env surcharge une valeur pour ce Pod précis. Encore faut-il savoir qui l'emporte quand les deux définissent la même variable.

envFrom:
- configMapRef:
name: app-settings # contient APP_ENV=production
env:
- name: APP_ENV
value: "valeur-de-env"
Fenêtre de terminal
kubectl exec p-precedence -- printenv APP_ENV
Sortie
valeur-de-env

C'est env qui gagne, quel que soit l'ordre d'écriture dans le manifeste. Retenez la règle dans ce sens : envFrom pose la base, env pose l'exception. L'inverse produirait un manifeste où la surcharge explicite serait silencieusement ignorée.

Pour des fichiers de configuration complets :

deployment-volume.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d
volumes:
- name: config
configMap:
name: nginx-config

Le fichier /etc/nginx/conf.d/nginx.conf contient le contenu du ConfigMap.

Attention à un effet de bord que ce manifeste ne montre pas : monter un volume sur /etc/nginx/conf.d recouvre le dossier existant. Tout ce que l'image y avait placé disparaît de la vue du conteneur, à commencer par le default.conf livré par nginx :

Fenêtre de terminal
kubectl exec temoin -- ls /etc/nginx/conf.d # sans montage
kubectl exec p-volume -- ls /etc/nginx/conf.d # avec montage
Sortie
default.conf
nginx.conf

Le fichier d'origine n'est pas supprimé, il est masqué par le point de montage, exactement comme un mount sur un dossier non vide. Si votre application a besoin des deux, montez le ConfigMap dans un dossier dédié, ou utilisez subPath en acceptant sa contrepartie.

Le contenu monté n'est pas une copie de fichiers ordinaires, et cette mécanique explique à elle seule pourquoi un volume se rafraîchit et pourquoi subPath ne le fait jamais. Regardez ce que le conteneur voit vraiment :

Fenêtre de terminal
kubectl exec p-volume -- ls -la /etc/nginx/conf.d/
Sortie
drwxrwxrwx 3 root root 4096 ..
drwxr-xr-x 2 root root 4096 ..2026_09_14_09_07_40.2162828806
lrwxrwxrwx 1 root root 32 ..data -> ..2026_09_14_09_07_40.2162828806
lrwxrwxrwx 1 root root 17 nginx.conf -> ..data/nginx.conf

Le vrai contenu vit dans un dossier horodaté. Un lien ..data pointe dessus, et chaque clé du ConfigMap est un lien symbolique vers ce lien. Quand vous modifiez le ConfigMap, le kubelet écrit un nouveau dossier horodaté complet, puis bascule le seul lien ..data. L'opération est donc atomique : votre application ne lit jamais un fichier à moitié réécrit, et ne voit jamais un jeu de clés mi-ancien mi-nouveau.

Deux conséquences pratiques en découlent. La première : le volume est monté en lecture seule, une application qui tente d'y écrire reçoit une erreur système.

Sortie
sh: can't create /etc/nginx/conf.d/essai.txt: Read-only file system

La seconde tient au fonctionnement du rafraîchissement : rien n'est réécrit sur place, c'est un lien qui bascule. Tout montage qui contourne ce lien perd donc la mise à jour, et c'est précisément ce que fait subPath.

Modifier un ConfigMap ne met pas automatiquement à jour vos Pods dans tous les cas. Comprendre ces comportements évite de mauvaises surprises en production.

Piège 1 : Variables d'environnement jamais rafraîchies

Section intitulée « Piège 1 : Variables d'environnement jamais rafraîchies »

Les variables d'environnement sont lues une seule fois au démarrage du conteneur :

Fenêtre de terminal
# Modifier le ConfigMap
kubectl patch configmap app-settings --type merge -p '{"data":{"APP_ENV":"staging"}}'
# La variable dans le Pod garde l'ancienne valeur
kubectl exec deploy/myapp -- printenv APP_ENV
# Affiche : production (pas staging !)

Solution : redémarrer les Pods

Fenêtre de terminal
kubectl rollout restart deployment myapp

Si vous utilisez subPath pour monter un fichier spécifique, il n'est jamais mis à jour :

NE SERA PAS MIS À JOUR
volumeMounts:
- name: config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf # ATTENTION : pas de refresh auto

Le fichier garde son contenu initial, même après modification du ConfigMap. Ce n'est pas une limitation arbitraire, c'est la conséquence directe du mécanisme de liens. Un montage normal expose le lien nginx.conf, qui suit la bascule de ..data et voit donc toujours la version courante. Un subPath monte la cible du lien, résolue une fois, au démarrage du conteneur. Le kubelet peut ensuite créer autant de dossiers horodatés qu'il veut : ce montage-là pointe toujours sur le premier.

Le compromis est réel, et c'est pour cela que subPath reste utilisé : il est le seul moyen d'ajouter un fichier dans un dossier sans masquer les autres. Vous échangez donc le rafraîchissement automatique contre la cohabitation.

Solution : éviter subPath ou redémarrer le Pod

SE MET À JOUR
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d # Dossier entier, pas subPath

Piège 3 : la propagation peut prendre deux minutes, pas une

Section intitulée « Piège 3 : la propagation peut prendre deux minutes, pas une »

C'est le piège qui fait conclure à tort que le mécanisme ne marche pas. Le kubelet vérifie la fraîcheur du ConfigMap à chaque synchronisation périodique, mais il lit la valeur courante dans un cache local à durée de vie limitée. Les deux délais s'additionnent : la documentation Kubernetes annonce jusqu'à la période de synchronisation du kubelet (1 minute par défaut) plus la durée de vie du cache (1 minute par défaut), soit deux minutes.

Fenêtre de terminal
# Modifier le ConfigMap
kubectl patch configmap nginx-config --type merge \
-p '{"data":{"nginx.conf":"server { listen 8080; }"}}'
# Vérifier, en boucle plutôt qu'en pariant sur un sleep
kubectl exec deploy/nginx -- cat /etc/nginx/conf.d/nginx.conf

Un sleep 60 suivi d'une vérification unique vous fera conclure à l'échec une fois sur deux. Bouclez sur la vérification, ou forcez la mise à jour.

Modifier une annotation du Pod déclenche une resynchronisation immédiate du volume, sans redémarrer le conteneur ni recréer le Pod. C'est la parade officielle au délai de deux minutes, et elle change le temps d'attente d'un ordre de grandeur : 2 secondes au lieu de 83 sur le même cluster.

Fenêtre de terminal
kubectl annotate pod mon-pod relance="$(date +%s)" --overwrite

Sur un Deployment, la même idée s'écrit kubectl rollout restart, qui recrée les Pods : plus lent, mais c'est le geste à retenir quand la configuration est injectée en variables d'environnement, où rien ne se rafraîchit jamais.

InjectionMise à jour auto ?Action requise
env / envFromJamaiskubectl rollout restart
VolumeOui, jusqu'à 2 minAttendre, ou annoter le Pod pour forcer
Volume + subPathJamaiskubectl rollout restart

En production, marquez vos ConfigMaps comme immutables pour :

  • Empêcher les modifications accidentelles
  • Améliorer les performances (Kubernetes ne surveille plus les changements)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-settings-v1
data:
APP_ENV: production
immutable: true

Un ConfigMap immutable améliore la sécurité opérationnelle et limite les modifications accidentelles, mais il impose un workflow de versionnement explicite.

Toute tentative de modification échoue :

Fenêtre de terminal
kubectl patch configmap app-settings-v1 --type merge -p '{"data":{"APP_ENV":"staging"}}'
Sortie
The ConfigMap "app-settings-v1" is invalid: data: Forbidden: field is immutable when `immutable` is set

Workflow avec ConfigMaps immutables :

  1. Créer un nouveau ConfigMap portant un nom versionné, app-settings-v2 par exemple
  2. Mettre à jour le Deployment pour qu'il pointe vers ce nouveau ConfigMap
  3. Supprimer l'ancien une fois qu'aucun Pod ne le référence plus

Quatre habitudes suffisent à éviter la majorité des incidents. Elles portent toutes sur la même idée : un ConfigMap est une dépendance de déploiement au même titre qu'une image, et se traite avec le même sérieux.

Le nom d'un ConfigMap est référencé en dur dans vos Deployments : le renommer casse toutes les références existantes. Un nom qui dit à quelle application et à quel environnement il appartient (nginx-config-prod) évite les confusions au moment de brancher un Pod, là où un config1 oblige à ouvrir le contenu pour deviner son usage. Cette clarté compte d'autant plus avec les ConfigMaps immutables, qui imposent des noms versionnés du type app-settings-v2.

Bon
myapp-config
nginx-config-prod
database-settings
À éviter
config1
test-config
temp-settings

Un ConfigMap n'est jamais visible depuis un autre namespace : un Pod ne peut référencer que ceux du sien. Cette contrainte devient un avantage si vous gardez le même nom partout et laissez le namespace porter la différence. Le manifeste du Deployment est alors identique en production et en préproduction, et seul le ConfigMap change.

Fenêtre de terminal
kubectl create configmap myapp-config -n production --from-file=config.yaml
kubectl create configmap myapp-config -n staging --from-file=config-staging.yaml

Un ConfigMap ne peut pas dépasser 1 MiB, soit 1 048 576 octets. La limite vient d'etcd, qui stocke tous les objets de l'API, et elle porte sur la totalité du ConfigMap, pas sur chaque clé. Le refus est net et arrive dès la création :

Sortie
error: failed to create configmap: ConfigMap "trop-gros" is invalid: []:
Too long: may not be more than 1048576 bytes

Pour un fichier volumineux, passez par un PersistentVolume, ou découpez en un ConfigMap par fichier.

Stockez vos ConfigMaps en YAML dans Git pour tracer les changements et permettre les rollbacks.

  1. Le ConfigMap existe-t-il ?

    Fenêtre de terminal
    kubectl get configmap app-settings

    Si "NotFound", créez-le d'abord.

  2. La clé existe-t-elle ?

    Fenêtre de terminal
    kubectl get configmap app-settings -o jsonpath='{.data.APP_ENV}'

    Vérifiez l'orthographe exacte de la clé.

  3. Le Pod référence-t-il le bon ConfigMap ?

    Fenêtre de terminal
    kubectl describe pod myapp-xxx | grep -A5 "Environment\|Mounts"
  4. Le Pod a-t-il redémarré après modification ?

    Pour les variables d'environnement, un rollout restart est nécessaire.

Ces trois erreurs couvrent la quasi-totalité des incidents liés aux ConfigMaps. La première, CreateContainerConfigError, bloque le démarrage du Pod : le conteneur ne peut pas être créé car le ConfigMap ou la clé référencée n'existe pas, souvent une faute de frappe ou un ordre de déploiement inversé. Les deux autres sont plus sournoises car le Pod démarre normalement : une variable vide trahit une clé mal orthographiée, un fichier figé trahit l'emploi de subPath. Comparez toujours la référence du Pod avec la sortie de kubectl describe cm.

ErreurCauseSolution
CreateContainerConfigErrorConfigMap ou clé introuvableVérifier le nom et la clé
Variable videClé mal orthographiéekubectl describe cm pour vérifier
Fichier non mis à joursubPath utiliséÉviter subPath ou restart

Le premier cas se diagnostique sans deviner : Kubernetes distingue les deux causes possibles et nomme précisément ce qui manque.

Fenêtre de terminal
kubectl get pod <nom> \
-o jsonpath='{.status.containerStatuses[0].state.waiting.message}'
Sortie
configmap "nexiste-pas" not found
couldn't find key CLE-QUI-NEXISTE-PAS in ConfigMap lab-cm/bon

La première ligne signale un ConfigMap absent, la seconde un ConfigMap présent mais amputé de la clé demandée. Dans les deux cas le Pod reste bloqué en CreateContainerConfigError et ne démarre jamais.

Ce blocage est en général souhaitable : mieux vaut un Pod qui refuse de démarrer qu'un Pod qui tourne sans sa configuration. Mais pour une configuration réellement optionnelle, une surcharge de débogage par exemple, le champ optional: true autorise Kubernetes à passer outre.

envFrom:
- configMapRef:
name: surcharge-debug
optional: true

Le Pod démarre alors normalement, simplement privé de ces variables. Le champ existe sur configMapRef, sur configMapKeyRef et sur un volume. À manier avec précaution : une faute de frappe dans le nom du ConfigMap ne produit plus aucune erreur, et l'application démarre avec ses valeurs par défaut sans que rien ne le signale.

  1. Un ConfigMap stocke la configuration non sensible en clair ; les données sensibles vont dans un Secret
  2. Toutes les valeurs sont des chaînes : écrivez "100", jamais 100, sous peine de voir l'API rejeter le manifeste entier
  3. Trois méthodes d'injection : env, envFrom, volume. Quand les deux premières se contredisent, env gagne
  4. Les variables d'environnement ne sont jamais rafraîchies ; seul un redémarrage les met à jour
  5. Un volume monté est un jeu de liens symboliques en lecture seule, basculés de façon atomique. C'est ce qui le rafraîchit tout seul, en jusqu'à 2 minutes
  6. subPath monte la cible du lien, résolue une fois : il ne se met jamais à jour, en échange de quoi il ne masque pas le reste du dossier
  7. Une annotation posée sur le Pod force le rafraîchissement immédiat
  8. Un ConfigMap ou une clé introuvable bloque le Pod en CreateContainerConfigError, sauf avec optional: true
  9. En production, immutable: true et un plafond de 1 MiB à ne pas dépasser

Sept questions sur les points qui coûtent cher en production : ce qui se rafraîchit tout seul, ce qui ne se rafraîchit jamais, et ce que l'API refuse.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn