PowerodIT
Kubernetes Patterns: Stateful Applications

Kubernetes Patterns: Stateful Applications

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.

Kubernetes Stateful Application WordPress Example

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.

$ mkdir wordpress-example
$ cd wordpress-example

Then download all the example resource files for the WordPress example.

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

And that’s it. We are now ready to start with the deployment of the different Kubernetes resources.

Step 2 – Create Persistent Volumes

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

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

$ 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.

$ 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

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

Step 3 – Deploy WordPress Application

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.

$ kubectl create secret generic mysql-pass --from-literal=password=<YOUR_PASSWORD>

Check if the secret has been successfully created.

$ kubectl get secrets
NAME                 TYPE                                 DATA     AGE
...
mysql-pass           Opaque                               1         52s

Let’s look a bit deeper into the secret by using the describe key. We can see our password key with the hidden credentials.

$ kubectl describe secrets mysql-pass
Name:         mysql-pass
Namespace:   default
Labels:       <none>
Annotations: <none>
Type: Opaque

Data

====

password: 8 bytes

Now create the mysql deployment, by using the mysql-deployment.yaml kubernetes resource. This creates following resources:

  • 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
$ 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
$ 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

We can also verify the claim by looking at the persistent volumes.

$ 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

Last but not least, let’s deploy our WordPress application with the downloaded Kubernetes resource file. This will create:

  • 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
$ kubectl create –f wordpress-deployment.yaml

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

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

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

$ 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.

Kubernets WordPress example setup screen

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.

Kubernetes WordPress example faker press content

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

$ kubectl scale --replicas 0 deploy wordpress
$ kubectl scale --replicas 0 deploy wordpress-mysql

If we again scale up, our deployments content will reappear thanks to our persistent volume claims.

$ kubectl scale --replicas 1 deploy wordpress
$ kubectl scale --replicas 1 deploy wordpress-mysql

We just successfully tested our deployments persistent volume capability.

Conclusion

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: