How to deploy WordPress in Kubernetes

This guide describes how to deploy a scalable WordPress application inside a Kubernetes cluster based on Virtuozzo Infrastructure.

A typical simple WordPress application consists of the following components:

  1. The WordPress server that stores website content.
  2. The MySQL server that stores users and configurations.
  3. A load balancer that routes traffic from a public network to the WordPress server or servers.

To provide high availability for your website or the ability to scale it according to the external load, we will deploy multiple WordPress servers. In a real-life scenario, you should also have multiple MySQL servers with the active-active or active-passive configuration, which is out of scope of this guide. You can find a lot of examples of highly available MySQL clusters for Kubernetes on the Internet.

The schema of our WordPress deployment looks as follows:

wordpress architecture

Prerequisites

1. Deploy a Virtuozzo Infrastructure cluster.

2. Create the compute cluster with the Kubernetes service.

3. Configure the default storage policy for boot volumes on Kubernetes master nodes. Ensure that the selected policy is available for all projects where you are planning to deploy Kubernetes.

4. Install kubectl on your workstation.

5. Install Helm on your workstation.

6. Download and install Lens on your workstation.

7. Download the following WordPress deployment files:

8. In Virtuozzo Infrastructure, create a demo project with enough quotas to run a Kubernetes cluster. In your project, create a virtual network, a virtual router, and upload an SSH key:

  • For the network, specify the following parameters:

    • Name: private
    • IPv4 subnet: 10.0.0.0/24
    • Gateway: 10.0.0.1
    • DNS server: 8.8.8.8
  • For the router, specify the following parameters:

    • Name: router
    • The router connects your private and public networks

9. Download the ISO image of the TrueNAS server and upload it to your project.

Preparing the demo environment

Deploying the NFS server

The WordPress server itself does not support native scalability. With multiple WordPress servers, these servers must have access to the same persistent volumes (PV) where the WordPress server stores its content. To have simultaneous access from multiple Kubernetes pods to the same PV, this PV must support the ReadWriteMany (RWX) access mode. As the Virtuozzo Storage driver for Kubernetes does not support RWX, we need to use an external storage, such as NFS. For more details, refer to the Kubernetes CSI Storage Developer Documentation.

For this environment, we will deploy a virtual instance of the TrueNAS server inside a virtual machine.

1. Open the Virtuozzo Infrastructure self-service panel, go to the Virtual machines screen, and click Create virtual machine. Specify the following parameters:

  • Name: nas01

  • Image: <truenas-iso-image>

  • Volumes:

    • 100 GiB boot volume
    • 200 GiB data volume
  • Flavor: large (4 vCPUs, 8 GiB of RAM)

  • Network interface: private

2. Go to the Floating IPs screen and click Add floating IP. Select your public network to pick a floating IP from and assign this IP address to your nas01 private IP address.

3. Go to the nas01 virtual console and configure the TrueNAS server.

4. Go to the nas01 management IP address and log in to the interface.

5. Go to Services, enable the NFS service, and configure the following settings:

  • Enable NFSv4: enabled
  • NFSv3 ownership model for NFSv4: enabled
  • Allow non-root mount: enabled
  • Other settings: disabled

6. Go to StoragePools, click Add, and specify the following parameters:

  • Name: pool1
  • Select your 200 GiB disk
  • Policy: stripe

7. Go to StoragePoolspool1, click Add Dataset, and create a dataset with the name share1. Keep all other options by default.

8. Go to SharingUnix Shares (NFS), click Add, and specify the following parameters:

  • Path: /mnt/pool1/share1

  • General options:

    • All dirs: enabled
    • Quiet: disabled
    • Enabled: enabled
  • Advanced options:

    • Read only: disabled
    • Maproot User: non
    • Maproot Group: non
    • Mapall User: root
    • Mapall Group: wheel

9. Test the NFS share by mounting it to your workstation. For MacOS, for example, you can mount it as follows:

1
2
3
4
5
# mkdir /Users/<your_user>/Desktop/nfs
# mount -t nfs <nas01>:/mnt/pool1/share1 /Users/<your_user>/Desktop/nfs
# cd /Users/<your_user>/Desktop/nfs
# touch file
# rm file

