Installing WordPress on Kubernetes
My referral Link: Claim Your $200 Free Credit For 60 days With DigitalOcean
Installing WordPress on Kubernetes
There are a few ways to go about deploying WordPress on Kubernetes, depending on how much or how little you want to control and customise that process. You can create your own Docker images, publish them to your personal or public registry, or simply use the containers that are built from the official sources. There are Helm charts which are popular. You can even use the 1-click-installs that DigitalOcean author and provide. Try them all, but I’m sure if you think like me, you’ll want to have a little bit more control than a 1-click-install or use some of the Helm charts. I tried the Helm chart for bitnami WordPress and decided that I preferred to just use yaml and kubectl, which I will share with you here.
Overview
We’ll create our Kubernetes cluster on Digitalocean. If you don’t run linux or mac or just want to use a remote terminal env that is clean, Install doctl and kubectl tools within a local or remote virtual server running something like Ubuntu. I am using Ubuntu. I recommend that you do as well. But it is your choice. Seeing as you can click my link above and get $200 Credit for 60 days with DigitalOcean, create the smallest Ubuntu based droplet you can on DigitalOcean and make that your base for running commands locally within your Kubernetes cluster. Makes firewall rules and everything while getting started so much easier and convenient. While you are free to use Windblows at home, I assume if you do, you are smart enough on your own to make it work. I won’t cover any of that here. Kube the snail (located at the top of this page) is likely also the only Graphic you would see here. I have no snip or want to optimise jpegs here. Apparently google says my site loads real fast lol.
Setting Up Environment
I am using the root user for all commands but you do not have to. You can install within a normal user space if you prefer. Just change the locations from /usr/local/bin for example to somewhere within your PATH or use sudo.
Download/Install Helm, kubectl and DigitalOceans doctl tools.
We’ll use Helm later in order to install some of the components of our WordPress Kubernetes Cluster.
At the time of writing the version of Kubernetes I am using is 1.29.1, so I will download the same version of kubectl.
Doctl is a cli tool that enables the creation and management of most services available with DigitalOcean.
Install Helm
# snap install helm --classic
Install kubectl
# curl -LO "https://dl.k8s.io/release/v1.29.1/bin/linux/amd64/kubectl" -k
# install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
Install Doctl
# snap install doctl
# snap connect doctl:kube-config
# snap connect doctl:ssh-keys :ssh-keys
# snap connect doctl:dot-docker
Create DigitalOcean API Token
- Navigate the DigitalOcean Control Panel > API > Applications & API > Tokens
- In the Personal access tokens section, click the Generate New Token button
- This opens a New personal access token window: Enter meaningful name for token, Select from options
Expiration: Time limit or never as per your choice.
Select Scopes: read is default, click write as well if you'd like to be able to delete resources as well when you decommission services etc.
I recommend selecting write as well.
- Click Generate Token
When you click Generate Token, your token is generated and presented to you on your Personal Access Tokens page. The actual token is the long string of numbers and letters under the name. It is prefixed with dop_v1_ in order to distinguish it from other similar tokens.
Make sure to record your personal access token. For security purposes, it is only shown once! But hey, you can always create another one :)
Run the following to create the api authenticated connection between your doctl/kubectl terminal and DigitalOceans control panel. Use the API token to grant doctl access to your DigitalOcean account. Pass in the token string when prompted by doctl auth init, and give this authentication context a name.
# doctl auth init --context your-name-for-this
.....
.....
# doctl account get
expect
User Email Team Droplet Limit Email Verified User UUID Status
you@you Your Team 1000000 - high roller email is always true <YOUR AI UUID> active mostly wednesdays
Now You can use doctl to provision services, terminate services upon tearing down clusters or on demand and generally feel good about yourself. Trust me, when you are first getting started with Kubernetes, you will blow away clusters in a blink. That’s what my referral link: DigitalOcean $200 Credit for 60 Days is so fantastic for, you can learn on them and not worry about it. Further resources and options for the installations of the above tools can be located at the respective project websites Helm Install, Kubectl Install and Doctl Install
Create Your Kubernetes Cluster
You are able to create your cluster via doctl if you wish or via the DigitalOcean Control Panel. I’ll cover the DigitalOcean Control Panel here as there are a few things that we can get done that way to save time later. It’s also good to look at the prices of the resources you are installing. You can view those via cli as well too though.
– Access Your DigitalOcean Account here (Join DigitalOcean with above special $200 Credit for 60 Days referral links if you haven’t already and could benefit) https://cloud.digitalocean.com and login to your DigitalOcean account.
- Navigate the Top right Green "Create" button > Create Kubernetes Clusters.
- Choose the Region that you would like to deploy your Kubernetes Cluster to.
- Select the version of Kubernetes to run within your cluster (Current recommended 1.29.1)
- Choose Capacity of your Cluster
- Select fixed or autoscale - either is fine for this project, as our pods and storage pvc's will be able to autoscale if needed.
- Edit your pool name if you'd like, choose the node size (any is fine) and choose at least 2 nodes fixed or 2-3 nodes autoscale as you prefer.
Note: Surplus Autoscale nodes can help during upgrades of Kubernetes, as your cluster could spin up a few more nodes during an upgrade, transfer all services to those nodes and then fail back to upgraded nodes, then delete the autoscale nodes. Would cost a few cents to a dollar for our scale of WordPress on Kubernetes Cluster we have created here. Sometimes in the Kubernetes space, bad things can happen.
- Choose High Availability (For the control plane) if you want that - Not required but consider if you are running things you care about.
- Under Finalize edit the Kubernetes Cluster Name if you'd like.
- Click Create Cluster
The Cluster Creation takes a few minutes, so we’ll head off to Create our database cluster server while the Kubernetes cluster is being wired up and glued together.
Create Managed Database
We’ll create a managed MySQL (pronounced ‘my-essss-ql’ – not ‘my-seequel’ ;)) database based on the MySQL 8 Engine. Anyone saying that you don’t believe me about this pronunciation (I’ve met a few and I hate them all :), google it, I read the book way back then in the 90’s. NB: If you are Swedish it’s ‘mai eh skyoo el’, after all the Swedish not Americans invented MySQL :). It was worth writing this website to get that one off of my chest alone. haha. Never trust 100% but always reference your work: Some Facts
- Navigate under Manage > Databases
- Click Create Database
- Choose your region (you want the same as your Kubernetes Cluster uses).
- Choose the Database Engine > MySQL v8
- Select the size of your Database > Regular SSD > 1vcpu 1GB Ram and 10GB Storage defaults are fine.
- Under Finalize and Create, edit the MySQL Cluster Name and add Tags if you wish.
- Click Create Database Cluster
The Database Cluster will take a few minutes to create, so we will go ahead and complete the Get started wizard for our Kubernetes Cluster that we created just before.
- Navigate to Manage > Kubernetes
- Your Cluster should be ready and displaying the Green dot on the Kubernetes logo > Click your cluster name
- Click the Get Started Wizard
- Step 2 Connecting to Kubernetes > Select Automated > copy
- Run the command within your terminal that you use to manage your cluster
expected result
# doctl kubernetes cluster kubeconfig save <YOUR CLUSTER ID>
Notice: Adding cluster credentials to kubeconfig file found in "/root/.kube/config"
Notice: Setting current-context to <YOUR k8s CLUSTER NAME>
- Click Continue
- Practice the displayed commands if you would like, both doctl and kubectl to ensure you are connected and able to communicate.
- Click Great I'm Done!
Create Database and Users
Firstly, we’ll edit the trusted sources to allow incoming requests to the database cluster, from your Kubernetes cluster. Then we will create our database cluster database and database users.
- Navigate Manage > Databases
- Click your database cluster (it's a cluster even if it's only 1)
- Click 'Get Started'
- Restrict inbound connections
- Click the box and select your Kubernetes cluster and also anything else, that:
You may want your cli linux droplet to be able to perform mysqldump's or generally be able to have some access to your database.
At least choose your Kubernetes Cluster.
- Click Allow these connections only
- Review and copy your dbadmin details
- Click Continue
- Read (If you like) and Click Great I'm Done
- Click Users & Databases
- Fill in the username box to create a new database user
eg: wordpress_user
leave encryption set to MySQL 8+
- Scroll Down to Databases
Fill in the new database name
eg: wordpress
- Wait for Databases to be created but this won't take long, click the blue button 'Actions' > top right > Connection Details
Review the databases, vpc’s and users. Note that you should be choosing the ‘VPC Network’, the Database and the User for each database, rather than Public for connections from within your WordPress Kubernetes Cluster. Don’t Note all of those down, just remember how to get them. Don’t forget to choose all of the following when obtaining Credentials, database name, user name, password and the VPC network for your database, we’ll need them to be at hand in the near future, in order to create our secrets.
Configuring Your Cluster
We’ll take advantage of a couple of things that will make our Kubernetes ‘autoscale cluster ready WordPress on Kubernetes’ installation possible.
Firstly, we’ll be using the openEBS-nfs-provisioner Helm Chart, which allows having multiple nodes being able to share and read/write your fixed data (such as website files) to your pvc volumes, multiple nodes and pods that you can scale up or down on the fly and not require an external NFS server to facilitate that.
# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
do-block-storage (default) dobs.csi.digitalocean.com Delete Immediate true 28h
do-block-storage-retain dobs.csi.digitalocean.com Retain Immediate true 28h
do-block-storage-xfs dobs.csi.digitalocean.com Delete Immediate true 28h
do-block-storage-xfs-retain dobs.csi.digitalocean.com Retain Immediate true 28h
DigitalOcean Block Storage Volumes are mounted as read-write by a single node (RWO). Additional nodes cannot mount the same volume. The data content of a PersistentVolume can not be accessed by multiple Pods simultaneously.
Horizontal pod autoscaling (HPA) is used to scale the WordPress Pods in a dynamic StatefulSet hence WordPress requires a volume mounted as read-write by many nodes (RWX).
NFS is a commonly used solution to provide Read/Write RWX volumes on block storage. An NFS server can provide a PersistentVolumeClaim (PVC) in RWX mode so that multiple web applications can access the data in a clustered and shared fashion.
OpenEBS Dynamic NFS Provisioner allows users to create an NFS PV that sets up a new Kernel NFS instance for each PV on top of the user’s choice of backend storage. Let’s install it
# helm repo add openebs-nfs https://openebs.github.io/dynamic-nfs-provisioner
# helm repo update
Create the file openEBS-nfs-provisioner-values.yaml by copying the following 4 lines and pasting them into your bash shell, and then hit ENTER
cat << EOF > openEBS-nfs-provisioner-values.yaml
nfsStorageClass:
backendStorageClass: "do-block-storage"
EOF
Then ensure the file exists and contains the data as per this example.
# ls -la openEBS-nfs-provisioner-values.yaml
-rw-r--r-- 1 root root 57 Apr 2 06:11 openEBS-nfs-provisioner-values.yaml
# cat openEBS-nfs-provisioner-values.yaml
nfsStorageClass:
backendStorageClass: "do-block-storage"
Now Install the Chart, including our custom storage file, to include the DigitalOcean storage device name
# helm install openebs-nfs openebs-nfs/nfs-provisioner --version 0.11.0 \
--namespace openebs \
--create-namespace \
-f "openEBS-nfs-provisioner-values.yaml"
NAME: openebs-nfs
LAST DEPLOYED: Tue Apr 2 06:26:28 2024
NAMESPACE: openebs
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Thank you for installing nfs-provisioner 😀
Your release is named openebs-nfs and it's installed to namespace: openebs.
The OpenEBS NFSPV Provisioner has been installed check its status by running:
$ kubectl get pods -n openebs
For more information, visit our Slack at https://openebs.io/community or view
the documentation online at https://github.com/openebs/dynamic-nfs-provisioner/
Check our status
# kubectl get pods -n openebs
NAME READY STATUS RESTARTS AGE
openebs-nfs-nfs-provisioner-6d49f5b8bc-vjqjm 1/1 Running 0 5m24s
# helm ls -n openebs
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
openebs-nfs openebs 1 2024-04-02 06:26:28.989586441 +0000 UTC deployed nfs-provisioner-0.11.0 0.11.0
NFS provisioner requires a block storage device to create the disk capacity required for the NFS server. Next, you will configure the default Kubernetes Storage Class (do-block-storage) provided by DigitalOcean as the backend storage for the NFS provisioner. In that case, whichever application uses the newly created following Storage Class, can consume shared storage (NFS) on a DigitalOcean volume using OpenEBS NFS provisioner.
Next, create and inspect the following file sc-rwx-values.yaml, by copying all of the following blocks lines and hitting ENTER after pasting it into your bash shell.
cat << EOF > sc-rwx-values.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rwx-storage
annotations:
openebs.io/cas-type: nsfrwx
cas.openebs.io/config: |
- name: NSFServerType
value: "kernel"
- name: BackendStorageClass
value: "do-block-storage"
provisioner: openebs.io/nfsrwx
reclaimPolicy: Delete
EOF
Check the file
# ls -la sc-rwx-values.yaml
-rw-r--r-- 1 root root 333 Apr 2 06:40 sc-rwx-values.yaml
# cat sc-rwx-values.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rwx-storage
annotations:
openebs.io/cas-type: nsfrwx
cas.openebs.io/config: |
- name: NSFServerType
value: "kernel"
- name: BackendStorageClass
value: "do-block-storage"
provisioner: openebs.io/nfsrwx
reclaimPolicy: Delete
Now update the values using kubectl
# kubectl apply -f sc-rwx-values.yaml
storageclass.storage.k8s.io/rwx-storage created
Now Finally check our RWX Storage has been configured correctly
# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
do-block-storage (default) dobs.csi.digitalocean.com Delete Immediate true 29h
do-block-storage-retain dobs.csi.digitalocean.com Retain Immediate true 29h
do-block-storage-xfs dobs.csi.digitalocean.com Delete Immediate true 29h
do-block-storage-xfs-retain dobs.csi.digitalocean.com Retain Immediate true 29h
openebs-kernel-nfs openebs.io/nfsrwx Delete Immediate false 21m <<<<
rwx-storage openebs.io/nfsrwx Delete Immediate false 2m20s <<<<
Now we have the rwx-storage to dynamically provision shared volumes on top of DigitalOcean Block Storage
Install WordPress
Now we have most things in place, we can configure and install WordPress. We’ll begin by installing without tls support and skipping the WordPress install Wizard. That is required in order for us to utilise Cert-manager and deploy letsencrypt ssl certificates later. We will also deploy an Nginx ingress-controller. The reason that we will also install the Nginx ingress-controller, is to enable us to add several public facing apps/services and/or tls/ssl enabled applications without having to create a separate load balancer for each service. Load balancers cost $12 each, so an Ingress controller is a must if you run more than 1 site per cluster.. and also offers additional flexibility. The Nginx ingress-controller will create 1 initial load balancer but from then on, will manage ingress connections without the need for any further load balancers. You can can add more if you would like though.
Namespaces. You may notice in this tutorial, we haven’t created a namespace to install WordPress within, our helm installed apps have and will create their own as applicable. It’s good practice and easier to clean up old apps by installing them within their own namespace. For example, namespaces can be dev, tst or prd. Pretty common example, all separated and able to run apps within their own space. For now, we’ll just use default namespace and I’ll add some examples of how to create and set current namespaces at the end, or you could just use Mr Google and enquire in the meantime. You can practice with those to get further insight and create additional apps/environments in your own time.
To view all of our current namespaces you can list them out as follows (-A means display all namespaces). This list isn’t complete, try kubectl get pv, get pvc, get rs, get secrets and so it goes.. even add -o wide on the end of those to see more details.
# kubectl get ns -A
NAME STATUS AGE
default Active 30h
kube-node-lease Active 30h
kube-public Active 30h
kube-system Active 30h
openebs Active 126m
# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system cilium-grdmf 1/1 Running 0 30h
kube-system cilium-operator-6dd945f68b-dsxtc 1/1 Running 0 30h
kube-system cilium-vbngd 1/1 Running 0 30h
kube-system coredns-6857f5494d-gzcr8 1/1 Running 0 30h
kube-system coredns-6857f5494d-xpplr 1/1 Running 0 30h
kube-system cpc-bridge-proxy-jwvwg 1/1 Running 0 30h
kube-system cpc-bridge-proxy-t6t68 1/1 Running 0 30h
kube-system csi-do-node-8mlg9 2/2 Running 0 30h
kube-system csi-do-node-qmbqj 2/2 Running 0 30h
kube-system do-node-agent-n2jxv 1/1 Running 0 30h
kube-system do-node-agent-xb94v 1/1 Running 0 30h
kube-system hubble-relay-778856d87d-6h9xx 1/1 Running 0 30h
kube-system hubble-ui-776986f894-ncwmf 2/2 Running 0 30h
kube-system konnectivity-agent-6b7tm 1/1 Running 0 30h
kube-system konnectivity-agent-vldnp 1/1 Running 0 30h
kube-system kube-proxy-2kjz2 1/1 Running 0 30h
kube-system kube-proxy-76gmh 1/1 Running 0 30h
openebs openebs-nfs-nfs-provisioner-6d49f5b8bc-vjqjm 1/1 Running 0 129m
# kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 30h
kube-system hubble-peer ClusterIP 10.245.7.142 <none> 443/TCP 30h
kube-system hubble-relay ClusterIP 10.245.175.118 <none> 80/TCP 30h
kube-system hubble-ui ClusterIP 10.245.208.241 <none> 80/TCP 30h
kube-system kube-dns ClusterIP 10.245.0.10 <none> 53/UDP,53/TCP,9153/TCP 30h
Lets create our kustomization.yaml and wordpress-deployment.yaml files, that we will use to install WordPress and we include our secrets within kustomization.yaml. Modify the parts as necessary with your details obtained for your database instance/password and user. Don’t forget to select the VPC network connection string of your database.
Copy and paste the following block into your shell, hit ENTER and then edit the resulting kustomization.yaml file with your real password.
cat << EOF > kustomization.yaml
secretGenerator:
- name: mysql-pass-tutorial
literals:
- password=< YOUR DB USER PASSWORD >
resources:
- wordpress-deployment.yaml
EOF
Copy and paste the following block into your shell, hit ENTER and then edit the resulting wordpress-deployment.yaml file.
cat << EOF > wordpress-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: wordpress
namespace: default
labels:
app: wordpress
spec:
selector:
app: wordpress
ports:
- name: http
protocol: TCP
port: 80
- name: https
protocol: TCP
port: 443
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
namespace: default
name: wp-pv-claim-tutorial-1
labels:
app: wordpress
spec:
storageClassName: rwx-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
replicas: 2
selector:
matchLabels:
app: wordpress
tier: backend
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: wordpress
tier: backend
spec:
containers:
- image: wordpress:php8.3
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: < YOUR DATABASE HOST URL>:25060
- name: WORDPRESS_DB_NAME
value: < YOUR WORDPRESS DB NAME >
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass-tutorial
key: password
- name: WORDPRESS_DB_USER
value: < YOUR WORDPRESS DB USER >
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim-tutorial-1
EOF
Lets explain some of the yaml options.
name: wp-pv-claim-tutorial-1 – This is the name of your shared, static disk resource that is exported via nfs and thus able to be shared. Name uniquely within namespaces. It’s a pvc, so kubectl get pvc -A to view all.
storageClassName: rwx-storage – Our clustered nfs storage.
storage: 5Gi – Deploys a 5GB Static disk that will be shared. You can change 5 to suit your needs.
image: wordpress:php8.3 – This is the official WordPress Docker image version. See: hub.docker.com
NOTE: You should change the version of the WordPress release in the future as needed, but php8.3 will do for some time.
replicas: 2 – The number of wordpress pods you would like to run at once. A Cluster should have at least 2.
value: < ANYTHING WITHIN THESE BLOCKS NEEDS TO BE REPLACED >
example
value: < YOUR WORDPRESS DB NAME >
replace with
value: wordpress
It may seem like a lot of text to indulge in 1 go, but honestly just read line by line, it’s not exactly a thesis. Welcome back to the 90’s lol. Just watch your spaces when editing, yaml is a bit strict like that.
WIP… to be continued….
Published @ March 24, 2024 4:04 pm