ConfigMap und Secret in Kubernetes: Konfiguration aus dem Image auslagern

1. Übersicht

Beim Verpacken einer Anwendung in ein Container-Image sollten Sie keine harten Werte wie Datenbank-URLs oder API-Schlüssel ins Image einbauen. Dieser Artikel hilft Ihnen, ConfigMap und Secret in Kubernetes zu verstehen — zwei Objekte zum Auslagern von Konfigurationen aus dem Image, deren Erstellung und Einbindung in Pods via Umgebungsvariablen oder Volumes, sowie die Integration mit Spring-Boot-Anwendungen. Danach können Sie selbstbewusst ein Deployment konfigurieren, das dynamisch Config liest, ohne das Image jedes Mal neu zu bauen, wenn sich ein Wert ändert.

2. Was ist ConfigMap?

ConfigMap ist ein Objekt, das Konfigurationsdaten in Key-Value-Form speichert, ohne Verschlüsselung, für nicht vertrauliche Daten: Servicenamen, Feature-Flags, Properties-Dateien…

Die Daten in einer ConfigMap existieren unabhängig von den Pods — Sie können die ConfigMap ändern, ohne das Image neu zu bauen. Sie müssen nur den Pod neu starten (oder einen Reload-Mechanismus verwenden, wenn die Anwendung ihn unterstützt).

3. ConfigMap erstellen

3.1. Aus einer YAML-Datei

apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "INFO"
  application.properties: |
    server.port=8080
    spring.jpa.hibernate.ddl-auto=validate

Hier enthält data zwei Arten: einfache Keys (APP_ENV, LOG_LEVEL) und einen Key mit gesamtem Dateiinhalt (application.properties). Anwenden mit:

kubectl apply -f order-service-config.yaml

3.2. Aus Literals oder vorhandenen Dateien

kubectl create configmap order-service-config \
  --from-literal=APP_ENV=production \
  --from-file=application.properties=./config/application.properties

Dieser Befehl erstellt die ConfigMap direkt über die Kommandozeile ohne YAML-Datei — praktisch für CI/CD-Skripte.

4. ConfigMap in Pods verwenden

4.1. Laden als Umgebungsvariablen

apiVersion: v1
kind: Pod
metadata:
  name: order-service
spec:
  containers:
    - name: order-service
      image: nonladev/order-service:1.0
      envFrom:
        - configMapRef:
            name: order-service-config

envFrom lädt alle Keys aus der ConfigMap als Umgebungsvariablen mit gleichem Namen. Der Container liest APP_ENV, LOG_LEVEL wie normale Umgebungsvariablen.

Wenn Sie nur einen bestimmten Key auswählen möchten:

      env:
        - name: APP_ENVIRONMENT
          valueFrom:
            configMapKeyRef:
              name: order-service-config
              key: APP_ENV

So können Sie die Umgebungsvariable im Container (APP_ENVIRONMENT) anders benennen als den ursprünglichen Key in der ConfigMap (APP_ENV).

4.2. Laden als Volume

      volumeMounts:
        - name: config-volume
          mountPath: /app/config
  volumes:
    - name: config-volume
      configMap:
        name: order-service-config

Jeder Key in der ConfigMap wird zu einer Datei in /app/config (z.B. /app/config/application.properties). Diese Methode eignet sich, wenn die Anwendung ganze Konfigurationsdateien lesen muss, statt einzelner Umgebungsvariablen.

Wichtig: Wenn Sie ConfigMap als Volume mounten, wird Kubernetes automatisch die Dateien aktualisieren, wenn sich die ConfigMap ändert (mit einer Verzögerung von einigen Sekunden bis Minuten, abhängig vom kubelet sync period). Bei Umgebungsvariablen (env/envFrom) lädt der Pod nicht automatisch neu — Sie müssen den Pod neu starten.

5. Secret: Was ist das und wie unterscheidet es sich von ConfigMap?

Secret hat eine ähnliche Struktur wie ConfigMap, ist aber für vertrauliche Daten gedacht: Passwörter, Tokens, Private Keys. Die Hauptunterschiede:

