Run one step, then check it
Read the step before copying. Bash commands belong in Git Bash on Windows or Azure Cloud Shell set to Bash; PowerShell uses different syntax. A JSON, YAML, Bicep or application-code example belongs in the named file, not directly at the terminal prompt. Follow any shell-specific instructions below.
Keep the same terminal open so earlier variables remain available. Copy one complete command block without the terminal's $ prompt. A backslash continues a command onto the next line. If Bash shows a lone >, it is waiting for the rest of a quote, command or file block. Finish the complete block; if the paste was incomplete, press Ctrl+C and copy it again. Do not paste the next step into an unfinished command.
A line such as RG="training-group" stores a value without printing it. Output captured with $(...) is also stored; a blank screen alone does not prove failure. A sleep deliberately waits, and -o none suppresses normal output. Wait for your normal prompt, then run the step's checkpoint. After an error, stop and keep the exact command and message; later steps cannot repair a missing resource. Never share passwords, access tokens or secret values in a screenshot.
Pods are disposable. Some of what they touch is not.
Delete a pod and its container's writable layer goes with it. An emptyDir survives a container restart inside the same pod but disappears when the pod is removed. To keep this lab's file across pod replacement, create a separate PersistentVolumeClaim (PVC) backed by an Azure Disk through the managed-csi StorageClass. You will write a file, delete the pod, and read it from a new one. Then two pods on this single node will mount the same claim: ReadWriteOnce limits read-write access to one node, not one pod.
Replace the pod. Keep the claim.
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 uses the cluster from Your first AKS cluster if it still exists; otherwise Step 1 recreates it. The AKS control plane is on the Free tier, but the node, disk, and other Azure resources can incur charges. Disk billing depends on the provisioned tier and size, not simply the requested 1Gi. Deleting a pod does not delete the claim. The teardown removes the PVC and resource group; verify both the cluster group and its managed node group are gone.
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 # if Azure says this size "is not allowed in your subscription", the same error lists the sizes # that are; pick a 2-vCPU one from that list (Standard_D2as_v4 worked for us in October 2026) az aks create -n campux-aks -g "$RG" \ --tier free \ --node-count 1 \ --node-vm-size Standard_D2as_v4 \ --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 among the built-in classes. The default marker may be on another class; this lab names managed-csi explicitly. If the cluster was not created by this lab, verify the selected context with kubectl config current-context before creating anything.A PersistentVolumeClaim, backed by an Azure Disk
Request 1Gi through the built-in disk class, then mount the claim into a pod at /data. Azure may provision and bill a larger disk tier than the requested capacity.
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 # STATUS: Pending
Pending until a pod uses it if the StorageClass has WaitForFirstConsumer binding. Check with kubectl get storageclass managed-csi -o yaml; a different binding mode may make it Bound sooner. Step 3 verifies the final binding.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.
This block includes a deliberate wait. Leave this terminal open until the normal prompt returns; a wait is not proof that access has propagated.
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.
This block includes a deliberate wait. Leave this terminal open until the normal prompt returns; a wait is not proof that access has propagated.
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 also removes its container's writable layer, but it leaves this separate PVC in place. The claim remains bound to a PV backed by an Azure managed disk. A new pod that references claimName: demo-pvc can mount that disk and read the same bytes, provided the claim and backing resource still exist. Deleting the PVC is different; its reclaim policy governs what happens to the disk.
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.
This block includes a deliberate wait. Leave this terminal open until the normal prompt returns; a wait is not proof that access has propagated.
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
kubectl get pods reader second-reader -n storage-demo -o wide. On this one-node cluster both pods should show the same NODE, and second-reader can reach Running. Check the shared file with kubectl exec second-reader -n storage-demo -- cat /data/note.txt. That observable result demonstrates the easy-to-miss point: RWO is a one-node access mode, not a one-pod lock. This one-node lab cannot demonstrate a cross-node attach failure; it would require another node and a controlled placement test.Two pods on the same node can mount this RWO disk. Do not let both write to the same data files unless the application is designed for it. If a workload needs a shared filesystem across nodes, evaluate Azure Files with RWX. If it needs exactly one pod to mount a volume, RWO alone does not enforce that; evaluate ReadWriteOncePod where the driver supports it, plus the workload's own safety controls.
kubectl delete pod reader second-reader -n storage-demo
Tear it down
Delete the pods first, then the PVC and namespace. The built-in class normally uses a Delete reclaim policy, but confirm the disk is gone. The final group deletion also removes the cluster used by the first-cluster lab; skip it if you deliberately want to retain that cluster and accept its ongoing charges.
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 PVC backed by Azure Disk, wrote a file, deleted its pod, and read the same file from a new pod. You also mounted the claim from two pods on the same node, showing that RWO is a node limit rather than a one-pod lock. This single-node exercise does not test what happens when a second node requests the disk.