Home > Azure > Kubernetes

Stateful Workloads on Azure AKS: Persistent Volumes with Azure Disks & Azure Files

@admin 5 min read

By default, Kubernetes containers run on an ephemeral writable layer—meaning if a pod crashes, restarts, or is rescheduled to another node by the cluster scheduler, all runtime data, database transactions, and generated log files are permanently lost. In cloud-native production environments, stateful workloads (such as PostgreSQL, MySQL, Redis, MongoDB, or content management systems like WordPress) require Persistent Storage that survives pod lifecycles and cluster upgrades.

In this hands-on guide, you will master enterprise Kubernetes stateful storage on Azure Kubernetes Service (AKS):

  • Azure Managed Disks (Block Storage): High-performance, low-latency NVMe/SSD storage attached directly to a single node.
  • Azure Files (Shared Storage): Distributed SMB/NFS file shares mounted concurrently across multiple cluster nodes.
  • StorageClasses & Dynamic Provisioning: How the AKS CSI (Container Storage Interface) driver automatically provisions physical Azure cloud storage on demand.
  • PersistentVolumeClaims (PVCs): Decoupling storage requests from cloud infrastructure implementation.
  • Data Persistence Verification: Writing data, forcefully deleting a pod, and proving that a fresh replacement pod instantly recovers 100% of the data.

📌 Lab Prerequisites: To follow along, you need an active Azure Subscription (Free Trial, Student Credits, or Pay-As-You-Go) and a running AKS cluster with Azure CLI and kubectl connected locally. If you need to spin up a cluster first, refer to our foundational guide: Deploy Your First Containerized App on Azure Kubernetes Service (AKS).

📦 GitHub Lab Repository & Manifest Files

To clone, star, or download all ready-to-use Kubernetes storage manifests used in this guide, visit our official repository:
👉 StackLayerOfficially/azure-aks-hands-on-labs (04-persistent-storage-disks-files)

⚠️ Disclaimer & Cloud Cost Notice

This tutorial is built strictly for hands-on educational and lab purposes. The configurations and manifests shown here are simplified demonstrations and should not be used directly in live production environments without independent security reviews. Stack Layer and its technical team assume no responsibility or liability for any issues, outages, or data loss resulting from applying these commands. Provisioning Azure Managed Disks and Azure Files incurs standard Azure storage consumption costs (billed per GB/month). Always follow Step-05 to delete your PVCs and pause or tear down cluster resources when finished.

Understanding the Core Concepts

1. Ephemeral vs. Persistent Storage

A standard container filesystem exists only for the lifetime of that container. When the container stops, the writable layer is discarded. Kubernetes solves this by decoupling storage into a two-tier abstraction: Developers define their storage requirements (size and access mode) using a PersistentVolumeClaim (PVC), while the AKS Control Plane automatically provisions the underlying physical PersistentVolume (PV) in Azure storage behind the scenes.

2. StorageClasses & Dynamic Provisioning

A StorageClass defines the "profile" or tier of storage to provision (such as Standard SSD, Premium SSD, or Azure File Share). Instead of a cloud administrator manually going into the Azure Portal to pre-create virtual hard disks (VHDs), AKS uses Dynamic Provisioning: the CSI driver listens for incoming PVC requests and creates the exact Azure storage resource automatically on the fly.

3. Access Modes: ReadWriteOnce (RWO) vs. ReadWriteMany (RWX)

Understanding access modes is essential to choosing the right cloud storage technology:

Feature Azure Managed Disk (CSI) Azure Files (CSI)
Storage Class managed-csi (default) azurefile-csi
Storage Protocol High-speed Block Storage (NVMe/SSD) Shared File System (SMB / NFS)
Supported Access Modes ReadWriteOnce (RWO) only ReadWriteMany (RWX) & RWO
Best Use Case Single-instance Databases (PostgreSQL, MySQL, Redis) Multiple pods sharing uploads, media, logs, CMS
Multi-Node Sharing ❌ No (Attached to one Node at a time) ✅ Yes (Mounted concurrently across multiple nodes)

Connect Your Local CLI to AKS

Authenticate your local terminal to your running AKS cluster:

[code] # Connect Azure CLI to your running AKS cluster az aks get-credentials --resource-group stacklayer-aks-rg --name stacklayer-aks-cluster --overwrite-existing [/code]