Kriterium ConfigMap Secret
Verschlüsselung Nein Base64-kodiert (nicht echte Verschlüsselung)
Speicherung in etcd Klartext Kann Verschlüsselung at-rest aktiviert sein
Anzeige via kubectl get Werte sichtbar Werte verborgen (nur base64 sichtbar)
Zweck Normale Konfiguration Vertrauliche Daten

Wichtig: Secret kodiert nur base64, verschlüsselt nicht standardmäßig. Jeder mit get secret-Berechtigung im Namespace kann dekodieren. Für echte Sicherheit müssen Sie Verschlüsselung at rest im etcd aktivieren oder einen externen Secret-Manager (Vault, AWS Secrets Manager) über External Secrets Operator integrieren.

6. Secret erstellen

6.1. Aus YAML (Werte müssen selbst base64-kodiert sein)

echo -n "P@ssw0rd123" | base64
# UEBzc3cwcmQxMjM=
apiVersion: v1
kind: Secret
metadata:
  name: order-service-secret
type: Opaque
data:
  DB_PASSWORD: UEBzc3cwcmQxMjM=

Das Feld data erfordert base64-kodierte Werte. Wenn Sie nicht selbst kodieren möchten, verwenden Sie stringData:

stringData:
  DB_PASSWORD: "P@ssw0rd123"

Kubernetes kodiert automatisch beim Speichern. stringData ist besser lesbar, wenn Sie Manifeste manuell schreiben.

6.2. Über Kommandozeile

kubectl create secret generic order-service-secret \
  --from-literal=DB_PASSWORD=P@ssw0rd123 \
  --from-literal=DB_USERNAME=order_app

7. Secret in Pods verwenden

Das Laden funktioniert genauso wie bei ConfigMap:

      envFrom:
        - secretRef:
            name: order-service-secret

oder Volume mounten:

      volumeMounts:
        - name: secret-volume
          mountPath: /app/secret
          readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: order-service-secret

Das Mounten von Secrets als Volumes sollte immer mit readOnly: true gesetzt werden und Sie sollten Dateizugriffsrechte mit defaultMode einschränken, falls nötig:

      secret:
        secretName: order-service-secret
        defaultMode: 0400

8. Integration mit Spring Boot

Spring Boot liest Umgebungsvariablen nach dem Relaxed-Binding-Standard, also brauchen Sie keinen speziellen Code — Sie müssen nur die Variablen nach der Konvention benennen.

      envFrom:
        - configMapRef:
            name: order-service-config
        - secretRef:
            name: order-service-secret
      env:
        - name: SPRING_DATASOURCE_USERNAME
          valueFrom:
            secretKeyRef:
              name: order-service-secret
              key: DB_USERNAME
        - name: SPRING_DATASOURCE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: order-service-secret
              key: DB_PASSWORD

Spring Boot mapped automatisch SPRING_DATASOURCE_USERNAME → spring.datasource.username. Im Code brauchen Sie nur normal @ConfigurationProperties:

@ConfigurationProperties(prefix = "spring.datasource")
public record DataSourceProperties(String url, String username, String password) {
}
@Service
public class PaymentService {

    private final DataSourceProperties dataSourceProperties;

    public PaymentService(DataSourceProperties dataSourceProperties) {
        this.dataSourceProperties = dataSourceProperties;
    }

    public String describeConnection() {
        return "Connecting as %s".formatted(dataSourceProperties.username());
    }
}

PaymentService weiß nichts von Kubernetes — es empfängt einfach Werte, die Spring Boot aus Umgebungsvariablen gebunden hat, die von ConfigMap/Secret bereitgestellt wurden.

8.1. Tests unabhängig vom Cluster

Sie testen die Logik, indem Sie Umgebungsvariablen via @TestPropertySource simulieren, ohne echtes Kubernetes:

@SpringBootTest
@TestPropertySource(properties = {
    "spring.datasource.username=order_app",
    "spring.datasource.password=test-pass"
})
class PaymentServiceTest {

