I just launched WordPress and MySQL on Kubernetes in 11 minutes

WordPress is arguably the most popular CMS for websites and blogs. By running WordPress on Kubernetes we can build a reliable and scalable content management platform. It is available as a docker image with over 10 million pulls on DockerHub.

It’s believed that there are nearly two billion websites online today that means more or less one website for every four people in the world.

The fact that we have nearly two billion websites online today is thanks CMS, And according to BuiltWith Trends, WordPress has 59.9% market share making it top website platform and popular CMS.

So, If you are someone who cares about SEO, Speed, and Efficiency, Kubernetes is an answer because it allows large numbers of containers to work together in harmony, reducing operational burden.

With Kubernetes we can run containers across many different machines. Scaling up or down by adding or removing containers when demand changes.

Kubernetes Clusters
Source: Google Cloud

Alright, we heard that “Kubernetes is awesome”  but now it about time to walk the walk. In the next 30 minutes, I’ll take you through the entire process of creating your first WordPress running on Kubernetes and you better believe it’s going to be awesome.


Also, if you want to learn more about Kubernetes, takes one these classes;


Deploy WordPress and MySQL on Kubernetes in 8 Easy Steps 

Without further ado let’s start with what you’ll need. First off you’ll need to sign up for Google Cloud and you also claim $300 Credit from Google Cloud valid up to 12 months. There are a few steps you’ll have to take in order to be squared away but the entire process should take less than 5 minutes.


NFS Server Setup

We can use the NFS server to store WordPress and MySQL data. So, Let’s prepare it first.

Open ports TCP:111, UDP: 111, TCP:2049, UDP:2049

sudo yum install nfs-utils -y

Now we will share the NFS directory over the private subnet of Kubernetes:

sudo vi /etc/exports
/ 172.31.32.0/24(rw,sync,no_root_squash)

Remember, you will need to change 172.31.32.0/24 to your private cluster subnet.


Create Backup Directory

We will need to create a backup directory for MySQL & WordPress files for volumes:

sudo mkdir /{mysql,html}
sudo chmod -R 755 /{mysql,html}
sudo chown nfsnobody:nfsnobody /{mysql,html}
sudo systemctl enable rpcbind ; sudo systemctl enable nfs-server
sudo systemctl enable nfs-lock ; sudo systemctl enable nfs-idmap
sudo systemctl start rpcbind ; sudo systemctl start nfs-server
sudo systemctl start nfs-lock ; sudo systemctl start nfs-idmap

Create a Secret for MySQL Password

After MySQL setup, we need to use MySQL secrets engine. The MySQL Secrets engine Vault generates database credentials dynamically based on configured roles.

A Secret for MySQL is an object that stores a piece of sensitive data like a password or key.

Each item in a secret must be base64 encoded. So, let’s create a secret for admin use and encode the password for admin:

echo -n 'admin' | base64

You will get the encoded password: YRWtaW4=

Or we can also do the following according to the documentation,

Create the Secret object from the following command. You will need to replace YOUR_PASSWORD with the password you want to use.

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

Verify that the Secret exists by running the following command:

kubectl get secrets

The response should be like this:

NAME                  TYPE                    DATA      AGE
mysql-pass            Opaque                  1         42s
Note: To protect the Secret from exposure, neither get nor describe show its contents.

Create a secret.yml file for MySQL so that it will be mapped with WordPress as an Environment Variable:

apiVersion: v1
kind: Secret
metadata:
  name: mysql-pass
type: Opaque
data:
  password: YRWtaW4=

This means that services that need to access a database no longer need to hard code credentials: they can request them from Vault, and use Vault's leasing mechanism to more easily roll keys. So, don’t forget to add your encoded password to your secret.yml above.

Once done, run the following command:

kubectl create -f secret.yml

Deploy Persistent Volume for WordPress & MySQL

Now, let's go ahead with creating PV files and change the IP address of the NFS server you are using.

On Kubernetes, both WordPress and MySQL use PersistentVolumes (PV) and PersistentVolumeClaims (PVC) to store data. Here's a link to Kubernetes documentation, if you wish to learn more about PV and PVS.

PVC is a request for storage that can at some point become available, bound to some actual PV.

Let's create a PVC for WordPress first with the following content:

# Create PersistentVolume
# change the ip of NFS server
apiVersion: v1
kind: PersistentVolume
metadata:
  name: wordpress-persistent-storage
  labels:
    app: wordpress
    tier: frontend
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 172.31.39.63
    # Exported path of your NFS server
    path: "/html"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-persistent-storage
  labels:
    app: wordpress
    tier: mysql
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 172.31.39.63
    # Exported path of your NFS server
    path: "/mysql"

And then execute the following command to Deploy the MySQL from the mysql-deployment.yaml file:

kubectl create -f pvc-wordpress.yml

Then let's verify that a PersistentVolume got dynamically provisioned. Note that it can It can take up to a few minutes for the PVs to be provisioned and bound.

kubectl get pvc

The response should be like this:

NAME             STATUS    VOLUME                                     CAPACITY ACCESS MODES   STORAGECLASS   AGE
mysql-pv-claim   Bound     pvc-91e44fbf-d477-11e7-ac6a-42010a800002   20Gi     RWO            standard       29s

Verify that the Pod is running by running the following command:

kubectl get pods

Deploy PersistentVolumeClaim(PVC)

PVC is a request for storage that can at some point become available, bound to some actual PV.

Create a PVC for WordPress with the following content:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-persistent-storage
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 6Gi

And then run:

kubectl create -f pvc-wordpress.yml