10. (Optional) As soon as you configure your NFS server, we recommend limiting access to it from the Internet. With the recommended security group, you will be able to access the NFS server management UI, while the NFS server itself will only be accessible from your private network. Configure the security policy as follows:

  • Inbound rules:

    • Any: from your private IP range only
    • HTTPS: only from your own public IP
  • Outbound rules: keep the default rules

    nfs server

Creating a Kubernetes cluster

1. Open the Virtuozzo Infrastructure self-service panel.

2. Go to the Kubernetes screen, and click Create Kubernetes cluster.

3. Specify the following parameters:

  • Kubernetes version: the latest version
  • Cluster name: cluster01
  • SSH key: your uploaded SSH key
  • Network: private
  • Floating IP address: For Kubernetes API
  • High availability: enabled (it will create three master nodes in the HA configuration)
  • Master node flavor: medium or any other flavor with at least 2 vCPUs and 4 GiB of RAM
  • Integrated monitoring: enabled
  • Number of workers: 3
  • Worker node flavor: small
  • Other settings: keep the default values

4. Click Create.

Note: Boot volumes of Kubernetes master nodes use the default storage policy that you selected in the ComputeKubernetesSettings window in the admin panel.

Accessing the Kubernetes cluster

1. Once the status of your cluster01 Kubernetes cluster becomes Active, open its right panel and click Download kubeconfig to download the configuration file to your local machine.

2. On your local machine, open the terminal and load the kubectl configuration file:

1
# export KUBECONFIG=<your_kubeconfig_file>

3. View the list of nodes in your Kubernetes cluster:

1
2
3
4
5
6
7
# kubectl get nodes
NAME                                STATUS   ROLES    AGE     VERSION
cluster01-mxtbkucksr3w-master-0     Ready    master   5d20h   v1.23.5
cluster01-mxtbkucksr3w-master-1     Ready    master   5d20h   v1.23.5
cluster01-mxtbkucksr3w-master-2     Ready    master   5d20h   v1.23.5
cluster01-mxtbkucksr3w-node-0       Ready    <none>   5d20h   v1.23.5
cluster01-mxtbkucksr3w-node-1       Ready    <none>   5d20h   v1.23.5

4. Connect Lens to your Kubernetes cluster:

4.1. Open Lens and add a new Kubernetes cluster by adding your kubeconfig file.

4.2. Open the new Kubernetes cluster to review its components.

4.3. As the Kubernetes cluster has integrated monitoring enabled, you can also view the load for your master and worker nodes, as well as other helpful usage statistics provided by the integrated Prometheus server.

5. Create a storage class that allows creating persistent volumes automatically. PVs will be based on OpenStack Cinder and the underlying storage policy, and we will use the default storage class to create them for the MySQL server.

Note: If you want to use a storage class with a different name, you need to change it in the storage-class.yaml file.
1
2
3
4
# kubectl apply -f storage-class.yaml
# kubectl get storageclass
NAME                  PROVISIONER        AGE
default (default)     csi-cinderplugin   171m

Deploying NFS subdir external provisioner

We use NFS subdir external provisioner to automatically manage NFS-based persistent volumes for Kubernetes. The detailed description is also available on the GitHub page.

To deploy NFS subdir external provisioner, run the following command in your terminal:

1
2
3
4
5
# helm install nfs-external nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
    --set nfs.server=10.10.10.5 \
    --set nfs.path=/mnt/pool1/share1 \
    --set storageClass.accessModes=ReadWriteMany \
    --set storageClass.name=nfs

Where:

  • nfs.server is the private IP address of your NFS server.
  • nfs.path specifies the full path to your NFS share. If you configured TrueNAS according to this guide, the path will be /mnt/pool1/share1.
  • storageClass.accessModes is the PV access policy. In our case, it is ReadWriteMany. = storageClass.name sets the name for the new storage class based on NFS. In our case, it is nfs.

You can check that the new storage class is successfully created by running:

1
2
3
# kubectl get storageclass
NAME  PROVISIONER                                                 RECLAIMPOLICY  VOLUMEBINDINGMODE  ALLOWVOLUMEEXPANSION  AGE
nfs   cluster.local/nfs-external-nfs-subdir-external-provisioner  Delete         Immediate          true                  5d3h

Running the demo

Deploying a WordPress application

During the preparation stage, we have created two storage classes:

1
2
3
4
# kubectl get storageclass
NAME               PROVISIONER                                                 RECLAIMPOLICY  VOLUMEBINDINGMODE  ALLOWVOLUMEEXPANSION  AGE
default (default)  cinder.csi.openstack.org                                    Delete         Immediate          false                 5d3h
nfs                cluster.local/nfs-external-nfs-subdir-external-provisioner  Delete         Immediate          true                  5d3h

The default storage class is based on Virtuozzo Storage, and the nfs one is based on the external NFS server with RWX persistent volumes support.

Now, we can deploy a WordPress application (refer to the Prerequisites section, to download the required deployment files).

1. Use the kustomization.yaml file that does the following:

1.1. Creates a secret with a password for the MySQL database. You can change the default password in the parameter password=Virtuozzo1.

1.2. Creates the MySQL service and a pod by running the mysql-deployment.yaml file.

1.3. Creates the WordPress service and pods by running the wordpress-deployment-nfs.yaml file.

2. Run the command:

1
# kubectl apply -k ./

3. To check the deployed components, use these commands:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# kubectl get secrets
NAME                    TYPE                                  DATA   AGE
mysql-pass-6t2254bh75   Opaque                                1      5d2h
# kubectl get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-pvc     Bound    pvc-40a1d6e7-c3f2-408a-bf28-2418b3580892   20Gi       RWO            default        4d2h
wp-pv-claim   Bound    pvc-c90b7670-f6f5-466d-9f92-b2d128d8fa93   20Gi       RWX            nfs            4d2h
# kubectl get deployment
NAME                                           READY   UP-TO-DATE   AVAILABLE   AGE
nfs-external-nfs-subdir-external-provisioner   1/1     1            1           5d3h
wordpress                                      3/3     3            3           4d2h
wordpress-mysql                                1/1     1            1           4d2h
# kubectl get pod
NAME                                                            READY   STATUS    RESTARTS        AGE
nfs-external-nfs-subdir-external-provisioner-6b9f86956d-x5hhh   1/1     Running   1 (3d23h ago)   5d3h
wordpress-7f74bffc48-f9865                                      1/1     Running   0               4d2h
wordpress-7f74bffc48-n87cf                                      1/1     Running   0               4d2h
wordpress-7f74bffc48-x98bl                                      1/1     Running   0               4d2h
wordpress-mysql-776d8c78cd-vcc77                                1/1     Running   0               4d2h

4. To get the public IP address of your WordPress, run:

1
2
3
# kubectl get services wordpress
NAME        TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
wordpress   LoadBalancer   10.254.158.185   23.109.29.208   80:30717/TCP   4d2h

In this example, your public IP address is 23.109.29.208. This is the floating IP that is assigned to the load balancer created specifically for this WordPress application in your Virtuozzo Infrastructure project.

Now, you can open this IP address in your browser to access your WordPress website. To review the WordPress components, you can also use Lens.

Testing WordPress availability and scalability

For high availability and the ability to scale our deployment, we have deployed three replicas of the WordPress server:

1
2
3
4
5
# kubectl get pod | grep wordpress
wordpress-7f74bffc48-f9865                                      1/1     Running   0               4d2h
wordpress-7f74bffc48-n87cf                                      1/1     Running   0               4d2h
wordpress-7f74bffc48-x98bl                                      1/1     Running   0               4d2h
wordpress-mysql-776d8c78cd-vcc77                                1/1     Running   0               4d2h

1. Open the Virtuozzo Infrastructure self-service panel, go to the Virtual machines screen, and find the Kubernetes worker node with a name similar to <cluster01-mxtbkucksr3w-node-0>. Stop the VM by clicking Power off.

2. To check the status of our pods, list them by running:

1
2
3
4
5
6
# kubectl get pod | grep wordpress
wordpress-7f74bffc48-f9865                                      1/1     Terminating   0               4d2h
wordpress-7f74bffc48-n87cf                                      1/1     Running       0               4d2h
wordpress-7f74bffc48-x7kqx                                      1/1     Running       0               50s
wordpress-7f74bffc48-x98bl                                      1/1     Running       0               4d2h
wordpress-mysql-776d8c78cd-vcc77                                1/1     Running       0               4d2h

You can see that one of our pods has just been restarted. This happens because Kubernetes automatically supports the required number of pods and creates a new pod if one of them was lost due to worker node issues. The WordPress website is still available.

Note: With the default Kubernetes settings, it takes up to five minutes to reschedule a pod.

3. To add more WordPress servers to support the growing workload on our website, we need to change the number of replicas by running:

1
2
# kubectl scale --replicas=5 deployment/wordpress 
deployment.apps/wordpress scaled       0               4d2h

4. Check the number of pods by listing them:

1
2
3
4
5
6
7
8
# kubectl get pod | grep wordpress
wordpress-7f74bffc48-6mk2q                                      1/1     Running       0               10s
wordpress-7f74bffc48-f9865                                      1/1     Terminating   0               4d2h
wordpress-7f74bffc48-hhvdb                                      1/1     Running       0               10s
wordpress-7f74bffc48-n87cf                                      1/1     Running       0               4d2h
wordpress-7f74bffc48-x7kqx                                      1/1     Running       0               5m40s
wordpress-7f74bffc48-x98bl                                      1/1     Running       0               4d2h
wordpress-mysql-776d8c78cd-vcc77                                1/1     Running       0               4d2h

Now, we have five WordPress pods running.

You can also scale WordPress by changing the number of replicas in the WordPress deployment file and re-running it:

1. Open wordpress-deployment-nfs.yaml.

2. Change the replicas parameter from 3 to 5 or vice versa.

3. Run the command:

1
# kubectl apply -k ./

If you have any issues with pods, you can use the following command for troubleshooting:

1
2
3
4
5
6
7
8
# kubectl describe pod wordpress-7f74bffc48-f9865
Name:                      wordpress-7f74bffc48-f9865
Namespace:                 default
Priority:                  0
Service Account:           default
Node:                      cluster01-mxtbkucksr3w-node-1/10.11.0.141
Start Time:                Tue, 29 Nov 2022 16:33:03 +0200
...

(Optional) Deploying WordPress with Helm and Bitnami

Helm allows you to deploy WordPress with a fully highly available and scalable configuration in a single click by using the Bitnami WordPress template. The detailed description is also available on the GitHub page.

wordpress architecture based on bitnami

Before you proceed, ensure that you have the following:

  • Helm is installed.
  • NFS provisioner is configured with the storage class named nfs.

To deploy WordPress with Helm:

1. Adapt the WordPress configuration file according to your requirements. To create the default file, run:

1
# helm show values oci://registry-1.docker.io/bitnamicharts/wordpress > wordpress.yaml

The file will be created in your local directory. Please inspect it. If you have the default configuration created based on this guide, you can download the example configuration file or use wget:

1
# wget https://virtuozzo-k8s-demo-nfs.s3.amazonaws.com/wordpress.yaml

The example file has the following additional settings:

  • Storage policy for persistent volumes: nfs
  • WordPress admin password: Virtuozzo1!
  • WordPress app servers replication: enabled with 3 replicas
  • MariaDB replication: enabled with the active-passive configuration
  • Memcached server: enabled
  • Integrated metrics: enabled

2. Run the command:

1
# helm install -f wordpress.yaml my-wp oci://registry-1.docker.io/bitnamicharts/wordpress

Now, you can follow the onscreen guide to access your WordPress website:

1
2
3
4
5
6
7
1. Get the WordPress URL by running these commands:
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace default -w my-wp-wordpress'
   export SERVICE_IP=$(kubectl get svc --namespace default my-wp-wordpress --template "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}")
   echo "WordPress URL: http://$SERVICE_IP/"
   echo "WordPress Admin URL: http://$SERVICE_IP/admin"
2. Open a browser and access WordPress using the obtained URL.

Enjoy!