Learn how you can persist data independently from the pod lifecycle with a hands on example application

In my previous guide Kubernetes Patterns: Stateless Applications we managed to set up a basic guestbook application. But what about applications which need to persist data independently of the pod lifecycle.

Thats where persistent volume storage and secrets come in. Persistent volumes allow you to persist data independently of the pod lifecycle, while secrets allow you to store sensitive data into a separate space from the pod configuration.

We will demonstrate these features by setting up a simple WordPress site inside our Kubernetes cluster.

Prerequisites

Running Kubernetes cluster

Internet connection

Kubectl connection

If you don’t have a running Kubernetes cluster yet, get started with my guide All in One Cluster with kubeadm as we will base this guide on that setup.

Step 1 – Prepare Deployment Resources

We will prepare all Kubernetes Resource files needed for later deployment.

local-volumes.yaml

mysql-deployment.yaml

wordpress-deployment.yaml

First create a directory where we will store all Kubernetes resources.



user@computer$ mkdir wordpress-example

user@computer$ cd wordpress-example

user@computer$ wget https://raw.githubusercontent.com/kubernetes/examples/master/mysql-wordpress-pd/local-volumes.yaml

user@computer$ wget https://raw.githubusercontent.com/kubernetes/examples/master/mysql-wordpress-pd/mysql-deployment.yaml

user@computer$ wget https://raw.githubusercontent.com/kubernetes/examples/master/mysql-wordpress-pd/wordpress-deployment.yaml

Step 2 – Create Persistent Volumes

Then download all the example resource files for the WordPress example.And that’s it. We are now ready to start with the deployment of the different Kubernetes resources.

To store our data persistently we will have to prepare persistent volumes.

Create two persistent volumes by using the downloaded Kubernetes volumes resource file.



user@computer$ kubectl create -f local-volumes.yaml

local-volumes.yaml apiVersion: v1

kind: PersistentVolume

metadata:

name: local-pv-1

labels:

type: local

spec:

capacity:

storage: 20Gi

accessModes:

– ReadWriteOnce

hostPath:

path: /tmp/data/pv-1

—

apiVersion: v1

kind: PersistentVolume

metadata:

name: local-pv-2

labels:

type: local

spec:

capacity:

storage: 20Gi

accessModes:

– ReadWriteOnce

hostPath:

path: /tmp/data/pv-2

Now check that the persistent volumes exist by using the persistentvolumes or pv key.



user@computer$ kubectl get pv

NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE

local-pv-1 20Gi RWO Retain Available 1m

local-pv-2 20Gi RWO Retain Available 1m

Step 3 – Deploy WordPress Application

And that’s it, we got our persistent volumes ready to serve claimable space for our pods.

Let’s setup our WordPress application to claim storage from our prepared volumes. To set up the database we will need to securely store a password. That’s where secrets come in handy.

Define a secret object with the name mysql-pass and the key password to store our mysql database credentials.



user@computer$ kubectl create secret generic mysql-pass –from-literal=password=<YOUR_PASSWORD>

user@computer$ kubectl get secrets

NAME TYPE DATA AGE

…

mysql-pass Opaque 1 52s

describe

user@computer$ kubectl describe secrets mysql-pass

Name: mysql-pass

Namespace: default

Labels: <none>

Annotations: <none>

Type: Opaque Data ==== password: 8 bytes

Service named WordPress, routing traffic to mysql pod

Persistent volume claim of 20GB

MySQL database deployment Loading credentials from secret object into MYSQL_ROOT_PASSWORD variable. Persistent volume claim mounting into container



Check if the secret has been successfully created.Let’s look a bit deeper into the secret by using thekey. We can see our password key with the hidden credentials.Now create the mysql deployment, by using the mysql-deployment.yaml kubernetes resource. This creates following resources:

user@computer$ kubectl create -f mysql-deployment.yaml

mysql-deployment.yaml apiVersion: v1

kind: Service

metadata:

name: wordpress-mysql

labels:

app: wordpress

spec:

ports:

– port: 3306

selector:

app: wordpress

tier: mysql

clusterIP: None

—

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

name: mysql-pv-claim

labels:

app: wordpress

spec:

accessModes:

– ReadWriteOnce

resources:

requests:

storage: 20Gi

—

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

name: wordpress-mysql

labels:

app: wordpress

spec:

strategy:

type: Recreate

template:

metadata:

labels:

app: wordpress

tier: mysql

spec:

containers:

– image: mysql:5.6

name: mysql

env:

– name: MYSQL_ROOT_PASSWORD

valueFrom:

secretKeyRef:

name: mysql-pass

key: password

ports:

– containerPort: 3306

name: mysql

volumeMounts:

– name: mysql-persistent-storage

mountPath: /var/lib/mysql

volumes:

– name: mysql-persistent-storage

persistentVolumeClaim:

claimName: mysql-pv-claim

