Einleitung
Container-Images sollten in verschiedenen Umgebungen wiederverwendbar sein. Kubernetes trennt nicht vertrauliche Einstellungen in ConfigMaps und Zugangsdaten in Secrets. Beide Objekttypen können anschließend als Umgebungsvariablen oder Dateien in Container eingebunden werden.
Sie konfigurieren eine Anwendung mit beiden Mechanismen, überprüfen das Ergebnis sicher, aktualisieren die Konfiguration und beobachten, warum sich Umgebungsvariablen und eingebundene Volumes unterschiedlich aktualisieren.
Ziel ist nicht nur, zwei API-Objekte zu erstellen. Sie verfolgen jeden Wert vom Speicherort in Kubernetes bis in einen laufenden Container, ändern ihn anschließend und beobachten, wie sich der Bereitstellungsmechanismus auf das Aktualisierungsverhalten auswirkt.
Anwendungskonfiguration erstellen
Erstellen Sie in diesem Schritt einen Namespace und eine ConfigMap aus direkt angegebenen Werten. Eine ConfigMap speichert Zeichenketten unter data; diese werden nicht verschlüsselt.
Wechseln Sie zunächst in das Arbeitsverzeichnis des Labs. Der Namespace bündelt alle Objekte dieses Beispiels und verhindert, dass versehentlich gleichnamige Konfigurationen aus anderen Labs ausgewählt werden.
cd /home/labex/project/application-config
kubectl create namespace practice-config
kubectl create configmap app-settings -n practice-config --from-literal=APP_MODE=production --from-literal=LOG_LEVEL=info
kubectl get configmap app-settings -n practice-config -o yaml
Jedes --from-literal=KEY=VALUE fügt einen Eintrag hinzu. Die YAML-Ausgabe macht die gespeicherten Schlüssel sichtbar und eignet sich für nicht vertrauliche Konfigurationen.
Suchen Sie unter data: nach APP_MODE und LOG_LEVEL. Kubernetes speichert die gewünschten Werte, aber noch verwendet sie kein Objekt; das alleinige Erstellen einer ConfigMap verändert keinen Pod.
Der relevante Teil der Ausgabe ist bewusst schlicht gehalten:
data:
APP_MODE: production
LOG_LEVEL: info
kind: ConfigMap
Felder wie uid, resourceVersion und creationTimestamp werden von Kubernetes erzeugt und unterscheiden sich. Lesen Sie in diesem Schritt nur den Bereich data, da er die von Ihnen bereitgestellte Konfiguration enthält.
Ein Secret sicher erstellen
Erstellen Sie in diesem Schritt ein Secret, ohne seinen Wert in einer YAML-Datei abzulegen. Secret-Daten werden in der API base64-kodiert, aber nicht allein deshalb verschlüsselt, weil es sich um ein Objekt vom Typ Secret handelt. RBAC und die Verschlüsselung ruhender Daten bleiben weiterhin wichtig.
generic bedeutet, dass es sich um ein gewöhnliches Schlüssel-Wert-Secret und nicht um ein spezialisiertes TLS- oder Image-Pull-Secret handelt. Die Anführungszeichen um das Passwort verhindern, dass die Shell Satzzeichen interpretiert, falls der Wert später Sonderzeichen enthält.
kubectl create secret generic app-credentials -n practice-config --from-literal=username=appuser --from-literal=password='practice-pass'
kubectl get secret app-credentials -n practice-config
kubectl get secret app-credentials -n practice-config -o jsonpath='{.data.username}' | base64 --decode; echo
Die letzte Pipeline dekodiert zu Demonstrationszwecken nur den Benutzernamen. Vermeiden Sie es, Passwörter routinemäßig in Betriebsausgaben auszugeben.
Das Pipe-Zeichen leitet das kodierte Ergebnis des JSONPath-Ausdrucks an base64 --decode weiter. Das Semikolon startet anschließend ein echo, das nach dem dekodierten Wert einen Zeilenumbruch ausgibt, damit die nächste Shell-Eingabeaufforderung lesbar bleibt. Die Ausgabe appuser zeigt, wie die Kodierung funktioniert, und macht zugleich deutlich, warum base64 niemals als Sicherheitsgrenze betrachtet werden darf.
Der mittlere Befehl gibt Metadaten aus, ohne einen der beiden Werte offenzulegen:
NAME TYPE DATA AGE
app-credentials Opaque 2 ...
appuser
DATA=2 bedeutet, dass das Secret zwei Schlüssel enthält; es handelt sich nicht um eine Byte-Anzahl. Opaque ist der API-Typ für ein gewöhnliches Schlüssel-Wert-Secret.
Konfiguration in einen Pod einbinden
Verwenden Sie in diesem Schritt ConfigMap-Werte als Umgebungsvariablen und binden Sie das Secret als schreibgeschützte Dateien ein.
Trennen Sie vor dem Erstellen des Pods die beiden Bereitstellungswege im Manifest:
envFrommacht jeden ConfigMap-Schlüssel zu einer Umgebungsvariablen des Containers.volumesverbindet das Secret mit dem Pod, währendvolumeMountsfestlegt, an welcher Stelle dieses Volume in genau diesem Container erscheint.
Die Schleife des Pods führt keine Anwendungslogik aus. Sie hält den Container lediglich am Laufen, damit Sie beide Bereitstellungswege untersuchen können.
cd /home/labex/project/application-config
cat <<'EOF' > configured-app.yaml
apiVersion: v1
kind: Pod
metadata:
name: configured-app
namespace: practice-config
spec:
containers:
- name: app
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "while true; do sleep 30; done"]
envFrom:
- configMapRef:
name: app-settings
volumeMounts:
- name: credentials
mountPath: /etc/app-credentials
readOnly: true
volumes:
- name: credentials
secret:
secretName: app-credentials
EOF
Wenden Sie das Manifest an und warten Sie, bis der Pod bereit ist:
kubectl apply -f configured-app.yaml
kubectl wait --for=condition=Ready pod/configured-app -n practice-config --timeout=60s
Untersuchen Sie nun beide Bereitstellungswege aus dem Container heraus. printenv liest die Prozessumgebung, während ls die vom Secret-Volume bereitgestellten Dateien anzeigt:
kubectl exec configured-app -n practice-config -- printenv APP_MODE LOG_LEVEL
kubectl exec configured-app -n practice-config -- ls -l /etc/app-credentials
envFrom importiert alle ConfigMap-Schlüssel. Das Secret-Volume erstellt für jeden Schlüssel eine Datei und ist im Container als schreibgeschützt markiert.
Die erwartete Ausgabe der Umgebungsvariablen lautet zuerst production und anschließend info. Das Verzeichnis sollte Dateien mit den Namen username und password enthalten; die Dateinamen entsprechen den Secret-Schlüsseln.
Im laufenden Container sieht die Ausgabe ungefähr so aus:
production
info
total 0
lrwxrwxrwx ... password -> ..data/password
lrwxrwxrwx ... username -> ..data/username
Bei diesen Einträgen handelt es sich um symbolische Links, die von Kubernetes verwaltet werden. Diese Indirektion ermöglicht es kubelet, die eingebundenen Daten auf eine neue Version umzuschalten, ohne eine Datei umzuschreiben, während die Anwendung sie liest. Ihre Anwendung kann weiterhin die vertrauten Pfade /etc/app-credentials/username und /etc/app-credentials/password öffnen.
Eingebundene Secret-Daten aktualisieren
Aktualisieren Sie in diesem Schritt das Secret und beobachten Sie die eingebundene Datei. kubectl create ... --dry-run=client -o yaml | kubectl apply -f - erzeugt das gewünschte YAML lokal und leitet es direkt an apply weiter; - steht für die Standardeingabe.
Dieses Muster eignet sich, um ein imperativ erzeugtes Objekt zu aktualisieren, ohne es zu löschen. Die linke Seite gibt ein vollständiges Secret-Manifest aus, die rechte Seite überträgt dieses Manifest an den API-Server.
kubectl create secret generic app-credentials -n practice-config --from-literal=username=serviceuser --from-literal=password='rotated-pass' --dry-run=client -o yaml | kubectl apply -f -
Da das Secret ursprünglich mit kubectl create erstellt wurde, kann die erste Aktualisierung vor der Erfolgsmeldung folgende Warnung ausgeben:
Warning: resource secrets/app-credentials is missing the kubectl.kubernetes.io/last-applied-configuration annotation ...
secret/app-credentials configured
Die Warnung weist darauf hin, dass apply keinen vorherigen deklarativen Snapshot zum Vergleichen besitzt. Kubernetes fügt die Annotation während dieser Aktualisierung automatisch hinzu. Die Meldung configured bestätigt daher den Erfolg; Sie müssen das Secret nicht löschen und neu erstellen.
kubelet aktualisiert den Inhalt projizierter Secret-Volumes asynchron. Prüfen Sie wiederholt, bis der neue Benutzername erscheint:
current_username=""
for attempt in $(seq 1 24); do
current_username=$(kubectl exec configured-app -n practice-config -- cat /etc/app-credentials/username)
if [ "$current_username" = "serviceuser" ]; then
break
fi
sleep 5
done
echo "$current_username"
test "$current_username" = "serviceuser"
Die Schleife wiederholt die Prüfung bis zu zwei Minuten lang, da die Projektion des Volumes letztlich konsistent erfolgt. In der getesteten Umgebung wurde das API-Objekt sofort geändert, die Aktualisierung der eingebundenen Datei dauerte jedoch länger als eine Minute.
$(...) ist eine Befehlsersetzung: Die Shell führt seq 1 24 aus und verwendet die ausgegebenen Zahlen als Schleifenwerte. Jede Iteration speichert den aktuellen Dateiinhalt in current_username; break beendet die Schleife beim ersten Treffer, und sleep 5 verhindert eine kontinuierliche Abfrage der API. Der abschließende test liefert einen Fehler, wenn sich der Wert nie ändert, sodass der Befehl nicht unbemerkt mit veralteten Daten fortfährt. Die erfolgreiche Ausgabe lautet serviceuser.
Konfiguration von Umgebungsvariablen aktualisieren
Aktualisieren Sie in diesem Schritt LOG_LEVEL und vergleichen Sie den Wert mit dem laufenden Pod. Umgebungsvariablen werden beim Start eines Containers festgelegt. Eine Änderung der ConfigMap schreibt daher die aktuelle Prozessumgebung nicht neu.
Der Merge-Patch ändert nur einen Eintrag unter data. Das sofortige Auslesen des laufenden Prozesses zeigt einen wichtigen Unterschied zu einem eingebundenen Volume:
kubectl patch configmap app-settings -n practice-config --type=merge -p '{"data":{"LOG_LEVEL":"debug"}}'
kubectl exec configured-app -n practice-config -- printenv LOG_LEVEL
Der Pod gibt weiterhin info aus. Erstellen Sie ihn aus dem gespeicherten Manifest neu, damit der neue Wert übernommen wird:
configmap/app-settings patched
info
Das ist ein erwartetes Ergebnis und kein fehlgeschlagener Patch: Die ConfigMap enthält debug, aber der bereits laufende Prozess verwendet weiterhin die beim Start erstellte Umgebung.
cd /home/labex/project/application-config
kubectl delete pod configured-app -n practice-config --wait=true
kubectl apply -f configured-app.yaml
kubectl wait --for=condition=Ready pod/configured-app -n practice-config --timeout=60s
kubectl exec configured-app -n practice-config -- printenv LOG_LEVEL
Der ersetzte Pod gibt debug aus. Deshalb lösen Workloads in Produktionsumgebungen nach Änderungen an umgebungsvariablenbasierter Konfiguration häufig ein Rollout aus.
pod "configured-app" deleted
pod/configured-app created
pod/configured-app condition met
debug
Das Manifest verweist weiterhin nur über den Namen auf die ConfigMap; kein Wert wurde in configured-app.yaml kopiert. Beim Neuerstellen des Pods löst kubelet diese Referenz erneut auf und injiziert die aktuellen Daten.
Zusammenfassung
Sie haben Anwendungseinstellungen und Zugangsdaten von einem Image getrennt, ConfigMap-Daten als Umgebungsvariablen verwendet, Secret-Daten als Dateien eingebunden, ein projiziertes Secret ausgetauscht und einen Pod neu erstellt, um eine umgebungsvariablenbasierte Konfiguration zu aktualisieren.


