How to add persistent storage to your Kubernetes apps on Azure
Tags: kubernetes, tutorial, showdev, beginners
Published: 2019-09-23 12:58:43

In this blog post, we will look at an example of how to use [Azure Disk](https://azure.microsoft.com/services/storage/disks/?WT.mc_id=devto-blog-abhishgu) as a storage medium for your apps deployed to [Azure Kubernetes Service](https://azure.microsoft.com/services/kubernetes-service/?WT.mc_id=devto-blog-abhishgu).

You will:

- Setup a Kubernetes cluster on Azure
- Create an Azure Disk and a corresponding `PersistentVolume`
- Create a `PersistentVolumeClaim` for the app `Deployment`
- Test things out to see how it all works end to end

### Overview

You can use Kubernetes `Volume`s to provide storage for your applications. There is support for multiple types of volumes in Kubernetes. One way of categorizing them is as follows

- **Ephemeral** - `Volume`s which are tightly coupled with the `Pod` lifetime (e.g. `emptyDir` volume) i.e. they are deleted if the `Pod` is removed (for any reason). 
- **Persistent** - `Volume`s which are meant for long term storage and independent of the `Pod` or the `Node` lifecycle.  This could be `NFS` or cloud based storage in case of managed Kubernetes offerings such as Azure Kubernetes Service, Google Kubernetes Engine etc.
 

Kubernetes `Volume`s can be provisioned in a `static` or `dynamic` manner. In "static" mode, the storage medium e.g. Azure Disk, is created manually and then referenced using the `Pod` spec as below:

        volumes:
            - name: azure
                azureDisk:
                kind: Managed
                diskName: myAKSDisk
                diskURI: /subscriptions/<subscriptionID>/resourceGroups/MC_myAKSCluster_myAKSCluster_eastus/providers/Microsoft.Compute/disks/myAKSDisk


> I would highly recommend reading through the excellent tutorial on how to ["Manually create and use a volume with Azure disks in Azure Kubernetes Service (AKS)"](https://docs.microsoft.com/azure/aks/azure-disk-volume?WT.mc_id=devto-blog-abhishgu)

### Is there a better way?

![](https://media.giphy.com/media/26gR0YFZxWbnUPtMA/giphy-downsized.gif)

In the above `Pod` manifest, the storage info is directly specified in the `Pod` (using the `volumes` section). This implies that the developer needs to know all details of the storage medium e.g. in case of Azure Disk - the `diskName`, `diskURI` (disk resource URI), it's `kind` (type). There is definitely scope for improvement here and like most things in software, it can be done with another level of indirection or abstraction using concepts of Persistent Volume and Persistent Volume Claim.

The key idea revolves around "segregation of duties" and decoupling storage creation/management from its usage:

- When an app needs persistent storage for their application, the developer can request for it by "declaring" it in the pod spec - this is done using a `PersistentVolumeClaim`
- The actual storage provisioning e.g. creation of Azure Disk (using azure CLI, portal etc.) and representing it in the Kubernetes cluster (using a `PersistentVolume`) can be done by another entity such as an admin

Let's see this in action!

### Pre-requisites:

You will need the following:

- [Microsoft Azure account](https://docs.microsoft.com/azure/?WT.mc_id=devto-blog-abhishgu) - go ahead and [sign up for a free one!](https://azure.microsoft.com/free/?WT.mc_id=devto-blog-abhishgu)
- [Azure Kubernetes Service (AKS) cluster](https://azure.microsoft.com/services/kubernetes-service/?WT.mc_id=devto-blog-abhishgu) - this blog will guide you through the process of creating one
- Azure CLI or Azure Cloud Shell - you can either choose to install the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu) if you don't have it already (should be quick!) or just use the [Azure Cloud Shell](https://azure.microsoft.com/features/cloud-shell/?WT.mc_id=devto-blog-abhishgu) from your browser.
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) to interact with yur AKS cluster

> on your Mac, you can install `kubectl` as such:

    curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl
    chmod +x ./kubectl
    sudo mv ./kubectl /usr/local/bin/kubectl

The code is available [on GitHub](https://github.com/abhirockzz/aks-azuredisk-static-pv). Please clone the repository before you proceed

    git clone https://github.com/abhirockzz/aks-azuredisk-static-pv
    cd aks-azuredisk-static-pv

### Kubernetes cluster setup

You need a single command to stand up a Kubernetes cluster on Azure. But, before that, we'll have to create a resource group

    export AZURE_SUBSCRIPTION_ID=[to be filled]
    export AZURE_RESOURCE_GROUP=[to be filled]
    export AZURE_REGION=[to be filled] (e.g. southeastasia)

Switch to your subscription and invoke `az group create`

    az account set -s $AZURE_SUBSCRIPTION_ID
    az group create -l $AZURE_REGION -n $AZURE_RESOURCE_GROUP

You can now invoke `az aks create` to create the new cluster

> To keep things simple, the below command creates a single node cluster. Feel free to change the specification as per your requirements

    export AKS_CLUSTER_NAME=[to be filled]

    az aks create --resource-group $AZURE_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --node-count 1 --node-vm-size Standard_B2s --node-osdisk-size 30 --generate-ssh-keys

Get the AKS cluster credentials using `az aks get-credentials` - as a result, `kubectl` will now point to your new cluster. You can confirm the same

    az aks get-credentials --resource-group $AZURE_RESOURCE_GROUP --name $AKS_CLUSTER_NAME
    kubectl get nodes

> If you are interested in learning Kubernetes and Containers using [Azure](https://azure.microsoft.com/services/kubernetes-service/?WT.mc_id=devto-blog-abhishgu), a good starting point is to use the [quickstarts, tutorials and code samples](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu) in the documentation to familiarize yourself with the service. I also highly recommend checking out the [50 days Kubernetes Learning Path](https://azure.microsoft.com/resources/kubernetes-learning-path/?WT.mc_id=devto-blog-abhishgu). Advanced users might want to refer to [Kubernetes best practices](https://docs.microsoft.com/azure/aks/best-practices?WT.mc_id=devto-blog-abhishgu) or the watch some of the [videos](https://azure.microsoft.com/resources/videos/index/?services=kubernetes-service&WT.mc_id=devto-blog-abhishgu) for demos, top features and technical sessions.

### Create an Azure Disk for persistent storage

An Azure Kubernetes cluster can use Azure Disks or Azure Files as data volumes. In this example, we will explore Azure Disk. You have the option of an [Azure Disk backed by a Standard HDD](https://docs.microsoft.com/azure/virtual-machines/windows/disks-types?WT.mc_id=devto-blog-abhishgu#standard-hdd) or a [Premium SSD](https://docs.microsoft.com/azure/virtual-machines/windows/disks-types?WT.mc_id=devto-blog-abhishgu#premium-ssd)

Get the AKS node resource group

    AKS_NODE_RESOURCE_GROUP=$(az aks show --resource-group $AZURE_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query nodeResourceGroup -o tsv)

Create an Azure Disk in the node resource group

    export AZURE_DISK_NAME=<enter-azure-disk-name>

    az disk create --resource-group $AKS_NODE_RESOURCE_GROUP --name $AZURE_DISK_NAME --size-gb 2 --query id --output tsv

> we are creating a Disk with a capacity of 2 GB

You will get the resource ID of the Azure Disk as a response which will be used in the next step

    /subscriptions/3a06a10f-ae29-4242-b6a7-dda0ea91d342/resourceGroups/MC_testaks_foo-aks_southeastasia/providers/Microsoft.Compute/disks/my-test-disk


### Deploy the app to Kubernetes

The `azure-disk-persistent-volume.yaml` file contains the `PersistentVolume` details. We create it in order to map the Azure Disk within the AKS cluster.

{% gist https://gist.github.com/abhirockzz/089cdd7b45251f0ef49037c683c8598d %}

> Notice that the capacity (`spec.capacity.storage`) is 2 GB which is same as that of the Azure Disk we just created

Update `azure-disk-persistent-volume.yaml` with Azure Disk info

  - `diskName` - name of the Azure Disk which you chose earlier
  - `diskURI` - resource ID of the Azure Disk

Create the `PersistentVolume`

    kubectl apply -f azure-disk-persistent-volume.yaml
    
    persistentvolume/azure-disk-pv created


Next we need to create the `PersistentVolumeClaim` which we will use as a reference in the `Pod` specification

{% gist https://gist.github.com/abhirockzz/f26336bef3edf7cbd62df3889278ba6d %}

> we are requesting for 2 GB worth of storage (using `resources.request.storage`)

To create it:

    kubectl apply -f azure-disk-persistent-volume-claim.yaml

    persistentvolumeclaim/azure-disk-pvc created

Check the `PersistentVolume`

    kubectl get pv/azure-disk-pv


    NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM    STORAGECLASS   REASON   AGE
    azure-disk-pv   2Gi        RWO            Retain           Bound    default/azure-disk-pvc            8m35s

Check the `PersistentVolumeClaim`

    kubectl get pvc/azure-disk-pvc

    NAME             STATUS   VOLUME          CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    azure-disk-pvc   Bound    azure-disk-pv   2Gi        RWO                           9m55s

> Notice (in the `STATUS` section of the above outputs) that the `PersistentVolume` and `PersistentVolumeClaim` are `Bound` to each other

### Test

To test things out, we will use a simple Go app. All it does is push log statements to a file `logz.out` in `/mnt/logs` - this is the path which is mounted into the `Pod`


    func main() {
        ticker := time.NewTicker(3 * time.Second)
        exit := make(chan os.Signal, 1)
        signal.Notify(exit, syscall.SIGTERM, syscall.SIGINT)
        for {
            select {
            case t := <-ticker.C:
                logToFile(t.String())
            case <-exit:
                err := os.Remove(fileLoc + fileName)
                if err != nil {
                    log.Println("unable to delete log file")
                }
                os.Exit(1)
            }
        }
    }

To create our app as a `Deployment`

    kubectl apply -f app-deployment.yaml

Wait for a while for the deployment to be in `Running` state
  
    kubectl get pods -l=app=logz

    NAME                               READY   STATUS    RESTARTS   AGE
    logz-deployment-59b75bc786-wt98d   1/1     Running   0          15s

To confirm, check the `/mnt/logs/logz.out` in the `Pod`

    kubectl exec -it $(kubectl get pods -l=app=logz --output=jsonpath={.items..metadata.name}) -- tail -f /mnt/logs/logz.out

You will see the logs (just the timestamp) every 3 seconds. This is because the Azure Disk storage has been mounted inside your `Pod`

    2019-09-23 10:00:18.308746334 +0000 UTC m=+3.002071866
    2019-09-23 10:00:21.308779348 +0000 UTC m=+6.002104880
    2019-09-23 10:00:24.308771261 +0000 UTC m=+9.002096693
    2019-09-23 10:00:27.308778874 +0000 UTC m=+12.002104406
    2019-09-23 10:00:30.308804587 +0000 UTC m=+15.002130219

Once you're done testing, you can delete the resources to save costs.

### To clean up

    az group delete --name $AZURE_RESOURCE_GROUP --yes --no-wait

> This will delete all resources under the resource group

That's all for this blog! You saw how to attach and mount an Azure Disk instance to your app running in AKS using standard Kubernetes primitives like `PersistentVolume` and `PersistentVolume`. Stay tuned for more 😃😃

I really hope you enjoyed and learned something from this article! Please like and follow if you did. Happy to get feedback via [Twitter](https://twitter.com/abhi_tweeter) or just drop a comment

![](https://media.giphy.com/media/93fsZ7rI488L908x0T/giphy-downsized.gif)