Keeping the State of Apps 1: Introduction to Volume and volumeMounts

In this part of our Kubernetes 101 series, we will bring persistence into play. You will learn how to provide persistent storage in the form of different volumes to the Pods. This allows containers within the Pods or other distributed instances in the cluster to have access to the same data by mounting the created volume inside a container.

The topics below will be covered as well as hands-on practice to show you a real-time scenario on how these components work:

  • Definitions of  Volumes and volumeMount 
  • What types of Volumes are available?
  • How to use simple Volumes and volumeMount inside a Pod 
  • Hands-on practice 

Kubernetes Volumes and volumeMounts

A Volume in Kubernetes represents a directory with data that is accessible across multiple containers in a Pod. The container data in a Pod is deleted or lost when a container crashes or restarts, but when you use a volume, the new container can pick up the data at the state before the container crashes. The volume outlives the containers in a Pod and can be consumed by any number of containers within that Pod. 

A volume usage entails the declaration of the volume in a Pod by specifying a “volumes” property under the spec (spec.volumes) field in a Pod manifest file, followed by the volume in an array format. The configuration will look like this:

spec:
 volumes:
 - name: xyz

A volumeMount, on the other hand, entails mounting of the declared volume into a container in the same Pod. A “volumeMounts” property (spec.container.volumeMounts), as well as the “name” property which is the volume name to be mounted and the mountPath field where the volume will be mounted, are declared in the container in a Pod. The configuration will look like this:

spec:
 containers:
 - name: my-app
   image: nginx
   volumeMounts:
   - name: xyz
     mountPath: /app/config

Volume and volumeMounts go hand in hand. You can not create a volume without mounting it or mount a volume that has not been created. 

Below is a configuration example of a volume declaration in a Pod and mounting of the declared volume in a container:

spec:
  containers:
  - name: my-app
    image: nginx
    volumeMounts:
    - name: xyz
      mountPath: /app/config
  volumes:
  - name: xyz

NOTE: It is vital that the name of the volume to be mounted in the container under the volumeMounts.name property is the same as the name of the volume. 

Types of Volumes

Currently, there are different volume types supported by Kubernetes. We will walk you through some of these types including hand-on practice to give you a deeper knowledge of the functionalities. There are volume types that require external configuration like awsElasticBlockStore, azureDisk etc. which might involve billing or payment. We will exclude these from our hands-on practice but focus on others like emptyDir, hostPath, secret, configMap etc.  More on volume types can be found in the Kubernetes documentation on volume. 

Volume Types Category

Volume types are classified into two categories:

  1. Ephemeral: This is the category with the same lifetime as the Pod lifecycle but persists beyond container restart. It is a fast volume solution but not durable, thus, should be used for temporary data or applications that do not require data persistency. The volume types under this category are emptyDir, configMap, secret etc.
  2. Durable: These are volume types that outlive the Pod lifecycle. The lifetime is independent on the Pod lifecycle but persists across both container and Pod restarts. Data is preserved in this category when Pod crashes or is deleted. The volume types under this category are: hostPath, persistentVolumeClaim, awsElasticBlockStore, azureDisk, gcePersistentDisk etc. 

EmptyDir Volume Type

An emptyDir volume is a volume type that is first created when a Pod is assigned to a Node. Its lifespan is dependent on the lifecycle of the Pod on that Node but recreates when the containers crash or restart. When a Pod dies, crashes, or is removed from a Node, the data in the emptyDir volume is deleted and lost. This type of volume is suitable for temporary data storage.

How to Create an emptyDir Volume

An emptyDir volume type is created by first creating a volume and then declaring the volume type name as a field in the Pod manifest file under the volume property section with empty curly braces{} as its value. The example will look like this:

  volumeMounts:
  - mountPath: /cache
    name: my-volume
volumes:
- name: my-volume
  emptyDir: {}

Now that you have seen what volume and volumeMount entail and their functionalities theoretically, it is now time to put this into practice by following the steps below on how to create a volume with emptyDir as the volume type. 

