Skip to content

Learn how to deploy a highly-available, auto-scalable WordPress with CDN on Kubernetes!

Last updated on May 17, 2023

In this tutorial we will learn how to deploy a highly available and automatically scalable containerized installation of popular WordPress CMS, reaping all benefits of modern technologies offered by mainstream public cloud providers such as AWS. We will deploy our containerized WordPress installation on their EKS kubernetes infrastructure, and we will store our website files on their S3 storage service, backed by their CDN (content delivery network) service, thus serving website visitors from geographically close locations, minimizing the lag and improving their overall experience. Biggest advantage of using containerized services is the easiness of implementation of high-availability and more importantly – automatic scalability! This tutorial is of course short and generalized, but with a little bit more work you will be able to set your system in such way that at any given moment it will only use the right amount of resources it needs for smooth operation, drastically cutting down hosting costs when visits are low, and be able to automatically deploy more when visits go up, keeping operation smooth at all times.

Deploy MySQL 

Creating a highly available containerized MySQL cluster on Kubernetes can be achieved by deploying a StatefulSet with persistent storage using a combination of Kubernetes manifests. In this example, I’ll provide you with YAML files to deploy a MySQL cluster using the official MySQL Docker image.

1. Create a `mysql-configmap.yaml` file for MySQL configurations:

apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-configmap
data:
mysql.cnf: |
[mysqld]
skip-host-cache
skip-name-resolve
explicit_defaults_for_timestamp
# Enable the READ-COMMITTED isolation level
transaction-isolation=READ-COMMITTED
# Disable binary logging to avoid running out of disk space
# Set this to 'ROW' to enable replication
binlog-format=ROW

2. Create a `mysql-storage.yaml` file to define the persistent storage class and volume claim template:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mysql-storage
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
annotations:
volume.beta.kubernetes.io/storage-class: "mysql-storage"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

3. Create a `mysql-statefulset.yaml` file for the MySQL StatefulSet deployment:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1"
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: MYSQL_ROOT_PASSWORD
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-config
mountPath: /etc/mysql/conf.d
- name: mysql-data
mountPath: /var/lib/mysql
subPath: mysql
volumes:
- name: mysql-config
configMap:
name: mysql-configmap
items:
- key: mysql.cnf
path: mysql.cnf
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi

4. Create a `mysql-service.yaml` file for the MySQL headless service:

apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
clusterIP: None
selector:
app: mysql

5. Apply the Kubernetes manifests:

kubectl apply -f mysql-configmap.yaml
kubectl apply -f mysql-storage.yaml
kubectl apply -f mysql-statefulset.yaml
kubectl apply -f mysql-service.yaml

These YAML files will deploy a 3-node MySQL cluster using a StatefulSet with a headless service for stable network identity and persistent storage for each MySQL instance. Be sure to replace the `MYSQL_ROOT_PASSWORD`

Deploy WordPress

To deploy a highly available WordPress site on a Kubernetes cluster with an S3 storage bucket as the WordPress upload folder, you’ll need to use an S3-compatible WordPress plugin, like WP Offload Media Lite, to handle the uploads. This example assumes you have an external MySQL database and will deploy WordPress with NGINX using Kubernetes manifests.

1. Create a `secrets.yaml` file for sensitive data:

apiVersion: v1
kind: Secret
metadata:
name: wordpress-secrets
type: Opaque
stringData:
DB_NAME: 'your_database_name'
DB_USER: 'your_database_user'
DB_PASSWORD: 'your_database_password'
DB_HOST: 'your_database_host'
AWS_ACCESS_KEY_ID: 'your_aws_access_key_id'
AWS_SECRET_ACCESS_KEY: 'your_aws_secret_access_key'
AWS_REGION: 'your_aws_region'
S3_BUCKET: 'your_s3_bucket_name'
S3_UPLOADS_CDN_URL: 'your_cdn_url'

Replace the placeholders with your actual values.

2. Create a `wordpress-deployment.yaml` file:

apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
spec:
replicas: 3
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:latest
env:
- name: WORDPRESS_DB_NAME
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: DB_NAME
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: DB_USER
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: DB_PASSWORD
- name: WORDPRESS_DB_HOST
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: DB_HOST
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: AWS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: AWS_SECRET_ACCESS_KEY
- name: AWS_REGION
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: AWS_REGION
- name: S3_BUCKET
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: S3_BUCKET
- name: S3_UPLOADS_CDN_URL
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: S3_UPLOADS_CDN_URL
ports:
- containerPort: 80
volumeMounts:
- name: wordpress-plugins
mountPath: /var/www/html/wp-content/plugins
volumes:
- name: wordpress-plugins
emptyDir: {}

3. Create a `nginx-deployment.yaml` file:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
volumes:
- name: nginx-conf
configMap:
name: nginx-conf

4. Create a `nginx-configmap.yaml` file:

apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
default.conf: |
upstream wordpress {
server wordpress:80;
}

server {
listen 80;

location / {
proxy_pass http://wordpress;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

5. Create a `wordpress-service.yaml` file:

apiVersion: v1
kind: Service
metadata:
name: wordpress
spec:
selector:
app: wordpress
ports:
- protocol: TCP
port: 80
targetPort: 80
clusterIP: None

6. Create a `nginx-service.yaml` file:

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80

7. Apply the Kubernetes manifests:

kubectl apply -f secrets.yaml
kubectl apply -f wordpress-deployment.yaml
kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-configmap.yaml
kubectl apply -f wordpress-service.yaml
kubectl apply -f nginx-service.yaml

These files will deploy a highly available WordPress site with NGINX as a reverse proxy in a Kubernetes cluster. Remember to install the WP Offload Media Lite plugin and configure it to use the provided AWS credentials and S3 bucket information.

Please note that in a production environment, you should consider using a more secure method to store sensitive data, such as Kubernetes Secrets encrypted with Sealed Secrets or using an external secrets manager like AWS Secrets Manager or HashiCorp Vault.

Published inTutorials

Be First to Comment

Leave a Reply

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

WordPress Appliance - Powered by TurnKey Linux