Now let’s check that our service, deployment and persistent volume claim have been created correctly by using their shorthand notations:

pods = po

= service = svc

= deployment = deploy

= persistentvolumeclaims = pvc

user@computer$ kubectl get po,svc,deploy,pvc

NAME READY STATUS RESTARTS AGE

po/wordpress-mysql-7b4ffb6fb4-65vd9 1/1 Running 0 10m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

svc/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d

svc/wordpress-mysql ClusterIP None <none> 3306/TCP 10m NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE

deploy/wordpress-mysql 1 1 1 1 10m NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE

pvc/mysql-pv-claim Bound local-pv-1 20Gi RWO 10m

user@computer$ kubectl get pv

NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE

local-pv-1 20Gi RWO Retain Bound default/mysql-pv-claim 54m

local-pv-2 20Gi RWO Retain Available 54m

Service named WordPress, routing traffic to pod

Persistent Volume Claim of 20GB

Deployment named WordPress Loading credentials from secret key into WORDPRESS_DB_PASSWORD variable and the database hostname defined in the WORDPRESS_DB_HOST variable Mounting volume claim



We can also verify the claim by looking at the persistent volumes.Last but not least, let’s deploy our WordPress application with the downloaded Kubernetes resource file. This will create:

user@computer$ kubectl create –f wordpress-deployment.yaml

wordpress-deployment.yaml apiVersion: v1

kind: Service

metadata:

name: wordpress

labels:

app: wordpress

spec:

ports:

– port: 80

selector:

app: wordpress

tier: frontend

type: LoadBalancer

—

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

name: wp-pv-claim

labels:

app: wordpress

spec:

accessModes:

– ReadWriteOnce

resources:

requests:

storage: 20Gi

—

apiVersion: apps/v1beta2

kind: Deployment

metadata:

name: wordpress

labels:

app: wordpress

spec:

selector:

matchLabels:

app: wordpress

tier: frontend

strategy:

type: Recreate

template:

metadata:

labels:

app: wordpress

tier: frontend

spec:

containers:

– image: wordpress:4.8-apache

name: wordpress

env:

– name: WORDPRESS_DB_HOST

value: wordpress-mysql

– name: WORDPRESS_DB_PASSWORD

valueFrom:

secretKeyRef:

name: mysql-pass

key: password

ports:

– containerPort: 80

name: wordpress

volumeMounts:

– name: wordpress-persistent-storage

mountPath: /var/www/html

volumes:

– name: wordpress-persistent-storage

persistentVolumeClaim:

claimName: wp-pv-claim

Although we are defining the service port of type LoadBalancer, this will in our case jump back to exposing the port on NodePort.

Again we check that our service, deployment and volume claim have been deployed without problems.



user@computer$ kubectl get po,svc,deploy,pvc

NAME READY STATUS RESTARTS AGE

po/wordpress-db8f78568-cgpbw 1/1 Running 0 4m

po/wordpress-mysql-7b4ffb6fb4-65vd9 1/1 Running 0 23m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

svc/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d

svc/wordpress LoadBalancer 10.98.84.33 <pending> 80:31097/TCP 4m

svc/wordpress-mysql ClusterIP None <none> 3306/TCP 23m NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE

deploy/wordpress 1 1 1 1 4m

deploy/wordpress-mysql 1 1 1 1 23m NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE

pvc/mysql-pv-claim Bound local-pv-1 20Gi RWO 23m

pvc/wp-pv-claim Bound local-pv-2 20Gi RWO 4m

As we are basing this guide on my All in One Cluster with kubeadm we can get the exposing nodePort on the WordPress service and combine it with our node IP in a web browser. This will show us a running WordPress installation.

We have now successfully set up a WordPress application by using a WordPress deployment and a mysql datastore deployment. Both save their data to persistent volume claims.

Step 4 – Testing Persistence Features

Now that we have our application set up, with persistent volumes and their mounted volume claims where to save data, let’s see how it holds up.

First, complete your WordPress setup. Afterwards login in to your new WordPress site and generate some test data by installing FakerPress plugin.

On your sidebar go to the FakerPress section and start generating some content. After that you should have many blog posts including images.

Now let’s scale down our WordPress and mysql database deployments to 0 replicas, which will wipe out all running pods.



user@computer$ kubectl scale –replicas 0 deploy wordpress

user@computer$ kubectl scale –replicas 0 deploy wordpress-mysql

,

user@computer$ kubectl scale –replicas 1 deploy wordpress

user@computer$ kubectl scale –replicas 1 deploy wordpress-mysql

Conclusion

If we again scale upour deployments content will reappear thanks to our persistent volume claims.We just successfully tested our deployments persistent volume capability.

We have managed to set up a small WordPress application with a MySQL data store. Both applications are able to persist their data independently of the pod lifecycle. We tested this by shutting down all pods of the application and then again starting them to find our data in their previous saved state. We also learned how to save sensitive data to secrets independently from pod resource specifications.