ConfigMaps und Secrets
Konfiguration und sensitive Daten in Kubernetes.
Lernziele
Nach dieser Lektion kannst du Konfiguration und sensible Daten sauber von Images trennen: ConfigMaps für Einstellungen, Secrets für Credentials. Du kennst die drei Einbindungswege env, envFrom und volumeMount samt ihrer Update-Semantik, kannst erklären, warum base64 keine Verschlüsselung ist und was Encryption at Rest sowie der External Secrets Operator dagegen tun, und du kennst die wichtigsten Secret Types jenseits von Opaque.
Das Problem: Dieselbe App, drei Umgebungen, ein Image
Ein Container-Image soll unveränderlich sein — einmal gebaut, überall identisch ausgerollt. Aber die Datenbank-URL in Staging ist eine andere als in Produktion, das Log-Level auch, und das DB-Passwort sowieso. Baust du die Werte ins Image, brauchst du pro Umgebung ein eigenes Image (und hast Credentials für immer in der Registry-History). Die Zwölf-Faktor-Antwort: Konfiguration gehört in die Umgebung, nicht ins Artefakt.
Kubernetes bildet das mit zwei API-Objekten ab: ConfigMaps für unkritische Konfiguration (Key-Value-Paare oder ganze Dateien wie eine nginx.conf), Secrets für alles Sensible — Passwörter, Tokens, TLS-Schlüssel. Beide sind strukturell fast identisch; der Unterschied liegt in der Behandlung: Secrets lassen sich per RBAC getrennt schützen, werden auf Nodes nur im tmpfs (RAM) abgelegt statt auf Platte, und nur an Nodes verteilt, die sie für einen Pod tatsächlich brauchen.
Drei Wege in den Pod — und ihre Update-Semantik
Beide Objekte erreichen den Container auf denselben drei Wegen, und der Unterschied zwischen ihnen ist eine beliebte Detailfrage:
Einzelne Environment-Variablen (env mit valueFrom): ein Key aus einer ConfigMap oder einem Secret wird eine Variable. Explizit und nachvollziehbar — aber bei zwanzig Werten zwanzig YAML-Blöcke.
Alle Keys auf einmal (envFrom): jede ConfigMap-/Secret-Key wird automatisch eine Variable. Bequem, aber mit Kollisionsrisiko bei mehreren Quellen und ohne explizite Doku, was die App eigentlich erwartet.
Als Dateien (volumeMount): jeder Key wird eine Datei im Mount-Pfad. Der einzige Weg für ganze Konfigurationsdateien — und der einzige mit Live-Updates: Ändert sich die ConfigMap, aktualisiert das kubelet die gemounteten Dateien nach kurzer Verzögerung (typisch unter einer Minute).
Daraus folgt die wichtigste Praxisregel des Themas: Environment-Variablen werden nach Container-Start nie aktualisiert. Eine geänderte ConfigMap erreicht env-basierte Pods erst beim Neustart — und ein Deployment rollt nicht automatisch neu, weil sich eine referenzierte ConfigMap geändert hat. Teams lösen das, indem sie einen Hash der Config als Pod-Annotation führen (Helm-Standardtrick) oder Tools wie Reloader einsetzen — oder mit immutable ConfigMaps arbeiten: immutable: true plus neuer Name pro Version macht jeden Config-Change zu einem expliziten, rückrollbaren Rollout und schont nebenbei den API Server, weil Watches entfallen.
Secrets ehrlich betrachtet: base64 ist Transport, nicht Schutz
Die unbequeme Wahrheit zuerst: In data eines Secrets stehen die Werte base64-codiert — nicht verschlüsselt. base64 ist eine Transportkodierung (damit auch Binärdaten wie Zertifikate in JSON/YAML passen) und in Sekunden umkehrbar:
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
Jeder mit get-Recht auf Secrets liest also Klartext. Daraus folgen die echten Schutzmaßnahmen:
- RBAC restriktiv:
get/listauf Secrets ist faktisch Klartext-Zugriff — kein Sammel-Read für alle, Vorsicht mitlist(liefert die Inhalte gleich mit). - Encryption at Rest: Per Default liegen Secrets auch in etcd unverschlüsselt — wer ein etcd-Backup (Lektion 2) liest, liest alle Secrets. Eine
EncryptionConfigurationam API Server verschlüsselt sie vor dem Schreiben, idealerweise über einen KMS Provider (Cloud-KMS oder Vault), damit der Schlüssel nicht neben den Daten auf dem Control-Plane-Node liegt. Managed-Angebote wie GKE verschlüsseln etcd standardmäßig; bei kubeadm ist es deine Aufgabe. - Secrets raus aus Git: Manifeste mit Secrets im Repo sind ein Leak mit Versionsgeschichte. Für GitOps-Workflows: Sealed Secrets oder SOPS verschlüsseln im Repo — oder besser, die Quelle ganz verlagern:
Der External Secrets Operator (ESO) macht einen externen Secret-Store zur Quelle der Wahrheit: In Git liegt nur ein ExternalSecret-Manifest („hole prod/db/password aus Vault/AWS Secrets Manager”), der Operator synchronisiert den Wert als natives Kubernetes-Secret und hält ihn aktuell — Rotation im Store kommt automatisch im Cluster an. Damit bekommst du zentrale Verwaltung und Audit beim Store, während Apps weiter ganz normale Secrets konsumieren. Der Trade-off: eine zusätzliche Komponente und eine Abhängigkeit, deren Ausfall das Erstellen neuer Secrets blockiert (bestehende bleiben).
Noch ein Begriff fürs Raster: Secret Types. Opaque ist der generische Default; kubernetes.io/tls strukturiert Zertifikat und Key (tls.crt/tls.key — das Format, das Ingress-Controller erwarten), kubernetes.io/dockerconfigjson trägt Registry-Credentials für imagePullSecrets, und ServiceAccount-Tokens haben einen eigenen Typ. Der Typ erzwingt die erwarteten Keys und macht den Zweck explizit.
Praxis: alle drei Wege plus der base64-Beweis
kind create cluster --name configlab
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=debug \
--from-literal=GREETING="Hallo aus der ConfigMap"
kubectl create secret generic db-creds \
--from-literal=username=app \
--from-literal=password='S3cret!'
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: configdemo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "env | grep -E 'LOG_LEVEL|GREETING|username'; ls -l /etc/secrets; sleep 3600"]
env:
- name: username
valueFrom:
secretKeyRef: { name: db-creds, key: username }
envFrom:
- configMapRef: { name: app-config }
volumeMounts:
- { name: creds, mountPath: /etc/secrets, readOnly: true }
volumes:
- name: creds
secret: { secretName: db-creds }
EOF
kubectl logs configdemo # env-Variablen + Secret-Dateien sichtbar
# Update-Semantik live: ConfigMap ändern …
kubectl patch configmap app-config -p '{"data":{"LOG_LEVEL":"info"}}'
kubectl exec configdemo -- sh -c 'echo $LOG_LEVEL' # immer noch "debug"!
# … und der base64-"Schutz" in einer Zeile entzaubert
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d; echo
Der Kontrast zwischen dem eingefrorenen LOG_LEVEL und dem in Klartext lesbaren Passwort ist die ganze Lektion in zwei Befehlen. Aufräumen: kind delete cluster --name configlab.
Typische Stolperfallen
„Es ist doch verschlüsselt, da steht so ein Hash”: base64 mit Verschlüsselung verwechseln ist der Klassiker — im Betrieb wie im Interview. Wer das sauber trennt (Codierung vs. Encryption at Rest vs. RBAC), hat den halben Themenkomplex.
Config geändert, nichts passiert: Env-Variablen aktualisieren sich nie zur Laufzeit, und ConfigMap-Änderungen lösen keinen Rollout aus. Ohne Hash-Annotation oder Reloader läuft Produktion munter mit alter Config weiter — besonders tückisch, weil neue Pods (etwa nach Node-Ausfall) die neue Config bekommen und die Flotte inkonsistent wird.
Secret im falschen Namespace: ConfigMaps und Secrets sind Namespace-gebunden; ein Pod kann nur Objekte aus seinem eigenen Namespace referenzieren. Der Fehler zeigt sich als CreateContainerConfigError bzw. hängender Mount — kubectl describe pod nennt das fehlende Objekt.
Größen- und Mengen-Grenzen ignorieren: ConfigMaps/Secrets sind auf 1 MiB begrenzt — sie sind Konfigurationstransport, kein Dateispeicher. Und hunderte einzeln gemountete Secrets pro Node erzeugen spürbare kubelet- und API-Last; Konsolidierung lohnt.
Interview-Vorbereitung
Auf „Wie gehst du mit Konfiguration und Secrets in Kubernetes um?” antwortest du in drei Schichten: Trennung (Image unveränderlich, Config/Credentials als ConfigMap/Secret in der Umgebung) → Konsum (env/envFrom für Variablen, Volume-Mount für Dateien — nur Mounts aktualisieren live, env nie) → Security ehrlich (base64 ≠ Verschlüsselung; Schutz kommt aus RBAC, Encryption at Rest via KMS, und extern verwalteten Secrets über ESO statt Secrets in Git).
Typische Follow-ups:
- „Sind Kubernetes Secrets sicher?” — Out-of-the-box nur begrenzt: base64-codiert, in etcd default unverschlüsselt; sicher werden sie durch RBAC, Encryption at Rest/KMS und externe Stores.
- „App sieht die neue ConfigMap nicht — warum?” — Als env eingebunden: erst beim Pod-Neustart; Rollout anstoßen (Hash-Annotation/Reloader) oder auf Volume-Mounts umstellen.
- „Wozu der External Secrets Operator?” — Vault/Cloud-Store als Quelle der Wahrheit, Git enthält nur Referenzen, Rotation propagiert automatisch; Preis: eine Komponente mehr.
- „Welche Secret Types kennst du?” —
Opaquegenerisch,kubernetes.io/tlsfür Zertifikate (Ingress),dockerconfigjsonfür Pull-Secrets; der Typ validiert die erwarteten Keys.
Zusammenfassung
ConfigMaps und Secrets trennen Konfiguration vom unveränderlichen Image: drei Konsumwege (env, envFrom, Volume-Mount) mit der entscheidenden Asymmetrie, dass nur gemountete Dateien live aktualisiert werden. Secrets sind base64-codiert, nicht verschlüsselt — echter Schutz entsteht erst durch restriktives RBAC, Encryption at Rest mit KMS-Provider und externe Secret-Stores via External Secrets Operator; Secret Types wie kubernetes.io/tls strukturieren die Spezialfälle. Immutable ConfigMaps und Hash-Annotationen machen Config-Änderungen zu kontrollierten Rollouts.
Neben Konfiguration brauchen Container noch eine zweite Zuteilung: CPU und Memory. Die nächste Lektion zeigt, wie Requests und Limits das Scheduling steuern, was QoS-Klassen bei Ressourcenknappheit entscheiden — und warum ein OOMKill kein Unfall, sondern Systemdesign ist.