I wrote a note inside my pod on Azure Kubernetes Service (AKS), next to the log file the app keeps. Then I deleted the pod. Kubernetes started a new one in 22 seconds. One of the two files survived. The other was gone.
This explainer shows where a pod's data really lives, why ReadWriteOnce means one node and not one pod, and when the Azure disk behind a claim is deleted for good. Everything below comes from the Microsoft Learn exercise and a few extra steps, run on my own cluster.
Key points
- A container's own files start clean. Every container starts from its image plus a fresh, empty writable layer. A replaced pod gets a new container, and even after a crash the kubelet restarts the container with a clean state. Anything you need later goes on a volume.
- A claim is a request, a volume is the storage, the disk is what Azure bills. A PersistentVolumeClaim asks for size, access mode and a storage class; the class creates a PersistentVolume, and on AKS the default class
managed-csicreates an Azure managed disk in the node resource group. - ReadWriteOnce means one node, not one pod. Two pods on the same node shared one disk. A pod on a second node waited in
ContainerCreatingbecause the disk was attached to the first node. For many nodes, use Azure Files (azurefile-csi, ReadWriteMany). - Deleting the claim deletes the disk. The built-in storage classes have reclaim policy
Delete. A storage class withreclaimPolicy: Retainkeeps the disk after the claim is gone, and you delete it yourself.
Gone with the container
In the exercise, a Deployment runs a small API. A Deployment keeps a set number of pods running from one template, and a pod is the unit that wraps your container. The app writes one line to a log file for every request it gets:
> python cfg.py get /logs/summaryGET /logs/summary -> 200{… "total_requests":264, … "first_request":"2026-10-28T12:31:16.705746", …}
Then I wrote a note into the container's /tmp folder, deleted the pod and waited for the Deployment to replace it:
> 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
In the new pod, /tmp was empty, while the log kept counting from the same first request:
> 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", …}
Why: every container starts from its image, plus a fresh, empty layer for whatever it writes. A new pod gets a new container, so it starts clean. Even a crash does that: the kubelet, the Kubernetes agent on the node (the virtual machine your pods run on), restarts a crashed container with a clean state. If you know Docker, that's the surprise: docker restart keeps a container's files; Kubernetes starts a fresh container instead.
The log survived because it was never in the container's own files. It was on a volume: storage from outside the pod, mounted at a folder path.
The request slip: claim, volume, disk
The exercise asks for storage with a PersistentVolumeClaim (a claim): a request that says how much storage, how it may be shared, and which storage class makes it. A storage class is the recipe that creates the storage. Rewritten short:
kind: PersistentVolumeClaimmetadata:name: api-logs-pvcspec:resources: {requests: {storage: 1Gi}}accessModes: [ReadWriteOnce]storageClassName: managed-csi
The Deployment mounts the claim by its name, at the folder where the app writes its log:
# the pod templatevolumes:- name: log-storagepersistentVolumeClaim: {claimName: api-logs-pvc}# the containervolumeMounts:- {name: log-storage, mountPath: /var/log/api}
The claim was Bound to a volume with a generated name. That storage didn't exist before: managed-csi (the default class on AKS) waits for the first pod that uses the claim, then creates it. Columns cut with "…", the long volume name shortened to its end:
> 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 …
Behind the volume is an Azure managed disk with the volume's name, in the cluster's node resource group, the resource group AKS keeps for the cluster's own resources:
> 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
So the claim is the request, the volume is the storage Kubernetes knows, and the disk is what Azure stores and bills. The data sits on that disk, outside every pod.
ReadWriteOnce: once per what?
Most people read ReadWriteOnce as one pod. I scaled the Deployment to two replicas while the cluster had a single node. Both started, both on the same node, both writing to the same 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 means one node, not one pod. An Azure disk attaches to one virtual machine, the node, and every pod on that node can use it. (Kubernetes has a separate access mode, ReadWriteOncePod, for a single pod across the cluster.)
Then I added a second node and a second Deployment: two pods, one new ReadWriteOnce disk, and a pod anti-affinity rule that puts the two pods on different nodes. The first pod was Running after 15 seconds. The second stayed in ContainerCreating for the whole two and a half minutes I watched. The warning event says why (excerpt):
> kubectl events --types=WarningFailedAttachVolume … Volume is already used by pod(s) disk-writer-…xqsgp
The disk was attached to the first node, and the second node can't attach it while the first pod uses it. No crash, no error in the app, just a pod that never starts. The AKS troubleshooting docs list this message as the Multi-Attach error. Without an anti-affinity rule the scheduler may still spread replicas across nodes, so a disk-backed Deployment with several replicas can hit it too.
Many nodes, one share
For pods on several nodes, use a different storage class: azurefile-csi. It creates an Azure Files share, a file share in an Azure storage account (AKS creates the account in the node resource group), and every node mounts it over the network. Its access mode is ReadWriteMany. In the claim, only two lines change:
kind: PersistentVolumeClaimmetadata: {name: shared-files}spec:resources: {requests: {storage: 1Gi}}accessModes: [ReadWriteMany]storageClassName: azurefile-csi
With the same two-node Deployment, both pods waited about 25 seconds while the share was created, then ran, one on each node. One pod wrote a line into the share, and both read it:
> 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
Why not always use a share? A database expects its files to itself, so the module puts databases on disks. Azure Files is for files that many pods share.
Azure Disk (managed-csi) | Azure Files (azurefile-csi) | |
|---|---|---|
| Access mode | ReadWriteOnce | ReadWriteMany |
| Who can use it | the pods on one node at a time | pods on many nodes at once |
| Typical use | a database's data | files many pods share |
| On delete of the claim | the disk is deleted | the file share is deleted |
The column that deletes the disk
The column in the volume list said it from the start: reclaim policy Delete. It decides what happens to the storage when its claim is deleted. I deleted the claim of the two-node test together with its Deployment (deleting only the Deployment would have kept the claim and its disk), and listed the disks again a minute and a half later:
> 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
One disk left, the exercise's. The test's disk was gone, and the data on it. The built-in storage classes all do that. To keep a disk, make your own storage class with reclaimPolicy: Retain and use it in the claim (adapted from the AKS docs example):
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: {name: managed-csi-retain}provisioner: disk.csi.azure.comparameters: {skuName: StandardSSD_LRS}reclaimPolicy: RetainvolumeBindingMode: WaitForFirstConsumer
Then the disk stays after the claim is gone. You delete it yourself, and Azure bills it until you do. AKS overwrites changes to its built-in classes, so add your own class instead of editing managed-csi.
What happened when I ran it
Tested on October 28, 2026 in my own Azure subscription (AKS 1.36 in westeurope, Azure CLI 2.88.0, kubectl 1.36); everything was deleted afterwards. The cluster started with one Standard_D2s_v5 node; adding the second node took 161.9 seconds. The replaced pod was ready 22.0 seconds after the delete; the first request to the app right after the rollout timed out once and answered on the retry. The second disk-writer pod sat in ContainerCreating from 4.0 seconds until the end of the 150-second watch. Deleting the disk claim with its Deployment took 31.2 seconds (Kubernetes keeps a claim while a pod still uses it); while the disk was still attached to the first node, Kubernetes recorded VolumeFailedDelete … is still attached to node warnings, and 90 seconds later the disk was gone. For Azure Files, the exercise gives the kubelet identity Storage Account Contributor on the node resource group; I did the same.
What the video leaves out
- emptyDir: a scratch volume that survives a container crash but is deleted when the pod leaves the node.
- StatefulSets with
volumeClaimTemplates, one claim per replica, the usual answer for databases with several replicas. - Azure Container Storage, Azure Blob storage through the CSI driver, Premium SSD v2 and zone-redundant (ZRS) disks, which AKS uses in clusters across availability zones.
- Volume expansion, snapshots and AKS backup.