Command Breakdown:

  • az aks get-credentials: Pulls cluster access credentials and certificates from Azure into your local ~/.kube/config.
  • --resource-group stacklayer-aks-rg: Target Azure Resource Group holding the cluster.
  • --name stacklayer-aks-cluster: Identifies your target AKS cluster name.
  • --overwrite-existing: Overwrites any expired or stale local kubeconfig contexts.

Step-01: Inspect Default AKS Storage Classes

AKS includes pre-configured StorageClasses that instruct Kubernetes which Azure storage tier to deploy when a claim is submitted. Let's inspect them:

[code] # List all default storage classes available on the AKS cluster kubectl get storageclass [/code]

Command Breakdown:

  • kubectl get storageclass (or kubectl get sc): Queries the cluster control plane to list all storage provisioner profiles installed by the AKS CSI driver.

You will see several default storage classes in the terminal output:

  • managed-csi (Default): Azure Managed Standard SSD Disk (LRS).
  • managed-csi-premium: High-performance Azure Premium SSD for low-latency database engines.
  • azurefile-csi: Standard Azure File Share (SMB).
  • azurefile-csi-premium: High throughput, low latency SSD Azure File Share.

Step-02: Create an Azure Managed Disk PersistentVolumeClaim (RWO)

Now, let's request a dedicated 5 Gigabyte Azure Managed Disk using a PersistentVolumeClaim manifest.

Create a file named 01-azure-disk-pvc.yaml:

[code] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: stacklayer-disk-pvc spec: accessModes: - ReadWriteOnce storageClassName: managed-csi resources: requests: storage: 5Gi [/code]

Apply the claim manifest to your cluster and verify its creation:

[code] # Submit the PersistentVolumeClaim to AKS kubectl apply -f 01-azure-disk-pvc.yaml # Check the status of the PVC kubectl get pvc stacklayer-disk-pvc [/code]

Command Breakdown:

  • kubectl apply -f 01-azure-disk-pvc.yaml: Submits the PVC definition to the Kubernetes API server.
  • kubectl get pvc stacklayer-disk-pvc: Checks the allocation status. Because managed-csi uses WaitForFirstConsumer, the status will show Pending until a pod actually requests to mount it, at which point it dynamically binds to a newly created disk.

Step-03: Attach the Volume to a Workload & Test Data Persistence

Let's attach this 5GB Azure Disk to an active deployment. The pod will mount the disk to the directory /mnt/azuredisk.

Create a file named 02-azure-disk-deployment.yaml:

[code] apiVersion: apps/v1 kind: Deployment metadata: name: stacklayer-disk-app spec: replicas: 1 selector: matchLabels: app: stacklayer-disk-app template: metadata: labels: app: stacklayer-disk-app spec: containers: - name: storage-tester image: stacklayer/kubernetes:nginx-v1 ports: - containerPort: 80 volumeMounts: - name: azure-disk-storage mountPath: /mnt/azuredisk volumes: - name: azure-disk-storage persistentVolumeClaim: claimName: stacklayer-disk-pvc [/code]

Deploy the workload and monitor until it reaches the Running state:

[code] # Deploy the stateful workload mounting our Azure Managed Disk kubectl apply -f 02-azure-disk-deployment.yaml # Watch the pod status transition from ContainerCreating to Running kubectl get pods -l app=stacklayer-disk-app -w [/code]

Command Breakdown:

  • kubectl apply -f 02-azure-disk-deployment.yaml: Instructs the AKS scheduler to bind the PVC, attach the Azure Managed Disk to the underlying VM node, and launch the container.
  • kubectl get pods -l app=stacklayer-disk-app -w: Streams live status changes as Azure finishes attaching the physical disk. Press Ctrl + C once active.

Verifying Dynamic Provisioning in Azure

While the pod is running, execute the following command to see the physical Persistent Volume created dynamically by AKS:

[code] # List cluster-wide Persistent Volumes provisioned by Azure CSI kubectl get pv [/code]

Command Breakdown:

  • kubectl get pv: Lists the cluster-wide storage volumes. Notice the auto-generated name (e.g., pvc-1a2b3c4d...) with 5Gi capacity and Bound status. AKS automatically created a real Azure Managed Disk in your cluster's node resource group (MC_stacklayer-aks-rg_stacklayer-aks-cluster_region)!

The Persistence Proof: Writing & Recovering Data

Let's write a file inside the mounted volume, forcefully delete the pod, let Kubernetes spin up a replacement, and prove our data remained completely intact.

