Kubernetes Kubernetes

Konfiguration: ConfigMaps und Secrets

Halte Einstellungen und Zugangsdaten aus deinen Images heraus, injiziere sie in Pods und vermeide die häufigen Fallen.

14 min Fortgeschritten

Ist eine Datenbank-URL oder ein Log-Level in dein Container-Image eingebacken, bedeutet eine Änderung, das Image neu zu bauen, zu pushen und neu zu deployen. Kubernetes trennt Konfiguration von Images mit zwei Objekten: ConfigMaps für gewöhnliche Einstellungen und Secrets für sensible Werte. Diese Anleitung zeigt, wie du beide erstellst, wie du sie in einen Pod bekommst und was Secrets schützen und was nicht.

Eine ConfigMap erstellen

Eine ConfigMap ist eine Menge von Schlüssel-Wert-Paaren. Werte können kurze Strings oder ganze Dateien sein:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  LOG_LEVEL: info
  API_URL: http://api.internal:8080
  motd.txt: |
    Welcome to the demo environment

Du kannst eine auch von der Kommandozeile erstellen, was für Dateien praktisch ist:

kubectl create configmap web-config \
  --from-literal=LOG_LEVEL=info \
  --from-file=motd.txt
kubectl describe configmap web-config

ConfigMaps sind für kleine, nicht sensible Daten gedacht (das Limit liegt bei 1 MiB) und gehören zu einem Namespace. Ein Pod kann nur ConfigMaps aus seinem eigenen Namespace nutzen.

Als Umgebungsvariable oder als Datei nutzen

Derselbe Pod kann eine ConfigMap auf beide Arten konsumieren:

apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: web
      image: nginx:1.25
      env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: web-config
              key: LOG_LEVEL
      volumeMounts:
        - name: config
          mountPath: /usr/share/nginx/html/motd.txt
          subPath: motd.txt
  volumes:
    - name: config
      configMap:
        name: web-config

Hier passieren zwei Dinge. Der env-Eintrag kopiert einen Schlüssel in eine Umgebungsvariable. (envFrom kann jeden Schlüssel auf einmal importieren, wobei Schlüssel, die keine gültigen Variablennamen sind, übersprungen werden.) Die Einträge volumes und volumeMounts mounten die ConfigMap als Dateien, eine Datei pro Schlüssel.

Mountest du die gesamte ConfigMap wie folgt in ein Verzeichnis, wird das Verzeichnis durch die gemounteten Dateien ersetzt:

      volumeMounts:
        - name: config
          mountPath: /etc/web

Das ist für ein Verzeichnis, das dir gehört, in Ordnung, verdeckt aber alles, was bereits unter diesem Pfad liegt. Ein Mount über ein bestehendes Konfigurationsverzeichnis wie /etc/nginx kann die Anwendung kaputt machen, und der Container kann mit einem Fehler „Datei nicht gefunden“ in eine Absturzschleife geraten. Dafür ist subPath da, wie im ersten Beispiel: Es mountet einen einzelnen Schlüssel als einzelne Datei und lässt den Rest des Verzeichnisses in Ruhe. Der Kompromiss: Mit subPath gemountete Dateien erhalten spätere Aktualisierungen der ConfigMap nicht.

Prüfe, was im Container gelandet ist:

kubectl exec web -- ls /usr/share/nginx/html
kubectl exec web -- env | grep LOG_LEVEL

Zugangsdaten in einem Secret speichern

Ein Secret sieht aus wie eine ConfigMap, ist aber für Passwörter, Tokens und Schlüssel gedacht:

kubectl create secret generic db-credentials \
  --from-literal=username=app \
  --from-literal=password='S3cr3t-value'

In YAML nutze stringData, um Klartext zu schreiben und Kubernetes kodieren zu lassen:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: app
  password: S3cr3t-value

Wenn du das kodierte Feld data selbst schreibst, kodiere mit echo -n 'value' | base64. Ohne -n wird der abschließende Zeilenumbruch Teil des Passworts, und die Authentifizierung schlägt auf verwirrende Weise fehl. Ein Secret konsumierst du auf dieselbe Weise wie eine ConfigMap:

      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Committe keine Secret-Manifeste mit echten Werten in Git. Werkzeuge wie Sealed Secrets, External Secrets Operator und SOPS gibt es, damit du verschlüsselte oder referenzierte Secrets in einem Repository halten kannst.

Wissen, was Secrets schützen und was nicht

Base64 ist eine Kodierung, keine Verschlüsselung. Jeder, der das Secret über die API lesen kann, kann es dekodieren:

kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

Standardmäßig werden Secrets außerdem unverschlüsselt in etcd gespeichert. Um sie tatsächlich zu schützen:

  • Beschränke mit RBAC, wer Secrets lesen kann, und denke daran, dass jeder, der in einem Namespace Pods erstellen kann, dessen Secrets mounten kann.
  • Aktiviere die Verschlüsselung ruhender Daten für etcd oder nutze die Schlüsselverwaltung eines Cloud-Anbieters.
  • Ziehe einen externen Secret Manager in Betracht, wenn deine Anforderungen streng sind.

Umgebungsvariablen sind praktisch, aber leicht zu leaken, da sie in kubectl exec ... env, in Crash-Dumps und manchmal in Logs auftauchen. Ein Secret als Datei zu mounten ist für sensible Werte oft die bessere Wahl.

Konfiguration bewusst aktualisieren

Umgebungsvariablen werden einmal gelesen, wenn der Container startet, sodass eine geänderte ConfigMap oder ein geändertes Secret laufende Pods nicht erreicht. Starte sie neu, um die Änderung aufzugreifen:

kubectl rollout restart deployment/web

Dateien aus einem Mount des ganzen Verzeichnisses werden irgendwann aktualisiert, aber viele Anwendungen lesen ihre Konfiguration ohnehin nur beim Start. Für automatisierte Setups sorge dafür, dass sich das Pod-Template ändert, wann immer sich die Konfiguration ändert. Dafür sind die Config-Hash-Techniken in der Argo-CD-Anleitung zu Helm und Kustomize da.

Beliebte Anleitungen