Veröffentlicht am

Config Management: ConfigMaps, Secrets und External Secrets

Teilen:
Authors

Kubernetes Config Management: ConfigMaps, Secrets und External Secrets Operator

TL;DR

  • ConfigMaps fuer nicht-sensible Daten, Secrets fuer Credentials -- aber native Secrets sind nur Base64-kodiert, nicht verschluesselt
  • Encryption at Rest in etcd ist Pflicht fuer jede produktive Umgebung
  • External Secrets Operator synchronisiert Secrets aus Vault, AWS KMS oder Azure Key Vault automatisch in den Cluster
  • Kustomize fuer einfache Umgebungs-Overrides, Helm fuer komplexere Szenarien mit Conditionals und Loops
  • GitOps (ArgoCD oder Flux) ist der sauberste Weg, Konfigurationsaenderungen auditierbar zu machen

Das Problem: Warum Config Management in Kubernetes nicht trivial ist

Auf den ersten Blick wirkt Konfigurationsmanagement in Kubernetes simpel. ConfigMap fuer Settings, Secret fuer Passwoerter, fertig. In der Praxis tauchen aber schnell Probleme auf.

ConfigMaps und Secrets haben kein eingebautes Versioning. Aendert jemand einen Wert, gibt es keinen automatischen Rollback. Pods, die ConfigMaps als Environment Variables lesen, bekommen Aenderungen erst nach einem Restart mit. Secrets liegen standardmaessig als Base64 in etcd -- jeder mit etcd-Zugriff kann sie lesen.

Dazu kommt die Frage der Umgebungstrennung: Dev, Staging, Prod brauchen unterschiedliche Werte fuer dieselben Keys. Und sobald mehrere Teams auf demselben Cluster arbeiten, wird die Zugriffskontrolle auf Konfigurationen relevant.

Dieses Post zeigt die drei Ebenen des Config Managements -- native Objekte, Templating und externe Secret-Stores -- und wann welche Ebene sinnvoll ist.

Ebene 1: ConfigMaps und Secrets richtig einsetzen

ConfigMaps fuer nicht-sensible Konfiguration

ConfigMaps speichern Key-Value-Paare oder ganze Dateien. Sie lassen sich als Environment Variables injizieren oder als Volume mounten.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-gateway-config
  namespace: production
data:
  LOG_LEVEL: "warn"
  MAX_CONNECTIONS: "100"
  CACHE_TTL_SECONDS: "300"
  nginx.conf: |
    worker_processes auto;
    events {
      worker_connections 1024;
    }
    http {
      upstream backend {
        server backend-svc:8080;
      }
      server {
        listen 80;
        location / {
          proxy_pass http://backend;
          proxy_connect_timeout 5s;
          proxy_read_timeout 30s;
        }
      }
    }

Wichtiger Punkt: ConfigMaps, die als Volume gemountet sind, werden bei Aenderungen automatisch aktualisiert (mit Delay von bis zu 60 Sekunden). ConfigMaps, die als Environment Variables geladen werden, erfordern einen Pod-Restart. Wer automatische Reloads braucht, setzt auf Volume Mounts oder einen Sidecar wie Reloader.

Secrets: Notwendig, aber allein nicht ausreichend

apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
  namespace: production
type: Opaque
stringData:
  DB_HOST: "postgres-primary.db.svc.cluster.local"
  DB_USER: "app_service"
  DB_PASSWORD: "r4nd0m-g3n3r4t3d-p4ssw0rd"
  DB_SSL_MODE: "verify-full"

Die stringData-Variante ist lesbarer als data mit Base64, wird aber bei kubectl get secret -o yaml trotzdem als Base64 angezeigt. Beides ist keine Verschluesselung.

Encryption at Rest aktivieren

Ohne Encryption at Rest liegen Secrets im Klartext in etcd. Die Konfiguration erfolgt ueber den API-Server:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key-2025
              secret: <base64-encoded-32-byte-key>
      - identity: {}

Nach dem Aktivieren muessen bestehende Secrets einmal neu geschrieben werden, damit sie verschluesselt abgelegt werden:

# Alle Secrets im Cluster neu verschluesseln
kubectl get secrets --all-namespaces -o json | \
  kubectl replace -f -

# Verifizieren: etcdctl muss verschluesselte Daten zeigen
ETCDCTL_API=3 etcdctl get /registry/secrets/production/database-credentials | hexdump -C | head -20

Ebene 2: Templating fuer Umgebungstrennung

Kustomize: Overlays ohne Templating-Sprache

Kustomize arbeitet mit Basis-Manifesten und umgebungsspezifischen Patches. Kein Templating noetig, alles bleibt valides YAML.

Verzeichnisstruktur:

config/
  base/
    configmap.yaml
    kustomization.yaml
  overlays/
    dev/
      kustomization.yaml
    staging/
      kustomization.yaml
    production/
      kustomization.yaml
      configmap-patch.yaml

Der Production-Overlay kann so aussehen:

# config/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patchesStrategicMerge:
  - configmap-patch.yaml
configMapGenerator:
  - name: api-gateway-config
    behavior: merge
    literals:
      - LOG_LEVEL=error
      - MAX_CONNECTIONS=500
      - CACHE_TTL_SECONDS=600