[code] # Dynamically grab the running pod name DISK_POD=$(kubectl get pods -l app=stacklayer-disk-app -o jsonpath="{.items[0].metadata.name}") # Write a verification file directly into the mounted Azure Disk kubectl exec $DISK_POD -- sh -c "echo 'Stack Layer Persistence Test: Data Saved at $(date)' > /mnt/azuredisk/status.txt" # Verify file content inside the mounted directory kubectl exec $DISK_POD -- cat /mnt/azuredisk/status.txt [/code]

Now, let's delete the pod simulating an unexpected container crash or node failure:

[code] # Forcefully delete the running pod kubectl delete pod $DISK_POD # Wait a few seconds for the Deployment controller to create a fresh pod kubectl get pods -l app=stacklayer-disk-app [/code]

Query the brand new pod and verify that the data survived:

[code] # Grab the name of the new replacement pod NEW_DISK_POD=$(kubectl get pods -l app=stacklayer-disk-app -o jsonpath="{.items[0].metadata.name}") # Read back the saved file from the new pod kubectl exec $NEW_DISK_POD -- cat /mnt/azuredisk/status.txt [/code]

Result: Your file and timestamp are 100% preserved! The new container attached to the existing Azure Disk and immediately accessed the persistent data.

Step-04: Azure Files for Shared Multi-Pod Storage (RWX)

Azure Disks can only be attached to a single cluster node at a time (ReadWriteOnce). If you run an application (like WordPress or distributed file processing) with multiple replicas spread across different worker nodes that all need to read and write to the same shared directory, you must use Azure Files with ReadWriteMany.

1. Create the Azure Files PersistentVolumeClaim

Create a manifest named 03-azure-files-pvc.yaml requesting a 5GB shared SMB file share:

[code] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: stacklayer-files-pvc spec: accessModes: - ReadWriteMany storageClassName: azurefile-csi resources: requests: storage: 5Gi [/code]

Deploy the claim to your cluster:

[code] # Deploy the Azure Files PersistentVolumeClaim kubectl apply -f 03-azure-files-pvc.yaml # Check the status of the Azure Files PVC kubectl get pvc stacklayer-files-pvc [/code]

Command Breakdown:

  • kubectl apply -f 03-azure-files-pvc.yaml: Submits the shared storage claim to the AKS API server.
  • storageClassName: azurefile-csi: Triggers the AKS CSI driver to automatically provision a managed Azure Storage Account and an SMB File Share.
  • accessModes: ReadWriteMany: Enables multiple pods running across different cluster nodes to concurrently mount and write to this shared filesystem.

2. Deploy a Multi-Pod Workload Mounting the Shared File Share

Let's create a deployment with 3 replicas that all mount stacklayer-files-pvc to /mnt/azurefiles.

Create a file named 04-azure-files-deployment.yaml:

[code] apiVersion: apps/v1 kind: Deployment metadata: name: stacklayer-files-app spec: replicas: 3 selector: matchLabels: app: stacklayer-files-app template: metadata: labels: app: stacklayer-files-app spec: containers: - name: shared-worker image: stacklayer/kubernetes:nginx-v1 ports: - containerPort: 80 volumeMounts: - name: shared-azurefile mountPath: /mnt/azurefiles volumes: - name: shared-azurefile persistentVolumeClaim: claimName: stacklayer-files-pvc [/code]

Deploy the workload and confirm that all 3 replicas are active:

[code] # Deploy the 3-replica shared storage application kubectl apply -f 04-azure-files-deployment.yaml # Confirm all 3 pod replicas are Running across cluster nodes kubectl get pods -l app=stacklayer-files-app -o wide [/code]

3. Test Multi-Pod Shared Concurrency

Let's write a file from Pod 1, and immediately read it back from Pod 2 and Pod 3:

[code] # Grab the names of the running pods POD_1=$(kubectl get pods -l app=stacklayer-files-app -o jsonpath="{.items[0].metadata.name}") POD_2=$(kubectl get pods -l app=stacklayer-files-app -o jsonpath="{.items[1].metadata.name}") POD_3=$(kubectl get pods -l app=stacklayer-files-app -o jsonpath="{.items[2].metadata.name}") # Write a shared file from Pod 1 kubectl exec $POD_1 -- sh -c "echo 'Shared message written by $POD_1 at $(date)' > /mnt/azurefiles/shared.txt" # Read the exact same file from Pod 2 and Pod 3 kubectl exec $POD_2 -- cat /mnt/azurefiles/shared.txt kubectl exec $POD_3 -- cat /mnt/azurefiles/shared.txt [/code]

