How to deploy WordPress and MySQL on Kubernetes

Containerum
6 min readSep 11, 2018

--

by Dmitry Krasnov

There are hardly many people who have not heard of WordPress — arguably the most popular CMS for websites and blogs. It is available as a docker image (over 10 million pulls on DockerHub), and by running it on Kubernetes you can build a reliable and scalable website platform.

In this tutorial I will show you how to deploy WordPress and a MySQL database using K8s. Both applications use PersistentVolumes and PersistentVolumeClaims to store data.

What are they? PersistentVolume (PV) resources are used to manage durable storage in a cluster. PersistentVolumes can also be used with other storage types like NFS. A PersistentVolumeClaim (PVC) is a request for storage by a user. Refer to the Kubernetes documentation for an exhaustive overview of supported PersistentVolumes.

Steps

In this manual we will go through several steps:

  1. Create PersistentVolumeClaims and PersistentVolumes
  2. Create a Secret for MySQL
  3. Deploy MySQL
  4. Deploy WordPress
  5. Troubleshooting

Before we begin

We will use NFS server to store WordPress and MySQL data. 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)

You need to change “172.31.32.0/24” to your private cluster subnet.

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

A Secret is an object that stores a piece of sensitive data like a password or key. Each item in a secret must be base64 encoded. Let’s create a secret for admin use. Encode the password (in our case — admin):

$ echo -n 'admin' | base64

You will get the encoded password: YWRtaW4=

Create a secret.yml file for MySQL and Wordpress that will be mapped as an Environment Variable as follows:

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

Don’t forget to add your password to the secret.yml above.

Then run:

$ kubectl create -f secret.yml

Deploy Persistent Volume for WordPress & MySQL

Create PV files and change the IP address of the NFS server you are using.

# 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"

Now run the following command to create a PV:

$ kubectl create -f pv-wordpress-mysql.yml

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

Great, now let’s deploy the apps!

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 2 separate configs:

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

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

  • it’s an image from the Docker Hub: mysql:5.6
  • it has app:wordpress & tier:frontend labels (used in Service)
  • it contains an environment variable called MYSQL_ROOT_PASSWORD which holds the value from our secret password
  • it has an open port 3306
  • it has a volume claim mounted in /var/lib/mysql.

Now create the deployment and service:

$ 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

Again, the file consists of two configs:

Service maps port 80 of the container to the node’s external IP:Port for all containers with the labels app:wordpress & 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 & tier:frontend labels (used in Service)
  • it contains environment variables WORDPRESS_DB_HOST, which is the internal host name of the MySQL instance, and WORDPRESS_DB_PASSWORD, which holds the value from our secret password
  • it has an open port 80
  • it has a volume claim mounted in /var/www/html from which the WP sources are served.

Here’s what we do next:

$ kubectl create -f wordpress-deploy.yml

Launch WordPress

To access WordPress list the services and navigate to the External IP:Port , in our case that would be EXTERNAL_IP:32723.

Done! Now you can create your own blog or website in the WordPress web panel.

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 & /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

Conclusion

We have deployed a wordpress with MySQL, Persistent volumes, and NFS on Kubernetes. The main benefit of this stack is flexibility since it allows you to implement practically any type of workflow. This workflow can be extended or complexified depending on your development needs.

Thanks for reading! Feel free to leave your feedback. Don’t forget to follow us on Twitter and join our Telegram chat to stay tuned!

Containerum Platform is an open source project for managing applications in Kubernetes available on GitHub. We are currently looking for community feedback, and invite everyone to test the platform! You can submit an issue, or just support the project by giving it a ⭐. Let’s make cloud management easier together!

--

--

Containerum

Containerum Platform for managing applications in Kubernetes.