Do the same for MySQL:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-persistent-storage
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 6Gi

Run:

kubectl create -f pvc-mysql.yml

Verify that a PersistentVolume got dynamically provisioned:

kubectl get pvc
 Note: It can take up to a few minutes for the PVs to be provisioned and bound.

The response should be like this:

NAME             STATUS    VOLUME                                     CAPACITY ACCESS MODES   STORAGECLASS   AGE
wp-pv-claim      Bound     pvc-e69d834d-d477-11e7-ac6a-42010a800002   20Gi     RWO            standard       7s

Verify that the Service is running by running the following command:

kubectl get services wordpress

The response should be like this:

NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
wordpress   ClusterIP   10.0.0.89    <pending>     80:32406/TCP   4m
Note:  Minikube can only expose Services through NodePort. The EXTERNAL-IP is always pending.

Great, we’ve made it to the deployment step.


Deploy MySQL

Here’s the mysql-deploy.yml for MySQL service and deployment:

apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql   # will be used as a value in
  labels:                 # WORDPRESS_DB_HOST in wordpress-deploy.yml
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion:  apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  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        # the one generated before in secret.yml
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage  # which data will be stored
          mountPath: "/var/lib/mysql"
      volumes:
      - name: mysql-persistent-storage    # PVC
        persistentVolumeClaim:
          claimName: mysql-persistent-storage

The file consists of two separate configurations:

The Service part maps MySQL’s port 3306 and makes it available for all containers with the labels app:wordpress and tier:mysql.

The Deployment part declares the strategy for creating and specs of our MySQL container:

  • It’s an image from the Docker Hub: mysql:5.6
  • It has app:wordpress and tier:frontend labels (used in Service)
  • It also contains an environment variable called MYSQL_ROOT_PASSWORD which holds the value from our secret password
  • Port 3306 is now open
  • Volume is claimed and mounted in /var/lib/mysql.

Now we can create the deployment and service by executing the following command:

kubectl create -f mysql-deploy.yml

Deploy WordPress

The process above is pretty much the same for WordPress. Here’s the wordpress-deploy.yml:

# create a service for wordpress
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: ClusterIP---
apiVersion: apps/v1beta2 # for versions before 1.9.0 use 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          # generated before in secret.yml
              key: password
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: "/var/www/html"          # which data will be stored
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wordpress-persistent-storage

As you might have noticed that this file also consists of two configurations:

Service maps port 80 of the container to the node’s external IP:Port for all containers with the labels app:wordpress and tier:frontend

Deployment declares the creation spec of our WordPress container:

  • It’s an image from the Docker Hub: wordpress:4.8-apache
  • It has app:wordpress and  tier:frontend labels (used in Service)
  • It contains environment variables WORDPRESS_DB_HOST, which is the internal hostname of the MySQL instance, and WORDPRESS_DB_PASSWORD, which holds the value from our secret password
  • Port 80 is open 
  • it has a volume claim mounted in /var/www/html from which the WordPress sources are served.

Here’s what we need to execute next:

kubectl create -f wordpress-deploy.yml

Verify that a PersistentVolume got dynamically provisioned:

  kubectl get pvc
  Note: It can take up to a few minutes for the PVs to be provisioned and bound.

The response should be like this:

  NAME             STATUS    VOLUME                                     CAPACITY ACCESS MODES   STORAGECLASS   AGE
  wp-pv-claim      Bound     pvc-e69d834d-d477-11e7-ac6a-42010a800002   20Gi     RWO            standard       7s

Verify that the Service is running by running the following command:

  kubectl get services wordpress

The response should be like this:

  NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
  wordpress   ClusterIP   10.0.0.89    <pending>     80:32406/TCP   4m
 Note: Minikube can only expose Services through NodePort. The EXTERNAL-IP is always pending.

Run the following command to get the IP Address for the WordPress Service:

minikube service wordpress --url

Launch WordPress

To access WordPress, Copy the IP address, and load the page in your browser to view your site.

You should see the WordPress set up the page similar to the following screenshot.

WordPress Kubernnetes

Warning: Do not leave your WordPress installation on this page. If another user finds it, they can set up a website on your instance and use it to serve malicious content.

Either install WordPress by creating a username and password or delete your instance.

Done! Now you can create your own website from the WordPress Dashboard.


Troubleshooting

You can check if everything works fine by using the following commands:

kubectl get pv,pvc // list pv,pvc for MySQL & WordPress
kubectl get deployment // list deployment for MySQL & WordPress
kubectl get pods
kubectl describe pods pod-name //pod-name from previous Command

On NFS Server

Check NFS server and you will see your data under /mysql and /html

ls /mysql
ls /html

After installing WordPress try to delete one pod and it will mount data from /html automatically:

kubeclt delete pods pod-name

Cleaning up

Run the following command to delete your Secret:

kubectl delete secret mysql-pass

Run the following commands to delete all Deployments and Services:

kubectl delete deployment -l app=wordpress
kubectl delete service -l app=wordpress

Run the following commands to delete the PersistentVolumeClaims. The dynamically provisioned PersistentVolumes will be automatically deleted.

kubectl delete pvc -l app=wordpress

That's it

You have deployed a WordPress with MySQL, Persistent volumes, and NFS using Kubernetes engine.

You can now go ahead and boldly Say "Hello World"  

Thanks for reading! If you liked this article enough, do share it with your friends on Social media and feel free to leave your Questions or Comments below!

happy learning . . .👇🏾

Learning How to Learn
Powerful mental tools to help you master tough subjects