Skip to main content

Module 6: AKS Storage - Azure Disks

Introduction

Azure Disks are high-performance, highly durable storage for business-critical workloads.

  • Cost effective
  • Resilient
  • Scalable
  • Secure (encryption using CMK or MMK)

Terms covered in this module:

  • Storage Class
  • Persistent Volume Claim
  • Persistent Volume
  • Deployment
  • Config Map
  • Environment Variables
  • Volumes
  • Volume Mounts
  • ClusterIP Service

This module uses Azure Disks with a MySQL deployment separate from the WebApp deployment.


Create Storage Class Kubernetes Manifest

kubectl get sc gets you the storage classes on your cluster.

  1. Create 5 files:

    • storageclass.yml
    • persistentvolumeclaim.yml
    • configmap.yml
    • mysql-deployment.yml
    • mysql-clusterip.yml
  2. Each of these starts from this template:

apiVersion:
kind:
metadata:
spec:

Storage Class

  1. Navigate to the API reference for the version of Kubernetes you are working on.
    • Reminder: kubectl version gets the Kubernetes version.
  2. Create storageclass.yml:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-premium-retain-sc
provisioner: kubernetes.io/azure-disk
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer # default here is Immediate
allowVolumeExpansion: true
parameters:
storageaccounttype: Premium_LRS
kind: managed

Reference: https://kubernetes.io/docs/reference/kubernetes-api/storage/storage-class-v1/

Create Persistent Volume Claim Manifest

  1. Create persistentvolumeclaim.yml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: azure-managed-disk-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: managed-premium-retain-sc
resources:
requests:
storage: 5Gi

Reference: https://kubernetes.io/docs/reference/kubernetes-api/core/persistent-volume-claim-v1/

  1. Deploy the storage class: kubectl apply -f storageclass.yml
  2. Deploy the Persistent Volume Claim: kubectl apply -f persistentvolumeclaim.yml

deploys

  1. Verify deployment: kubectl get pvc and kubectl get sc

deployed

  1. The PVC will show as Pending because it is waiting for a pod to be deployed (due to WaitForFirstConsumer).

MySQL Deployment with Config Map

Config Maps provide configuration data to pods.

  1. Create the mysql-deployment.yml file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
replicas: 1
strategy:
type: Recreate #RollingUpdate is default, but for stateful apps, Recreate is recommended
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.6
env:
- name: MYSQL_ROOT_PASSWORD
value: "dbpassword24"
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: usermanagement-dbcreation-script
mountPath: /docker-entrypoint-initdb.d/
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: azure-managed-disk-pvc # cross check name here with PVC manifest file
- name: usermanagement-dbcreation-script
configMap:
name: usermanagmeent-dbcreation-script

extension

  1. The mountPath of /docker-entrypoint-initdb.d/ is used because MySQL will execute .sh, .sql, .sql.gz, .sql.bz2, .sql.xz, and .sql.zst files found in that directory when the container starts for the first time. See https://hub.docker.com/_/mysql under "Initializing a Fresh Instance".

Create ClusterIP Service

  1. Create the mysql-clusterip.yml manifest using the standard template:
apiVersion:
kind:
metadata:
spec:
  1. Apply the entire directory: kubectl apply -f demo/

deploy

  1. If you get validation errors, check for typos such as incorrect capitalization of ClusterIP.

error

  1. Wait for the pod to come up. If it stays in Pending, check that resource requests don't exceed node capacity.

wait

  1. Once deployed, verify everything is running:

alt text

  1. Confirm the PVC is now Bound.
  2. Describe the pod: kubectl describe pod mysql-<pod-id>
  3. Get the logs: kubectl logs mysql-<pod-id>

logs

  1. Get the Persistent Volume: kubectl get pv

pv

  1. Check Azure — you will see the disk created in the MC_ resource group.

created disk

Connect to MySQL Database

  1. Run the following command to launch an interactive MySQL client pod:
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pdbpassword24

enter sql

Clean Up

  1. Delete all resources: kubectl delete -f demo/

delete

  1. Notice that the PVC is still present after deleting the rest of the infrastructure (due to the Retain policy).
  2. Run kubectl get pv to find the PV name.
  3. Delete the PV: kubectl delete pv <pv-name>
  4. Go to Azure and delete the disk manually from the MC_ resource group.

Use AKS Managed Storage Class

Now that we understand custom storage classes, we can use the simpler AKS-provisioned storage class.

  1. Remove persistentvolumeclaim.yml from the demo directory.
  2. Run kubectl apply -f demo/ to create the resources.
  3. A disk will be created automatically without needing a manual PVC.
  4. Run kubectl get pods to verify pods are running.
  5. Log into the MySQL server and check the database.
  6. Run kubectl get pv — note the reclaim policy is Delete instead of Retain.
  7. Clean up: kubectl delete -f demo/ — the disk will be deleted automatically.

Create an Application to Access the Database

This section introduces initContainers, which solve pod dependency ordering issues.

  1. Create two new files: appdeployment.yml and appservice.yml.

appdeployment.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: usermgmt-webapp
labels:
app: usermgmt-webapp
spec:
replicas: 2
selector:
matchLabels:
app: usermgmt-webapp
template:
metadata:
labels:
app: usermgmt-webapp
spec:
initContainers:
- name: init-db
image: busybox:1.31
command: ['sh', '-c', 'echo -e "Checking for the availability of MySQL Server deployment"; while ! nc -z mysql 3306; do sleep 1; printf "-"; done; echo -e " >> MySQL DB Server has started";']
containers:
- name: usermgmt-webapp
image: stacksimplify/kube-usermgmt-webapp:1.0.0-MySQLDB
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: DB_HOSTNAME
value: "mysql"
- name: DB_PORT
value: "3306"
- name: DB_NAME
value: "webappdb"
- name: DB_USERNAME
value: "root"
- name: DB_PASSWORD
value: "dbpassword24"

appservice.yml:

apiVersion: v1
kind: Service
metadata:
name: usermgmt-webapp-service
labels:
app: usermgmt-webapp
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: usermgmt-webapp
  1. Apply all manifests: kubectl apply -f demo/

deploy

  1. If you see image pull errors, check the image names in your manifests. Use kubectl describe pod <pod-name> to diagnose.

errors get pods

  1. Once fixed, apply again:

apply

  1. The Init:0/1 status means the init container is waiting for the MySQL service to be available on port 3306.
  2. Ensure the database password matches between the MySQL deployment and the webapp deployment.

running


Validation

  1. Check the logs and describe pods to verify health:

describe

  1. Check the PV: kubectl get pv
  2. Get the LoadBalancer IP from the service: kubectl get svc usermgmt-webapp-service
  3. Log into the application using admin101 / password101.
  4. Create a new user and verify it works.

webapp

  1. Verify data in MySQL:
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pdbpassword24
show tables;
select * from user;

Clean Up

  1. Delete all resources: kubectl delete -f demo/
  2. Check for remaining PVs: kubectl get pv
  3. Delete any remaining PVC disks from the Azure portal if needed.