It is essential to have a basic knowledge of Pods/Deployments to follow this exercise. You can check our previous blog posts on Pods and Deployments to familiarise yourself with how to work with them. You also need a running Kubernetes cluster and the kubectl command-line tool must be configured to work with the cluster. You can easily create a Kubernetes cluster on any environment with KubeOne. Check our Getting Started guide for instructions. Alternatively, you can simply use the Kubernetes playground for practising purposes. 

The steps below will walk you through how to create a Pod that uses emptyDir volume type.

Step 1: Create a Pod with the below manifest file:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: my-app
    image: nginx
    ports:
    - containerPort: 8080
    imagePullPolicy: Always
    volumeMounts:
    - name: my-volume
      mountPath: /app
  volumes:
  - name: my-volume
    emptyDir: {} 

The above manifest file is described as follow:

apiVersion→  ##  Deployment object apiVersion. Every object in Kubernetes has its apiVersion with values which includes v1, apps/v1, batch/v1 depending on the object.
kind →  ## This field declares the object and the value must be in sentence case.
metadata.name 🡪 ## The name of the object to be created is declared in this field. 
spec.containers.name 🡪 ## This field declares the name of the container image.
spec.containers.image 🡪 ## The container image for the application is declared in this field.
volumes.name → ## The name of the volume is declared in this field.
volumes.emptyDir →  ## The volume type is specified in this field.
volumeMounts.name → ## The declared volume is injected into the container in this field which is why the declaration is under the container specification. The name must be the same as the name of the volumes.
volumeMounts.mountPath → The path where the volume will be mounted.

Step 2: Create the Pod using kubectl create command:

$ kubectl create -f emptyDir.yaml
pod/myapp created

Step 3: Check the status of the Pod to see if it is running:

$ kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
myapp   1/1     Running      0       15s

Step 4: Check the Pod description for more information about the volume in the Pod:

$ kubectl describe pod myapp
Name:         myapp
Namespace:    default
Priority:     0
Node:         node01/172.17.0.11
Start Time:   Fri, 09 Oct 2020 19:25:37 +0000
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           10.244.1.7
IPs:
  IP:  10.244.1.7
Containers:
  my-app:
    Container ID:   docker://f7fe0c11eef32b6c619a619354895458b7d7a7602f5d09aea03fec0c90efa082
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:fc66cdef5ca33809823182c9c5d72ea86fd2cef7713cf3363e1a0b12a5d77500
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Fri, 09 Oct 2020 19:25:40 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /app from my-volume (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-z8p4x (ro)
Volumes:
  my-volume:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)

Step 5: Exec into the Pod and perform some basic commands:

$ kubectl exec -it myapp -- bin/bash
root@myapp:/# ls    ## Check to see if the directory is available
app  bin  boot  dev  docker-entrypoint.d

Step 6: Check the volume in the directory for existing data. In this case, it should be empty.

$ root@myapp:/# ls app/
$ root@myapp:/#

Step 7: Create a file and write some data into it:

$ root@myapp:/# echo I love Kubermatic > app/new-file

Step 8: Check to see if the data is stored:

$ root@myapp:/# ls app/
new-file

Now that you can see that it is stored, display the content of the data using the below command:

$ cat app/new-file
I love Kubermatic

Step 9: Exit the Pod and perform a clean up by deleting the Pod using kubectl delete command:

$ kubectl delete pod myapp
pod "myapp" deleted

HostPath Volume Type

hostPath volume type is a durable volume type that mounts a directory from the host Node’s filesystem into a Pod. The file in the volume remains intact even if the Pod crashes, is terminated or is deleted. It is important that the directory and the Pod are created or scheduled on the same Node. 

The below manifest represents a hostPath configuration inside a Pod:

volumes:
- name: hostpath-volume	  # The name of the volume
  hostPath:
   path: /data           # directory location on host

How to Create and use a hostPath Volume in a Pod

You can create a hostPath volume and just like the emptyDir volume type. However, the volume type name, hostPath, will replace the emptyDir in the Pod manifest file. You will also declare a path property which is the directory location on the host and a child of hostPath property. The complete configuration will look like this:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: my-app
    image: nginx
    ports:
    - containerPort: 8080
    volumeMounts:
    - name: my-volume
      mountPath: /app
  volumes:
  - name: my-volume
    hostPath:
      path: /mnt/vpath  

