Eine geänderte CRD schreibt gespeicherte Objekte nicht neu. StorageVersionMigration kann den Rewrite über den API-Server auslösen, wenn der Cluster die API-Gruppe storagemigration.k8s.io anbietet und der zugehörige Controller aktiv ist. Das Runbook ermittelt deshalb die verfügbare API-Version per Discovery, statt pauschal v1 vorauszusetzen. Ein erfolgreicher Controllerstatus allein reicht für den Rückbau nicht, weil er weder einen Restore-Test noch die Umstellung abhängiger Clients bestätigt.
Der Ablauf verbindet die Migration gespeicherter CRD-Versionen mit der Neuverschlüsselung von Secrets nach einer Schlüsselrotation. Er setzt Kenntnisse über den API-Server, etcd, CRDs und Secrets voraus. Der Kubernetes-Grundkurs behandelt diese Komponenten und die zugehörigen Administrationsabläufe.
Was ist eine Kubernetes-Storage-Version?
Eine Kubernetes-Storage-Version ist die API-Version, deren Schema der API-Server beim Speichern in etcd verwendet. Served-Versionen legen dagegen fest, über welche Endpunkte Clients dasselbe Objekt lesen oder schreiben. Beim Zugriff konvertiert der API-Server zwischen beiden Darstellungen, sofern die interne Konvertierung oder der CRD-Webhook funktioniert.
In spec.versions markiert genau ein Eintrag mit storage: true das aktuelle Persistenzschema. Mehrere Versionen dürfen gleichzeitig served: true verwenden. Ein Client kann deshalb über v1beta1 schreiben, während etcd das konvertierte Objekt im Schema von v1 erhält.
Die Storage-Version gilt nur für neue Schreibvorgänge. Warum eine CRD-Änderung vorhandene Objekte nicht erfasst, zeigt der nächste Abschnitt.
Warum migriert ein CRD-Update keine vorhandenen Objekte?
Neue oder veränderte Custom Resources speichert der API-Server mit der aktuell markierten Storage-Version. Unveränderte Objekte können weiterhin als v1alpha1 oder v1beta1 in etcd liegen, obwohl alle Clients bereits den neuen Endpunkt verwenden.
Entfernt das Team die alte Version aus der CRD, bevor jedes Objekt neu geschrieben wurde, kann dem API-Server das Schema oder der Konvertierungspfad für die alte Darstellung fehlen. status.storedVersions führt deshalb alle Versionen auf, die als Storage-Version verwendet wurden. Das Feld zählt keine Objekte und beweist nicht, dass noch Altdaten vorhanden sind.
Vor dem Rewrite braucht das Team deshalb eine Bestandsaufnahme, einen Wiederherstellungsweg und einen geprüften Konvertierungspfad.
Cluster auf die Migration vorbereiten
Vor der ersten StorageVersionMigration steht ein etcd-Snapshot. Ein Snapshot von einem gesunden etcd-Mitglied erfasst den Clusterzustand zum Zeitpunkt der Sicherung. Bei verschlüsselten Secrets gehören die verwendete EncryptionConfiguration und das alte Schlüsselmaterial in einen getrennt geschützten Wiederherstellungssatz, da der Snapshot ohne diese Schlüssel nicht vollständig lesbar ist.
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=<ca.crt> --cert=<client.crt> --key=<client.key> snapshot save /secure-backup/etcd-before-svm.db
etcdutl snapshot status /secure-backup/etcd-before-svm.db -w table
Der Statusbefehl prüft Metadaten und Lesbarkeit der Snapshot-Datei. Erst ein Restore-Test in einer isolierten Umgebung belegt, dass sich der gesicherte Zustand wiederherstellen lässt. Bei verwalteten Control Planes tritt das Sicherungs- und Wiederherstellungsverfahren des Plattformbetreibers an die Stelle des direkten etcd-Zugriffs.
Die Discovery-API zeigt, ob die API-Gruppe verfügbar ist und welche Version der Cluster bevorzugt. Die Zuweisung der Shell-Variablen benötigt jq.
kubectl get --raw='/apis/storagemigration.k8s.io'
kubectl api-resources --api-group=storagemigration.k8s.io
kubectl auth can-i create storageversionmigrations.storagemigration.k8s.io
SVM_API_VERSION="$(kubectl get --raw='/apis/storagemigration.k8s.io' | jq -r '.preferredVersion.groupVersion')"
printf '%s\n' "$SVM_API_VERSION"
Prüfpunkte für CRDs mit Webhook-Konvertierung
- Das referenzierte Service-Objekt besitzt erreichbare EndpointSlices
- Das CA-Bundle in
spec.conversion.webhook.clientConfigvalidiert das Serverzertifikat - Der Webhook verarbeitet ConversionReview-Anfragen für die Quell- und die Zielversion
- Die Netzwerkregeln erlauben Verbindungen vom API-Server zum Webhook
Während des Rewrites bleibt die CRD unverändert. Conversion-Fehler, ein nicht erreichbarer API-Server oder neue Schreibfehler stoppen alle weiteren Rückbauschritte. Sind Sicherung, API und Webhook geprüft, folgt die Bestandsaufnahme.
Veraltete CRD-Storage-Versionen finden
status.storedVersions wird mit der Version verglichen, die unter spec.versions den Wert storage: true trägt. Die folgende Ausgabe liefert eine clusterweite Übersicht:
kubectl get crd -o custom-columns='CRD:.metadata.name,STORAGE:.spec.versions[?(@.storage==true)].name,STORED:.status.storedVersions,SCOPE:.spec.scope'
Jede Zeile, in der STORED neben der aktuellen Storage-Version weitere Einträge enthält, gehört in das Migrationsinventar. Im Inventar stehen:
- Die CRD ist mit ihrem vollständigen Namen erfasst
- Der GroupVersionResource-Wert bezeichnet die Zielressource
- Der Scope unterscheidet clusterweite und namensraumgebundene Objekte
- Die Objektzahl dient der Zeit- und Lastplanung
- Der zuständige Conversion-Webhook ist dokumentiert
Ein alter Eintrag in storedVersions genügt, um einen kontrollierten Rewrite einzuplanen. Aus Group, aktueller Storage-Version und pluralem Ressourcennamen entsteht anschließend das Migrationsobjekt.
StorageVersionMigration-Manifest für eine CRD erstellen
Für die CRD crontabs.example.com verweist spec.resource auf die aktuelle Storage-Version. Der Ressourcenname ist der plurale API-Name crontabs, nicht der Kind-Name CronTab.
SVM_API_VERSION="$(kubectl get --raw='/apis/storagemigration.k8s.io' | jq -r '.preferredVersion.groupVersion')"
cat <<EOF | kubectl apply -f -
apiVersion: ${SVM_API_VERSION}
kind: StorageVersionMigration
metadata:
name: crontabs-example-com-to-v1
spec:
resource:
group: example.com
version: v1
resource: crontabs
EOF
kubectl get storageversionmigration crontabs-example-com-to-v1
Der Controller liest den Bestand über den API-Server und speichert die Objekte erneut. Ein eigener Objektname pro Versuch hält fehlgeschlagene und wiederholte Läufe auseinander. Den Abschluss zeigen die Status-Conditions.
Migration mit Conditions und kubectl wait überwachen
Ein abgeschlossener Lauf setzt die Condition Succeeded=True. Der Timeout richtet sich nach der Objektzahl, der Auslastung des API-Servers und der Latenz des Conversion-Webhooks. Läuft nur der Client-Timeout ab, arbeitet der Controller möglicherweise weiter.
kubectl wait --for='condition=Succeeded=True' storageversionmigration/crontabs-example-com-to-v1 --timeout=30m
kubectl get storageversionmigration crontabs-example-com-to-v1 -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{" message="}{.message}{"\n"}{end}'
Bei einem Fehlschlag liefern die Condition sowie die Protokolle des API-Servers und des Webhooks die gemeinsame Fehlerbasis. Wurde die CRD während des Laufs verändert, braucht der wiederholte Durchlauf nach der Korrektur einen neuen Namen.
Der gleiche Rewrite über den API-Server kann Secrets nach einer Schlüsselrotation mit dem neuen Schlüssel speichern.
Secrets nach der Schlüsselrotation neu verschlüsseln
In der EncryptionConfiguration steht der neue Schlüssel innerhalb des Providers an erster Stelle. Alle API-Server-Instanzen müssen während des Rollouts beide Schlüssel lesen können. Erst nachdem jede Instanz die neue Reihenfolge verwendet, werden neue Schreibvorgänge mit key-new verschlüsselt.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key-new
secret: <BASE64_ENCODED_32_BYTE_KEY>
- name: key-old
secret: <BASE64_ENCODED_PREVIOUS_KEY>
Das Beispiel setzt voraus, dass keine unverschlüsselten Secrets mehr gelesen werden müssen. Enthält die bestehende Konfiguration einen identity-Provider für ältere Klartextdaten, bleibt er bis zu einer getrennt geprüften Migration erhalten. Die Kubernetes-Administration behandelt die EncryptionConfiguration zusammen mit Storage und Zugriffskontrollen.
Nach dem Rollout fordert ein clusterweiter Lesezugriff alle Secrets über den API-Server an. Die Ausgabe enthält nur Ressourcennamen und landet nicht im Terminal. Der ausführende Account benötigt dafür weitreichende Leserechte.
kubectl get secrets --all-namespaces -o name >/dev/null
Die StorageVersionMigration für Secrets verwendet eine leere Group und die Version v1:
cat <<EOF | kubectl apply -f -
apiVersion: ${SVM_API_VERSION}
kind: StorageVersionMigration
metadata:
name: secrets-reencrypt-key-new
spec:
resource:
group: ""
version: v1
resource: secrets
EOF
Der alte Schlüssel bleibt in jeder API-Server-Konfiguration, bis der Rewrite erfolgreich abgeschlossen und der neue Encryption-Envelope in etcd geprüft ist. Danach folgt die getrennte Kontrolle des CRD-Status.
Ergebnis im CRD-Status und in etcd verifizieren
Die Condition bestätigt, dass der Controller seinen Lauf abgeschlossen hat. Bei einer CRD zeigt eine zusätzliche Abfrage, welche Versionen weiterhin in status.storedVersions stehen.
kubectl get crd crontabs.example.com -o jsonpath='{.status.storedVersions}{"\n"}'
Bleibt die alte Version nach dem Rewrite eingetragen, entfernt ein Status-Patch sie erst nach der Prüfung des unveränderten CRD-Schemas und der erfolgreichen Condition. Der Patch migriert keine Daten. Er dokumentiert lediglich, dass der abgeschlossene Rewrite keine Objekte in der alten Storage-Version zurückgelassen hat.
kubectl patch crd crontabs.example.com --subresource=status --type=merge -p '{"status":{"storedVersions":["v1"]}}'
Direkte etcd-Prüfungen gehören auf einen abgeschotteten Administrationspfad. Bei einer nicht vertraulichen Custom Resource kann ein ausgewählter Datensatz auf die serialisierte API-Version geprüft werden. Bei Secrets darf das Prüfwerkzeug nur den Encryption-Envelope wie k8s:enc:aescbc:v1:key-new: ausgeben, niemals den vollständigen etcd-Wert oder entschlüsselte Daten.
Wenn der CRD-Status nur noch die Zielversion führt und alle Secret-Datensätze den neuen Envelope tragen, kann der eigentliche Rückbau beginnen.
Alte API-Versionen und Schlüssel entfernen
Bei einer CRD wird die alte Version zunächst mit served: false für neue Clientzugriffe geschlossen. Nach Tests der abhängigen Controller und Clients kann der Versionsblock aus spec.versions entfallen. status.storedVersions darf die entfernte Version zu diesem Zeitpunkt nicht mehr enthalten.
Bei der Secret-Verschlüsselung wird der alte Schlüssel aus allen API-Server-Konfigurationen entfernt. Nach dem Rollout bestätigt ein weiterer clusterweiter Lesezugriff, dass die API-Server die Datensätze weiterhin laden. Snapshot und altes Schlüsselmaterial bleiben gemäß der internen Wiederherstellungsrichtlinie geschützt verfügbar, solange sie für die Rücksicherung dieses Snapshots benötigt werden.
Vor dem Abschluss bleiben Fehlerbehandlung und die Folgen eines Cluster-Rollbacks zu prüfen.
Troubleshooting und Rollback-Checkliste
- Prüfe bei Conversion-Fehlern den Webhook-Service, die EndpointSlices, das CA-Bundle und die unterstützten ConversionReview-Versionen
- Vergleiche bei Schreibkonflikten den CRD-Zustand mit dem Startzeitpunkt der Migration und erstelle nach der Korrektur ein neues Migrationsobjekt
- Behandle
resourceVersionals undurchsichtigen Wert und leite aus seinem Format keine Kompatibilität ab - Stimme Migrationen für aggregierte APIs mit dem Betreiber des zugehörigen API-Servers ab, statt das Verhalten nativer Ressourcen vorauszusetzen
- Behalte bei einer fehlgeschlagenen Secret-Migration den alten Schlüssel in jeder EncryptionConfiguration
- Lass alte CRD-Versionen verfügbar, solange die erfolgreiche Condition oder die Prüfung von
storedVersionsfehlt - Behandle einen etcd-Restore als Cluster-Rollback, da alle Änderungen seit dem Snapshot verloren gehen können
- Wiederhole nach jeder Korrektur die Discovery-, Lese-, Condition- und Statusprüfungen
Beginne mit einer einzelnen CRD ohne kritische Arbeitslast und führe den gesamten Ablauf bis zum getesteten Rückbau durch, bevor weitere Ressourcen folgen.