Helm vs. Kustomize: Wann was

KriteriumKustomizeHelm
LernkurveFlach, reines YAMLSteiler, Go Templates
Conditionals/LoopsNicht moeglichJa, volle Templating-Logik
Packaging/DistributionNicht vorgesehenCharts mit Versionierung
Umgebungs-OverridesOverlays und Patchesvalues.yaml pro Umgebung
RollbackUeber Git-Historyhelm rollback mit Release-History
Community-ChartsN/AGrosses Ecosystem (Bitnami, etc.)

Faustregel: Kustomize fuer eigene Anwendungen mit einfacher Umgebungstrennung. Helm, wenn man Third-Party-Software deployt oder komplexere Templating-Logik braucht. Beides laesst sich kombinieren -- Helm zum Rendern, Kustomize fuer letzte Overrides.

Ebene 3: External Secrets Operator und Vault-Integration

Native Secrets haben ein fundamentales Problem: Wer Zugriff auf das Git-Repo oder etcd hat, hat Zugriff auf die Secrets. Der External Secrets Operator (ESO) loest das, indem Secrets nur im externen Store (Vault, AWS Secrets Manager, Azure Key Vault) liegen und on-demand in den Cluster synchronisiert werden.

External Secrets Operator Setup

# ExternalSecret, die Credentials aus HashiCorp Vault holt
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  refreshInterval: 5m
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: database-credentials
    creationPolicy: Owner
    deletionPolicy: Retain
  data:
    - secretKey: DB_USER
      remoteRef:
        key: secret/data/production/database
        property: username
    - secretKey: DB_PASSWORD
      remoteRef:
        key: secret/data/production/database
        property: password
    - secretKey: DB_HOST
      remoteRef:
        key: secret/data/production/database
        property: host

Der dazugehoerige ClusterSecretStore verbindet sich per Kubernetes Service Account Authentication mit Vault:

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com:8200"
      path: secret
      version: v2
      auth:
        kubernetes:
          mountPath: kubernetes
          role: external-secrets
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

Vergleich der Secret-Management-Ansaetze

AnsatzSicherheitKomplexitaetKostenEmpfehlung
Native Secrets + Encryption at RestMittelNiedrigKeineEinstieg, kleine Teams
Sealed Secrets (Bitnami)HochNiedrigKeineSecrets in Git verschluesseln
External Secrets + VaultSehr hochMittelVault-BetriebAb 5+ Microservices
External Secrets + Cloud KMSSehr hochNiedrigPay-per-UseCloud-native Setups

Fuer die meisten Teams ist der Einstieg mit nativen Secrets und aktivierter Encryption at Rest der richtige erste Schritt. Sealed Secrets ist ein guter Zwischenschritt, wenn man Secrets in Git committen will. Der External Secrets Operator wird relevant, sobald mehrere Teams oder Cluster auf dieselben Secrets zugreifen muessen.

GitOps-Integration: Konfigurationen auditierbar ausrollen

Der sauberste Workflow fuer Config Management laeuft ueber GitOps. Jede Konfigurationsaenderung ist ein Git-Commit mit Review, Autor und Timestamp.

Der Ablauf:

  1. Entwickler aendert ConfigMap oder ExternalSecret-Definition im Git-Repo
  2. Pull Request wird reviewed und gemergt
  3. ArgoCD oder Flux erkennt die Aenderung und synchronisiert den Cluster
  4. Bei Problemen: Git Revert und Resync

Das hat zwei grosse Vorteile. Erstens Nachvollziehbarkeit: Jede Konfigurationsaenderung ist in der Git-History sichtbar -- wer, wann, warum. Das macht DSGVO-Audits deutlich einfacher. Zweitens Rollback: Ein git revert bringt den Cluster zurueck in den vorherigen Zustand, ohne dass jemand kubectl edit ausfuehren muss.

Typische Fehler und wie man sie vermeidet

Secrets in Git committen. Passiert staendig, besonders in der Anfangsphase. Loesung: Pre-commit Hooks mit detect-secrets oder gitleaks, plus Sealed Secrets oder ESO fuer den eigentlichen Workflow.

ConfigMaps nicht versionieren. Wenn eine ConfigMap-Aenderung Probleme verursacht, braucht man einen Rollback-Pfad. Loesung: ConfigMaps mit Hash-Suffix im Namen (Kustomize macht das automatisch mit configMapGenerator), sodass ein Rollback des Deployments automatisch die alte ConfigMap nutzt.

Zu breite RBAC-Regeln fuer Secrets. get secrets auf Namespace-Ebene gibt Zugriff auf alle Secrets im Namespace. Loesung: Secrets in dedizierte Namespaces isolieren oder RBAC auf einzelne Secret-Namen einschraenken.

Environment Variables statt Volume Mounts. Env Vars sind bequem, aber sie erfordern Pod-Restarts bei Aenderungen und tauchen in kubectl describe pod auf. Volume Mounts sind sicherer und unterstuetzen Live-Updates.

Weitergehende Themen


Wer Unterstuetzung beim Aufbau einer Config-Management-Strategie braucht -- von der Vault-Integration bis zum GitOps-Workflow -- kann sich gerne unter /kontakt melden.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen