Pods are disposable. Some of what they touch is not.
Delete a pod and its container filesystem goes with it — every log line, every temp file, every write, gone the instant the container stops. That is by design: pods are meant to be replaced, not nursed. But a database, a queue, anything stateful needs the opposite guarantee, and Kubernetes gives it one through a separate object: the PersistentVolumeClaim. A PVC is a request for storage that outlives whatever pod currently has it mounted — on AKS, backed by an Azure Disk, provisioned through the built-in managed-csi storage class. This lab proves the claim directly: write, delete the pod, read it back from the replacement. Then it proves the fine print — an Azure Disk PVC is ReadWriteOnce, mountable read-write by pods on exactly one node at a time, which is why it is the wrong choice the moment you need two pods sharing one volume.
The pod is disposable. The claim is not.
You need a free Azure account and the Azure CLI (az), signed in with az login. First time? The 15-minute Set up your machine page covers the account, the installs, and sign-in. Prefer zero installs? Run everything in Azure Cloud Shell (Bash) — az and kubectl are preinstalled and already signed in.
This lab assumes the cluster from Your first AKS cluster. If you already tore that one down, Step 1 recreates it with the same command. The smallest Azure Disk this claim can request still bills by the gigabyte-month even after the pod is deleted, until the PVC itself is deleted — the teardown step covers both.
The cluster
If you still have the cluster from Your first AKS cluster, skip to get-credentials. Otherwise recreate it with the identical command.
# Windows/Git Bash: stop it mangling /subscriptions/... arguments (harmless on macOS/Linux) export MSYS_NO_PATHCONV=1 RG="campux-lab-aks-rg" az group create -n "$RG" -l eastus # same command as the first-cluster lab: free control plane, one small node az aks create -n campux-aks -g "$RG" \ --tier free \ --node-count 1 \ --node-vm-size Standard_B2s \ --enable-managed-identity \ --generate-ssh-keys az aks get-credentials -n campux-aks -g "$RG" kubectl get nodes kubectl get storageclass
kubectl get storageclass lists managed-csi (marked default) among the built-in classes — AKS installs the Azure Disk CSI driver and this class by default, so nothing extra needs installing before Step 2.A PersistentVolumeClaim, backed by an Azure Disk
Request the smallest disk the class allows and mount it into a pod at /data.
kubectl create namespace storage-demo
cat > pvc.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-pvc
namespace: storage-demo
spec:
accessModes:
- ReadWriteOnce
storageClassName: managed-csi
resources:
requests:
storage: 1Gi
EOF
kubectl apply -f pvc.yaml
kubectl get pvc demo-pvc -n storage-demo -w # Ctrl-C once STATUS is Bound
Pending until a pod actually mounts it, because CSI binds the disk on first use, not on claim creation. Move to the next command and watch it bind.Write a file, then delete the pod
Run a pod that mounts the claim, write a file to it, confirm the claim is now Bound, then delete the pod outright.
cat > pod-v1.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: writer
namespace: storage-demo
spec:
containers:
- name: app
image: busybox:1.28
command: ["sh", "-c", "echo 'the disk remembers this' > /data/note.txt && sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: demo-pvc
EOF
kubectl apply -f pod-v1.yaml
kubectl wait --for=condition=Ready pod/writer -n storage-demo --timeout=120s
kubectl get pvc demo-pvc -n storage-demo
kubectl exec writer -n storage-demo -- cat /data/note.txt
demo-pvc now reads STATUS: Bound, and cat prints the disk remembers this. Now delete the pod and confirm it is really gone before moving on.kubectl delete pod writer -n storage-demo
kubectl get pod writer -n storage-demo # -> Error from server (NotFound)
The replacement reads it back
Start a new pod — different name, does not write anything — that mounts the same claim and reads the file the deleted pod left behind.
cat > pod-v2.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: reader
namespace: storage-demo
spec:
containers:
- name: app
image: busybox:1.28
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: demo-pvc
EOF
kubectl apply -f pod-v2.yaml
kubectl wait --for=condition=Ready pod/reader -n storage-demo --timeout=120s
kubectl exec reader -n storage-demo -- cat /data/note.txt
cat prints the disk remembers this — a pod that never wrote the file reads back exactly what a now-deleted pod wrote. The PVC, and the Azure Disk behind it, never went away; only the pod referencing it did.Deleting a pod removes the pod object and, with it, the ephemeral container filesystem layered on top of its image — but the volume the pod mounted was never part of the pod. A PersistentVolumeClaim is a separate Kubernetes object bound to a PersistentVolume, which the Azure Disk CSI driver backs with a real managed disk in your subscription. The pod spec just points at the claim by name. Delete the pod, the pointer disappears; the disk, and the claim pointing at it, stay exactly where they were. Any future pod that references claimName: demo-pvc gets the same disk, same bytes.
ReadWriteOnce means one node, not one pod
With reader still running and holding the claim, try to schedule a second pod that mounts the same PVC.
cat > pod-v3.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: second-reader
namespace: storage-demo
spec:
containers:
- name: app
image: busybox:1.28
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: demo-pvc
EOF
kubectl apply -f pod-v3.yaml
kubectl get pod second-reader -n storage-demo -w # Ctrl-C once it stops changing
second-reader is scheduled onto the same node as reader — there is no other node to place it on — and Kubernetes lets a ReadWriteOnce volume be mounted by multiple pods as long as they share a node, so it may reach Running. Confirm what ReadWriteOnce actually means with the events, not the pod status: kubectl describe pvc demo-pvc -n storage-demo and kubectl get events -n storage-demo --sort-by=.lastTimestamp show the volume is attached to one node only. On a multi-node cluster, scheduling second-reader onto a different node produces a FailedAttachVolume/FailedMount event, because an Azure Disk can be attached to exactly one node at a time — that is the constraint ReadWriteOnce is naming.ReadWriteOnce is a node-level constraint, not a pod-level one — the access mode governs which nodes may mount the volume, and Azure Disk allows exactly one. Multiple pods on that same node can share it; a pod scheduled to a second node cannot attach it at all, and sits in ContainerCreating until it times out or is rescheduled onto the node that already holds it. The fix for genuinely shared, multi-node storage is a different access mode and a different backing store — Azure Files over ReadWriteMany, not Azure Disk — and it is a real design decision, not a bug to route around.
kubectl delete pod reader second-reader -n storage-demo
Tear it down
Delete the PVC before the namespace — otherwise wait for it to finish reclaiming, since the disk itself keeps billing until it does. Then delete the cluster.
kubectl delete pvc demo-pvc -n storage-demo
kubectl delete namespace storage-demo
az group delete -n campux-lab-aks-rg --yes --no-wait
az group exists -n campux-lab-aks-rg # -> false once complete
demo-pvc is gone (search "Disks" in your resource group before it disappears with the group), and that both the cluster's resource group and its auto-managed node group (named like MC_campux-lab-aks-rg_campux-aks_eastus) are gone.What you can now honestly claim
You created a PersistentVolumeClaim backed by a real Azure Disk, wrote a file from one pod, deleted that pod, and read the identical file back from a pod that never wrote it — proof that a PVC's lifetime is independent of any pod that mounts it. You then scheduled a second pod against the same claim and read the exact meaning of ReadWriteOnce off the events, not off a guess: one node, not one pod, and a real limit that decides whether Azure Disk or Azure Files is the right call for a given workload.