    @Autowired
    private PaymentService paymentService;

    @Test
    void shouldDescribeConnectionUsingBoundProperties() {
        assertThat(paymentService.describeConnection())
            .isEqualTo("Connecting as order_app");
    }
}

Dieser Test bestätigt, dass DataSourceProperties richtig gebunden wird und PaymentService die richtigen Werte nutzt — vollständig entkoppelt davon, ob die Werte von echten ConfigMap oder Secret im Cluster kommen.

PaymentServiceTest > shouldDescribeConnectionUsingBoundProperties() PASSED

9. Dynamische Updates ohne Pod-Neustart

Wenn Sie ConfigMap/Secret als Volume mounten, werden die Dateien automatisch aktualisiert, aber Spring Boot liest nicht automatisch neu, es sei denn Sie:

  • Verwenden Spring Cloud Kubernetes (spring-cloud-starter-kubernetes-client-config), das ConfigMap pollt und @RefreshScope-Beans refreshed.
  • Oder implementieren selbst einen File-Watcher, der gemountete Dateien erneut liest.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring.cloud.kubernetes.config.enabled=true
spring.cloud.kubernetes.reload.enabled=true
spring.cloud.kubernetes.reload.mode=polling

Diese Konfiguration aktiviert das Polling von ConfigMap-Änderungen durch Spring Cloud Kubernetes nach einem bestimmten Zyklus und refresht mit @RefreshScope gekennzeichnete Beans — kein manueller Pod-Neustart nötig.

Wichtig: Kontinuierliches Polling verbraucht API-Server-Ressourcen. Bei großen Clustern sollten Sie spring.cloud.kubernetes.reload.mode=event (Watch-API) statt Polling erwägen.

10. Häufige Fehler und wann Sie ConfigMap/Secret nicht verwenden sollten

  • Passwörter in ConfigMap speichern: ConfigMap wird nicht verschlüsselt, kubectl get configmap -o yaml zeigt Werte offen. Verwenden Sie immer Secret für vertrauliche Daten.
  • Base64-Kodierung in data von Secret-YAML vergessen: führt zu illegal base64 data-Fehler. Verwenden Sie stringData, wenn Sie nicht selbst kodieren möchten.
  • ConfigMap geändert, aber Pod erhält neuen Wert nicht: da env/envFrom nicht automatisch neu laden — Sie müssen kubectl rollout restart deployment ausführen.
  • Secret-Volume gemountet, aber readOnly: true vergessen: Container könnte Dateien überschreiben, was zu Verwirrung beim Debugging führt.
  • Secrets als Langzeitgeheimnisspeicher verwenden: Secrets in Kubernetes eignen sich für Runtime-Konfiguration, können aber einen dedizierten Secret-Manager (Vault, AWS Secrets Manager) nicht ersetzen, wenn Sie Audit-Logs, automatische Rotation oder strenge Versionierung brauchen.
  • ConfigMap/Secret zu groß: Kubernetes limitiert die Objektgröße auf etwa 1 MiB (wegen etcd-Limits). Für große Config-Dateien sollten Sie die Verwendung von Volume-Mounts von PersistentVolumes oder Init-Containern erwägen, die aus Object Storage laden.

11. Fazit

ConfigMap und Secret helfen Ihnen, Konfigurationen aus dem Image auszulagern und das gleiche Image für mehrere Umgebungen (dev, staging, production) bereitzustellen — indem Sie einfach die Config-Objekte wechseln. ConfigMap ist für öffentliche Daten, Secret für vertrauliche Daten — aber denken Sie daran, dass Secret standardmäßig nur kodiert, nicht echte Verschlüsselung bietet. Mit Spring Boot nutzen Sie Relaxed Binding, um Werte über Umgebungsvariablen zu laden, ohne den Business-Code zu ändern, und Sie können Spring Cloud Kubernetes aktivieren, um Konfigurationen dynamisch zu aktualisieren, ohne Pods neu zu starten.

kubernetesconfigmapsecretspring-bootdevops