Now that the manifest file is ready, the below steps will guide you on how to create a hostPath volume and mount it into a Pod; after that, we will test the functionalities.

Step 1: Create a Pod with the manifest file above:

$ kubectl create -f hostpath-volume.yaml
pod/myapp created

Step 2: Check the status of the Pod using kubectl get command:

$kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
myapp   1/1     Running      0       10m

Step 3: Exec into the Pod and create a file in the directory:

$kubectl exec -it myapp -- /bin/bash
root@myapp:/# 

Step 4: Change to the /app directory:

root@myapp:/# cd /app	 ## Where /app is the mountPath value from the YAML manifest file.

Step 5: Create a file using echo command and store some data in the file:

root@myapp:/app# echo "I love Kubermatic" > file.txt

Step 6: Check to see if the data is created and stored in the file:

root@myapp:/app# ls
file.txt
root@myapp:/app# cat file.txt
I love Kubermatic
Exit the Pod.
root@myapp:/app# exit
exit

Now, ssh into the Node to check if the data created in the /app directory in the Pod can be found in the /mnt/vpath in the Node.

Step 7:

$ ssh ubuntu@x.xx.xxx.xxx   
ubuntu@x.xx.xxx.xxx:~$

Here, ubuntu is the username while x.xx.xxx.xxx is the hostname i.e (internal or external IP address). Your username and hostname might differ depending on the OS and cloud provider you used when provisioning your cluster. 

Step 8:

Change to the /mnt/vpath directory, which is the value of the hostpath path in the YAML manifest file.

ubuntu@x.xx.xxx.xxx:~$ cd /mnt/vpath
ubuntu@x.xx.xxx.xxx:/mnt/vpath$

Step 9: Check to confirm if the file and data created in the Pod above are available in the directory:

ubuntu@x.xx.xxx.xxx:/mnt/vpath$ ls
file.txt
ubuntu@x.xx.xxx.xxx:/mnt/vpath$ cat file.txt
I love Kubermatic

Now, create another file and store some data in the file inside the Node. Exit the Node and login into the Pod using kubectl exec. Then perform Step 6 once again to view the file and data created in the Node directly in the Pod and exit.

NOTE: The Pod must be running in the same Node.

Checking the Persistent State of a hostPath Volume

One of the advantages of hostPath volume over EmptyDir is the ability to outlive the Pod lifecycle. We want to show you the practical steps of this scenario by deleting the Pod and then ssh into the Node once again to check the file and the data.  

Step 1: Delete the Pod using kubectl delete command:

$ kubectl delete pod myapp
pod "myapp" deleted

Step 2: Check the status of the Pod:

$ kubectl get pod 
No resources found in default namespace.

Step 3: Now that the Pod has been deleted, ssh into the Node and perform step 8 and 9.

ubuntu@x.xx.xxx.xxx:~$ cd /mnt/vpath
ubuntu@x.xx.xxx.xxx:/mnt/vpath$
ubuntu@x.xx.xxx.xxx:/mnt/vpath$ ls
file.txt
ubuntu@x.xx.xxx.xxx:/mnt/vpath$ cat file.txt
I love Kubermatic

You can see that the data stored in a file which was initially created in a Pod remains intact and can be accessed through the Node even when the Pod has been deleted. This affirms the ability of hostPath volume type to outlive the lifecycle of a Pod.

Summary

Now that you have seen and practiced how to create a volume using emptyDir and hostPath volume types, and mount the volume into a container in a Pod , we are going to look at  Secret and ConfigMap which are ephemeral volume types in our next blog post. What is a Kubernetes Secret and configMap? What are their similarities, differences and functionalities? These and many more questions will be our focus in the next parts of this series. 

We’d love to hear from you! Please contact us with any thoughts or questions you might have about volumes.

Learn More

Seyi Ewegbemi

Seyi Ewegbemi

Student Worker