Both pods immediately read the shared file across node boundaries, proving concurrent read/write access.

4. Verify the Provisioned File Share in the Azure Portal GUI

To inspect the underlying cloud storage created by AKS:

  1. Sign in to the Azure Portal.
  2. Open your cluster's node resource group (starts with MC_stacklayer-aks-rg_stacklayer-aks-cluster_<region>).
  3. Locate the auto-generated Storage account.
  4. Under Data storage in the left sidebar, click File shares.
  5. Click into your auto-provisioned file share (e.g., pvc-xxxx...). You will see shared.txt sitting inside Azure cloud storage!

Step-05: Clean Up Storage Resources & Prevent Cloud Charges

Storage volumes persist in Azure even when application pods are terminated. To prevent ongoing storage billing after your lab session, delete the workloads and their associated PVCs:

[code] # 1. Delete workloads kubectl delete -f 02-azure-disk-deployment.yaml kubectl delete -f 04-azure-files-deployment.yaml # 2. Delete Persistent Volume Claims to trigger automated cloud storage deletion kubectl delete pvc stacklayer-disk-pvc stacklayer-files-pvc # 3. Confirm all PVs are released and deleted from Azure kubectl get pv [/code]

Command Breakdown:

  • kubectl delete -f ...: Gracefully terminates and unmounts all container workloads from both the Azure Managed Disk and Azure Files share.
  • kubectl delete pvc stacklayer-disk-pvc stacklayer-files-pvc: Because default AKS StorageClasses use the Delete reclaim policy, deleting the PVCs instructs the Azure CSI driver to immediately delete the underlying physical Azure Managed Disk and SMB File Share.
  • kubectl get pv: Confirms that zero orphaned storage volumes remain active in Azure.

Option A: Pause Cluster Nodes (Recommended If Practicing Later Labs)

If you plan to follow additional hands-on guides, stop your cluster nodes to halt VM compute charges while preserving your cluster configuration:

[code] # Stop the AKS cluster nodes to halt compute billing az aks stop --resource-group stacklayer-aks-rg --name stacklayer-aks-cluster [/code]

Command Breakdown:

  • az aks stop: Stops the underlying Azure VM worker nodes, deallocating compute resources so you are not billed for hourly VM run time. All cluster settings, network topologies, and configurations are preserved.
  • az aks start: Resumes cluster operations and wakes up worker nodes in approximately 2–3 minutes.

Option B: Complete Deletion (Zero Cost)

If you are finished experimenting and want to wipe out all cloud resources:

[code] # Delete the entire Resource Group and all associated cluster resources az group delete --name stacklayer-aks-rg --yes --no-wait [/code]

Command Breakdown:

  • az group delete: Permanently deletes the resource group, cluster, networking, disks, and associated load balancers.
  • --name stacklayer-aks-rg: Target resource group name.
  • --yes: Bypasses interactive confirmation prompts.
  • --no-wait: Executes asynchronously in the background so your terminal returns immediately.

Summary

In this guide, you mastered the core architecture of Kubernetes stateful storage on Microsoft Azure:

  • How StorageClasses abstract cloud-specific disk implementations from Kubernetes manifests.
  • How PersistentVolumeClaims (PVCs) allow developers to request storage dynamically on demand.
  • How the AKS CSI driver dynamically provisions physical Azure Managed Disks and Azure Files.
  • How ReadWriteOnce (RWO) binds dedicated block storage to a single node, while ReadWriteMany (RWX) shares file storage across multiple nodes.
  • Proved data durability by deleting an active pod and verifying that a brand new replacement pod mounted the exact same data without loss.
  • Safeguarded your cloud balance using az aks stop and az group delete.

“Did you successfully practice Persistent Storage with Azure Disks and Azure Files on your AKS cluster?”

If yes, leave a comment below! If you ran into any issues or have questions, feel free to drop them below as well.

Share:

Enjoying our guides? Support Stack Layer!

If you find our tutorials helpful, consider buying us a coffee!

Buy me a coffee

Discussions

Show Ads here — Add AdSense or HTML widget in Layout tab
Show Ads here — Add AdSense or HTML widget in Layout tab