Wat je aan het einde hebt
Een werkende ConfigMap-setup waarbij configuratiewijzigingen je draaiende pods bereiken zonder handmatig ingrijpen. Je begrijpt welke consumptiemethod bij welk scenario past, hoe je file-based hot-reload correct implementeert, en wanneer je beter Stakater Reloader of immutable ConfigMaps kunt inzetten.
Vereisten
kubectlv1.21 of nieuwer (immutable ConfigMaps zijn GA sinds v1.21)- Een draaiend Kubernetes-cluster (welke distributie dan ook: EKS, GKE, AKS, kind, minikube)
- Bekendheid met Deployments, Pods en volumes (als PersistentVolumes nog vaag zijn, lees dan eerst PersistentVolumes en PersistentVolumeClaims uitgelegd)
helmv3 als je Stakater Reloader wilt installeren
Een ConfigMap aanmaken
Een ConfigMap is een Kubernetes API-object (apiVersion: v1, kind: ConfigMap) dat niet-vertrouwelijke key-value-data opslaat. Twee optionele velden bevatten de data: data voor UTF-8-strings en binaryData voor base64-encoded binaire content. Keys in beide velden mogen niet overlappen.
De totale grootte is begrensd op 1 MiB, een limiet die voortkomt uit de request-size-beperkingen van etcd.
Declaratieve YAML
# configmap-app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
namespace: production
data:
LOG_LEVEL: "info"
MAX_RETRIES: "3"
application.yaml: |
server:
port: 8080
read-timeout: 30s
database:
pool-size: 25
idle-timeout: 300s
kubectl apply -f configmap-app.yaml
Imperatief aanmaken
# Vanuit literal key-value-paren
kubectl create configmap order-service-config \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_RETRIES=3
# Vanuit een bestand (de bestandsnaam wordt de key)
kubectl create configmap nginx-config \
--from-file=nginx.conf
# Vanuit een .env-bestand (KEY=VALUE per regel)
kubectl create configmap env-config \
--from-env-file=.env.production
Let op bij --from-env-file: keys die niet matchen met het patroon [A-Za-z_][A-Za-z0-9_]* worden stilletjes overgeslagen. Geen foutmelding, geen waarschuwing.
Vier manieren om een ConfigMap te gebruiken in een pod
Elke methode heeft andere update-semantiek. De verkeerde kiezen is de meest voorkomende oorzaak van "ik heb de ConfigMap aangepast maar er gebeurt niks."
Patroon 1: individuele omgevingsvariabelen
containers:
- name: order-service
image: registry.internal/order-service:4.2.1
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: order-service-config
key: LOG_LEVEL
optional: true # pod start ook als de key ontbreekt
Waarden worden eenmalig opgehaald, bij het aanmaken van de pod. Draaiende pods zien nooit een update. Een pod-restart is vereist.
Patroon 2: alle keys als omgevingsvariabelen (envFrom)
containers:
- name: order-service
image: registry.internal/order-service:4.2.1
envFrom:
- configMapRef:
name: order-service-config
Zelfde update-gedrag als patroon 1: waarden worden opgehaald bij het aanmaken van de pod en daarna nooit meer. Keys die niet voldoen aan het naming-patroon voor omgevingsvariabelen worden stilletjes genegeerd.
Patroon 3: volume mount (het hot-reload-pad)
spec:
containers:
- name: order-service
image: registry.internal/order-service:4.2.1
volumeMounts:
- name: config-vol
mountPath: /etc/config
readOnly: true
volumes:
- name: config-vol
configMap:
name: order-service-config
items: # optioneel: selecteer specifieke keys
- key: application.yaml
path: application.yaml
Bestanden in het gemounte volume worden automatisch bijgewerkt door de kubelet binnen de sync-periode. De standaard is ongeveer 60 seconden, maar de werkelijke vertraging varieert van bijna nul tot circa 90 seconden afhankelijk van kubelet-cacheconfiguatie en jitter. Je applicatie moet het bestand actief opnieuw lezen om de wijziging op te pikken.
Als items is opgegeven, worden alleen de genoemde keys bestanden. Zonder items wordt elke key een bestand met dezelfde naam als de key.
Patroon 4: Kubernetes API watch
Applicatiecode gebruikt een Kubernetes-clientbibliotheek om de ConfigMap direct te GET-ten of te WATCH-en. Dat geeft nagenoeg nul latency bij updates en werkt cross-namespace (mits de juiste RBAC), maar het voegt complexiteit en een directe API-server-afhankelijkheid toe. De meeste applicaties hebben het niet nodig.
Samenvatting update-propagatie
| Consumptiemethod | Auto-update draaiende pods? | Typische vertraging | Actie nodig in applicatie? |
|---|---|---|---|
env / configMapKeyRef |
Nee | Nooit | Pod-restart |
envFrom / configMapRef |
Nee | Nooit | Pod-restart |
Volume mount (zonder subPath) |
Ja | 10 tot 90 seconden | Bestand opnieuw lezen |
Volume mount (met subPath) |
Nee | Nooit | Pod-restart |
Kubernetes API WATCH |
Ja | Bijna nul | Programmatische watch |
Hoe volume-updates echt werken: de atomic symlink
Dit mechanisme begrijpen is niet optioneel als je file-based hot-reload implementeert.
Wanneer een ConfigMap als volume wordt gemount, schrijft de kubelet geen bestanden rechtstreeks. Er wordt een gelaagde symlink-structuur aangemaakt:
/etc/config/
├── ..2026_04_09_10_00_00.123456789/ # timestamped directory met echte bestanden
│ └── application.yaml
├── ..data -> ..2026_04_09_10_00_00.123456789/ # symlink (atomisch geswapped)
└── application.yaml -> ..data/application.yaml # zichtbare symlink voor de gebruiker
Bij een ConfigMap-wijziging maakt de kubelet een nieuwe timestamped directory, swapt de ..data-symlink atomisch en verwijdert de oude. De update is atomisch en consistent: je ziet nooit een half-bijgewerkte configuratie.
Het gevolg voor file watchers: de bestanden op het zichtbare pad worden niet op hun plek bewerkt. Het symlink-target verandert. Standaard inotify-watchers die individuele bestanden volgen ontvangen IN_DELETE_SELF, niet IN_MODIFY of IN_CLOSE_WRITE. Dat brengt veel hot-reload-implementaties in de war.
Correcte file-watch-implementatie
- Watch de directory (
/etc/config/) of de..data-symlink, niet individuele bestandspaden - Behandel
IN_DELETE_SELFals een config-updated-event, niet als een fout - Stel de watch opnieuw in na een deletion-event (het oude target is weg)
- Gebruik niet de
IN_DONT_FOLLOW-flag - Test op een echt Kubernetes-cluster, niet alleen op een lokaal bestandssysteem (het symlink-gedrag verschilt)
Aanbevolen bibliotheken per taal:
| Taal | Bibliotheek | Opmerkingen |
|---|---|---|
| Go | fsnotify |
Watch directory, niet individuele bestanden |
| Node.js | chokidar |
Gebruik stabilization delay (debounce) |
| Python | watchdog |
Watch directory met debouncing |
| Java | NIO WatchService of Spring @ConfigurationProperties refresh |
Directory-level watch |
De subPath-valkuil
Wanneer een volume mount subPath gebruikt, wordt het bestand direct gemount en omzeilt het de symlink-keten. ConfigMap-updates worden nooit doorgegeven aan dat bestand. Dit is een bekende, won't-fix-beperking.
volumeMounts:
- name: config-vol
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf # auto-update is UITGESCHAKELD voor deze mount
Heb je een enkel bestand op een specifiek pad nodig en wil je toch live updates? Mount dan de hele directory op een apart pad en configureer je applicatie om daar te lezen. Of combineer de subPath-mount met Stakater Reloader, die de pod herstart bij ConfigMap-wijzigingen.
Restarts automatiseren met Stakater Reloader
Wanneer configuratie via omgevingsvariabelen (of subPath-mounts) wordt geconsumeerd, bereiken live updates de draaiende pods niet. Stakater Reloader automatiseert de rolling restart die je anders handmatig zou moeten doen met kubectl rollout restart.
Installeren met Helm
helm repo add stakater https://stakater.github.io/stakater-charts
helm install reloader stakater/reloader \
-n reloader --create-namespace
Je workload annoteren
Kies de annotatie die past bij je gewenste granulariteit:
# Optie A: herstart bij elke ConfigMap- of Secret-wijziging die deze workload referenceert
metadata:
annotations:
reloader.stakater.com/auto: "true"
# Optie B: herstart alleen bij wijzigingen aan specifieke ConfigMaps
metadata:
annotations:
configmap.reloader.stakater.com/reload: "order-service-config,feature-flags"
Reloader ondersteunt Deployments, StatefulSets, DaemonSets, Argo Rollouts en CronJobs. Voor Argo Rollouts voeg je reloader.stakater.com/rollout-strategy: "restart" toe om GitOps-drift te voorkomen die de standaard annotation-patching-strategie kan veroorzaken bij ArgoCD of Flux.
Wat Reloader niet doet. Het triggert een rolling restart (nieuwe pods). Het voert geen in-process config-reload uit. Applicaties ervaren nog steeds een korte rollover. Voor latency-gevoelige workloads waar zero-restart reload belangrijk is, gebruik je volume mounts met een applicatie-side file watcher.
Immutable ConfigMaps
Een ConfigMap als immutable markeren heeft twee voordelen: het voorkomt onbedoelde in-place wijzigingen en het vermindert de belasting op de API-server en etcd, omdat de kubelet stopt met het watchen van immutable resources.
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config-v3
immutable: true # kan niet meer teruggedraaid worden na het instellen
data:
LOG_LEVEL: "info"
MAX_RETRIES: "3"
Het immutable-veld is GA sinds Kubernetes v1.21 (april 2021). Eenmaal ingesteld op true kan het niet meer worden teruggedraaid. Elke poging om data of binaryData te wijzigen wordt geweigerd door de API-server. Om de configuratie te updaten maak je een nieuwe ConfigMap met een nieuwe naam en pas je de Deployment aan:
# Maak de nieuwe versie aan
kubectl create configmap order-service-config-v4 \
--from-literal=LOG_LEVEL=debug \
--from-literal=MAX_RETRIES=5
# Update de Deployment (triggert een rolling update)
kubectl set env deployment/order-service \
--from=configmap/order-service-config-v4
# Ruim op nadat de rollout voltooid is
kubectl rollout status deployment/order-service
kubectl delete configmap order-service-config-v3
Kustomize configMapGenerator: immutability zonder handmatige naamgeving
Kustomize's configMapGenerator voegt automatisch een content hash toe aan de ConfigMap-naam. Elke inhoudelijke wijziging genereert een nieuwe naam, waardoor de pod-spec verandert en een rolling update getriggerd wordt. Geen handmatige versioning nodig.
# kustomization.yaml
configMapGenerator:
- name: order-service-config
files:
- application.yaml
literals:
- LOG_LEVEL=info
Dit genereert een ConfigMap met een naam als order-service-config-g4h8m2k7. Pas application.yaml aan en draai kubectl apply -k . opnieuw: een nieuwe hash, een nieuwe ConfigMap, een nieuwe rollout. Dit combineert goed met Kustomize overlays voor het beheren van per-omgeving-configuratie.
ConfigMap vs. Secret: wanneer welke gebruiken
Een veelgehoord misverstand is dat ConfigMaps en Secrets equivalent zijn op base64-encoding na. Dat klopt niet.
| Aspect | ConfigMap | Secret |
|---|---|---|
| Bedoelde data | Niet-vertrouwelijke configuratie | Wachtwoorden, tokens, certificaten |
| etcd-opslag | Plaintext | Standaard plaintext; encryption at rest beschikbaar |
| RBAC | Standaard | Meer granulair; list/watch geeft toegang tot alle Secret-data in een namespace |
| Maximale grootte | 1 MiB | 1 MiB |
Base64-encoding in het Secret data-veld is voor binary safety, niet voor encryptie. Secrets zijn standaard niet versleuteld at rest; een beheerder moet EncryptionConfiguration expliciet inschakelen.
De vuistregel: als de waarde een probleem zou zijn in een publieke git-commit, gebruik dan een Secret. Feature flags, logniveaus, timeout-waarden en bestandspaden horen in een ConfigMap.
Wanneer je de 1 MiB-limiet raakt
ConfigMaps worden opgeslagen in etcd, dat een standaard maximum request-grootte van 1,5 MiB heeft. Kubernetes handhaaft 1 MiB om marge te houden. Als je configuratie daar overheen groeit:
- Splits over meerdere ConfigMaps. Mount ze elk apart. Werkt goed bij matige overschrijdingen.
- Externe configuratie. HashiCorp Vault, AWS AppConfig, Azure App Configuration. Deze ondersteunen versioning, audit trails en toegangscontroles die ConfigMaps niet bieden.
- Object storage + init container. Sla grote configuratiebestanden op in S3 of GCS en download ze tijdens pod-startup.
Als praktische richtlijn: houd individuele ConfigMaps onder de 100 tot 200 KiB. Kleinere ConfigMaps verminderen de belasting op etcd, versnellen pod-startup en vereenvoudigen debugging.
Het eindresultaat verifiëren
Na het toepassen van je ConfigMap en Deployment controleer je of alles werkt:
# Controleer of de ConfigMap bestaat en de verwachte data bevat
kubectl get configmap order-service-config -n production -o yaml
# Controleer of de pod de gemounte config ziet
kubectl exec -n production deploy/order-service -- \
cat /etc/config/application.yaml
# Werk de ConfigMap bij en bevestig propagatie (volume mount)
kubectl edit configmap order-service-config -n production
# Wacht ~60-90 seconden, dan:
kubectl exec -n production deploy/order-service -- \
cat /etc/config/application.yaml
# De output zou de bijgewerkte waarden moeten tonen
Bij een Reloader-setup zou de pod automatisch moeten herstarten na de ConfigMap-wijziging. Controleer met:
kubectl get pods -n production -w
# Je zou de oude pod moeten zien terminaten en een nieuwe opstarten
Veelvoorkomende problemen
ConfigMap-update bereikt de pod niet. Controleer de consumptiemethod. Bij env of envFrom is een pod-restart nodig. Bij een volume mount met subPath worden updates nooit doorgegeven. Zie de samenvatting update-propagatie.
File watcher geeft IN_DELETE_SELF in plaats van IN_MODIFY. Verwacht gedrag. De kubelet swapt een symlink, het bewerkt bestanden niet op hun plek. Zie het atomic-symlink-gedeelte. Watch de directory, niet individuele bestanden.
Pod start niet met "configmap not found". ConfigMaps zijn namespace-scoped. De ConfigMap moet in dezelfde namespace staan als de pod. Controleer met kubectl get configmap -n <namespace>. Als de ConfigMap echt niet bestaat en optional: true niet is ingesteld, blijft de pod hangen in ContainerCreating.
Reloader is geinstalleerd maar pods herstarten niet. Controleer of de annotatie op de Deployment staat (niet op de Pod). Bekijk de Reloader-logs: kubectl logs -n reloader deploy/reloader-reloader.
ConfigMap overschrijdt 1 MiB. Splits de data over meerdere ConfigMaps of verplaats grote blobs naar externe opslag. De API-server weigert elke ConfigMap groter dan 1 MiB bij admission.