Ich habe in meinem Pod auf Azure Kubernetes Service (AKS) eine Notiz geschrieben, neben die Logdatei, die die App führt. Dann habe ich den Pod gelöscht. Kubernetes hat in 22 Sekunden einen neuen gestartet. Eine der beiden Dateien hat überlebt. Die andere war weg.
Dieser Beitrag zeigt, wo die Daten eines Pods wirklich liegen, warum ReadWriteOnce einen Node bedeutet und nicht einen Pod, und wann die Azure-Disk hinter einem Claim endgültig gelöscht wird. Alles stammt aus der Übung des Microsoft-Learn-Moduls und ein paar zusätzlichen Schritten, ausgeführt in meinem eigenen Cluster.
Das Wichtigste
- Die eigenen Dateien eines Containers starten leer. Jeder Container startet aus seinem Image plus einer frischen, leeren beschreibbaren Schicht. Ein ersetzter Pod bekommt einen neuen Container, und selbst nach einem Absturz startet das Kubelet den Container in einem sauberen Zustand neu. Was Sie später noch brauchen, gehört auf ein Volume.
- Ein Claim ist eine Anfrage, ein Volume der Speicher, die Disk das, was Azure abrechnet. Ein PersistentVolumeClaim fragt nach Größe, Zugriffsmodus und Storage-Klasse; die Klasse erzeugt ein PersistentVolume, und auf AKS legt die Standardklasse
managed-csieine Azure Managed Disk in der Node-Ressourcengruppe an. - ReadWriteOnce bedeutet ein Node, nicht ein Pod. Zwei Pods auf demselben Node haben sich eine Disk geteilt. Ein Pod auf einem zweiten Node wartete in
ContainerCreating, weil die Disk am ersten Node hing. Für mehrere Nodes nehmen Sie Azure Files (azurefile-csi, ReadWriteMany). - Den Claim zu löschen, löscht die Disk. Die eingebauten Storage-Klassen haben die Reclaim-Policy
Delete. Eine Storage-Klasse mitreclaimPolicy: Retainbehält die Disk, wenn der Claim weg ist, und Sie löschen sie selbst.
Weg mit dem Container
In der Übung betreibt ein Deployment eine kleine API. Ein Deployment hält eine feste Anzahl Pods aus einer Vorlage am Laufen, und ein Pod ist die Einheit, die Ihren Container umschließt. Die App schreibt für jede Anfrage eine Zeile in eine Logdatei:
> python cfg.py get /logs/summaryGET /logs/summary -> 200{… "total_requests":264, … "first_request":"2026-10-28T12:31:16.705746", …}
Dann habe ich eine Notiz in den Ordner /tmp des Containers geschrieben, den Pod gelöscht und gewartet, bis das Deployment ihn ersetzt hatte:
> kubectl exec deploy/aks-config-api -- sh -c "echo note > /tmp/note.txt; ls /tmp"note.txt> kubectl delete pod -l app=aks-config-apipod "aks-config-api-876f7899f-jqpxj" deleted from default namespace> kubectl rollout status deployment aks-config-api --timeout 300sdeployment "aks-config-api" successfully rolled out
Im neuen Pod war /tmp leer, während das Log ab derselben ersten Anfrage weiterzählte:
> kubectl exec deploy/aks-config-api -- ls /tmp> python cfg.py get /logs/summary{… "total_requests":266, … "first_request":"2026-10-28T12:31:16.705746", …}
Warum: Jeder Container startet aus seinem Image plus einer frischen, leeren Schicht für alles, was er schreibt. Ein neuer Pod bekommt einen neuen Container, also startet er leer. Sogar ein Absturz wirkt so: Das Kubelet, der Kubernetes-Agent auf dem Node (der virtuellen Maschine, auf der Ihre Pods laufen), startet einen abgestürzten Container in einem sauberen Zustand neu. Wer Docker kennt, wird hier überrascht: docker restart behält die Dateien eines Containers, Kubernetes startet dagegen einen frischen Container.
Das Log hat überlebt, weil es nie in den eigenen Dateien des Containers lag. Es lag auf einem Volume: Speicher von außerhalb des Pods, unter einem Ordnerpfad eingebunden.
Der Bestellzettel: Claim, Volume, Disk
Die Übung fordert Speicher mit einem PersistentVolumeClaim (kurz Claim) an: einer Anfrage, die sagt, wie viel Speicher, wie er geteilt werden darf und welche Storage-Klasse ihn anlegt. Eine Storage-Klasse ist das Rezept, das den Speicher erzeugt. Gekürzt:
kind: PersistentVolumeClaimmetadata:name: api-logs-pvcspec:resources: {requests: {storage: 1Gi}}accessModes: [ReadWriteOnce]storageClassName: managed-csi
Das Deployment bindet den Claim über seinen Namen in den Ordner ein, in den die App ihr Log schreibt:
# the pod templatevolumes:- name: log-storagepersistentVolumeClaim: {claimName: api-logs-pvc}# the containervolumeMounts:- {name: log-storage, mountPath: /var/log/api}
Der Claim war Bound, gebunden an ein Volume mit generiertem Namen. Diesen Speicher gab es vorher nicht: managed-csi (die Standardklasse auf AKS) wartet auf den ersten Pod, der den Claim nutzt, und legt ihn dann an. Spalten mit „…“ gekürzt, der lange Volume-Name auf sein Ende verkürzt:
> kubectl get pvc api-logs-pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS …api-logs-pvc Bound pvc-…0001 1Gi RWO managed-csi …> kubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS …pvc-…0001 1Gi RWO Delete Bound …
Hinter dem Volume steht eine Azure Managed Disk mit dem Namen des Volumes, in der Node-Ressourcengruppe des Clusters, der Ressourcengruppe, die AKS für die eigenen Ressourcen des Clusters führt:
> az disk list -g MC_rg-demo_aks-demo_westeurope --query "[].{name:name, gb:diskSizeGB}" -o tableName Gb---------------------------------------- ----pvc-00000000-0000-0000-0000-000000000001 1
Der Claim ist also die Anfrage, das Volume der Speicher, den Kubernetes kennt, und die Disk das, was Azure speichert und abrechnet. Die Daten liegen auf dieser Disk, außerhalb jedes Pods.
ReadWriteOnce: einmal pro was?
Die meisten lesen ReadWriteOnce als einen Pod. Ich habe das Deployment auf zwei Replikate skaliert, während der Cluster nur einen Node hatte. Beide starteten, beide auf demselben Node, beide schrieben auf dieselbe Disk:
> kubectl get pods -l app=aks-config-api -o wideNAME READY STATUS … NODE …aks-config-api-876f7899f-csl9m 1/1 Running … node …vmss000000 …aks-config-api-876f7899f-tmq9k 1/1 Running … node …vmss000000 …
ReadWriteOnce bedeutet ein Node, nicht ein Pod. Eine Azure-Disk hängt an genau einer virtuellen Maschine, dem Node, und jeder Pod auf diesem Node kann sie nutzen. (Für genau einen Pod im ganzen Cluster hat Kubernetes einen eigenen Zugriffsmodus, ReadWriteOncePod.)
Danach habe ich einen zweiten Node und ein zweites Deployment hinzugefügt: zwei Pods, eine neue ReadWriteOnce-Disk und eine Pod-Anti-Affinity-Regel, die die beiden Pods auf verschiedene Nodes legt. Der erste Pod lief nach 15 Sekunden. Der zweite blieb die ganzen zweieinhalb Minuten, die ich zugesehen habe, in ContainerCreating. Das Warning-Event sagt, warum (Auszug):
> kubectl events --types=WarningFailedAttachVolume … Volume is already used by pod(s) disk-writer-…xqsgp
Die Disk hing am ersten Node, und der zweite Node kann sie nicht anhängen, solange der erste Pod sie nutzt. Kein Absturz, kein Fehler in der App, nur ein Pod, der nie startet. Die AKS-Troubleshooting-Dokumentation führt diese Meldung als Multi-Attach-Fehler. Auch ohne Anti-Affinity-Regel kann der Scheduler Replikate auf mehrere Nodes verteilen, ein Deployment mit Disk und mehreren Replikaten kann also ebenfalls darauf stoßen.
Viele Nodes, eine Freigabe
Für Pods auf mehreren Nodes nehmen Sie eine andere Storage-Klasse: azurefile-csi. Sie legt eine Azure-Files-Freigabe an, eine Dateifreigabe in einem Azure-Speicherkonto (AKS legt das Konto in der Node-Ressourcengruppe an), und jeder Node bindet sie über das Netzwerk ein. Ihr Zugriffsmodus ist ReadWriteMany. Im Claim ändern sich nur zwei Zeilen:
kind: PersistentVolumeClaimmetadata: {name: shared-files}spec:resources: {requests: {storage: 1Gi}}accessModes: [ReadWriteMany]storageClassName: azurefile-csi
Mit demselben Deployment über zwei Nodes warteten beide Pods etwa 25 Sekunden, während die Freigabe angelegt wurde, dann liefen sie, einer pro Node. Ein Pod schrieb eine Zeile in die Freigabe, und beide lasen sie:
> python cfg.py share files-writer0.8s files-writer-6bc8ddf6b-85gqk writes /data/note.txt1.3s files-writer-6bc8ddf6b-85gqk reads: written by the first pod1.8s files-writer-6bc8ddf6b-zcmcc reads: written by the first pod
Warum dann nicht immer eine Freigabe? Eine Datenbank erwartet ihre Dateien für sich allein, deshalb legt das Modul Datenbanken auf Disks. Azure Files ist für Dateien, die viele Pods teilen.
Azure Disk (managed-csi) | Azure Files (azurefile-csi) | |
|---|---|---|
| Zugriffsmodus | ReadWriteOnce | ReadWriteMany |
| Wer sie nutzen kann | die Pods auf jeweils einem Node | Pods auf vielen Nodes gleichzeitig |
| Typischer Einsatz | die Daten einer Datenbank | Dateien, die viele Pods teilen |
| Beim Löschen des Claims | die Disk wird gelöscht | die Freigabe wird gelöscht |
Die Spalte, die die Disk löscht
Die Spalte in der Volume-Liste hat es von Anfang an gesagt: Reclaim-Policy Delete. Sie legt fest, was mit dem Speicher passiert, wenn sein Claim gelöscht wird. Ich habe den Claim des Tests mit zwei Nodes zusammen mit seinem Deployment gelöscht (nur das Deployment zu löschen, hätte den Claim und seine Disk behalten) und die Disks eineinhalb Minuten später noch einmal aufgelistet:
> kubectl delete -f k8s/storage/disk-writer.yamlpersistentvolumeclaim "shared-disk" deleted from default namespacedeployment.apps "disk-writer" deleted from default namespace> az disk list -g MC_rg-demo_aks-demo_westeurope --query "[].{name:name, gb:diskSizeGB}" -o tableName Gb---------------------------------------- ----pvc-00000000-0000-0000-0000-000000000001 1
Eine Disk übrig, die der Übung. Die Disk des Tests war weg, samt ihren Daten. Das tun alle eingebauten Storage-Klassen. Um eine Disk zu behalten, legen Sie eine eigene Storage-Klasse mit reclaimPolicy: Retain an und nutzen sie im Claim (angepasst aus dem Beispiel der AKS-Dokumentation):
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: {name: managed-csi-retain}provisioner: disk.csi.azure.comparameters: {skuName: StandardSSD_LRS}reclaimPolicy: RetainvolumeBindingMode: WaitForFirstConsumer
Dann bleibt die Disk, wenn der Claim weg ist. Sie löschen sie selbst, und Azure rechnet sie ab, bis Sie das tun. AKS überschreibt Änderungen an seinen eingebauten Klassen, legen Sie also eine eigene Klasse an, statt managed-csi zu ändern.
Was passiert ist, als ich es ausgeführt habe
Getestet am 28. Oktober 2026 in meinem eigenen Azure-Abonnement (AKS 1.36 in westeurope, Azure CLI 2.88.0, kubectl 1.36); danach wurde alles gelöscht. Der Cluster startete mit einem Node Standard_D2s_v5; der zweite Node brauchte 161,9 Sekunden. Der ersetzte Pod war 22,0 Sekunden nach dem Löschen bereit; die erste Anfrage an die App direkt nach dem Rollout lief einmal in ein Timeout und wurde beim zweiten Versuch beantwortet. Der zweite disk-writer-Pod stand von Sekunde 4,0 bis zum Ende der 150 Sekunden Beobachtung in ContainerCreating. Den Disk-Claim samt Deployment zu löschen, dauerte 31,2 Sekunden (Kubernetes behält einen Claim, solange ein Pod ihn nutzt); solange die Disk noch am ersten Node hing, zeichnete Kubernetes Warnungen VolumeFailedDelete … is still attached to node auf, und 90 Sekunden später war die Disk weg. Für Azure Files gibt die Übung der Kubelet-Identität die Rolle Storage Account Contributor auf der Node-Ressourcengruppe; das habe ich genauso gemacht.
Was das Video weglässt
- emptyDir: ein Scratch-Volume, das einen Container-Absturz übersteht, aber gelöscht wird, wenn der Pod den Node verlässt.
- StatefulSets mit
volumeClaimTemplates, ein Claim pro Replikat, die übliche Antwort für Datenbanken mit mehreren Replikaten. - Azure Container Storage, Azure Blob Storage über den CSI-Treiber, Premium SSD v2 und zonenredundante Disks (ZRS), die AKS in Clustern über Verfügbarkeitszonen nutzt.
- Volume-Erweiterung, Snapshots und AKS-Backup.



