Provision infrastructure in AWS and deploy WordPress with MariaDB in Kubernetes Cluster using Helm charts.
There are six sections to follow and implement as shown below:
-
Provision AWS Infrastructure
-
Set up Kubernetes cluster and NFS srver
-
Create Dynamic Persistent Volume Provisioner
-
Install Wordpress and MariaDB with Helm charts
-
Connect to Wordpress and MariaDB
-
Testing Data Persistence
-
Securing Traffic with Let's Encrypt Certificates
-
Enable WordPress monitoring metrics
Clone this repo to local machine
cd /
git clone git@github.com:odennav/wordpress-mariadb-helm.git
cd terraform-kubernetes-aws-ec2/terraform-manifest
Provision AWS resources
Execute these terraform commands sequentially on your local
machine to create the AWS infrastructure.
cd terraform-manifest
Initialize the terraform working directory
terraform init
Validate the syntax of the terraform configuration files
terraform validate
Create an execution plan that describes the changes terraform will make to the infrastructure.
terraform plan
Apply the changes described in execution plan
terraform apply -auto-approve
Check AWS console for instances created and running
SSH Access
Use .pem key from AWS to SSH into the public EC2 instance.
IPv4 address of public EC2 instance will be shown in terraform outputs.
ssh -i private-key/terraform-key.pem ec2-user@<ipaddress>
Its possible to use public EC2
instance as a jumpbox to securely SSH into private EC2 instances within the VPC.
Change password of root user for public EC2instance control-dev
sudo passwd
Switch to root user
Update apt
package manager
cd /
apt update -y
apt upgrade -y
Confirm Git was installed
git --version
Confirm terraform-key
was transferred to public EC2
instance by null provisioner
Please note if .pem
key not found, copy it manually.
Also key can be copied to another folder because it will be deleted if node is restarted or shutdown
ls -la /tmp/terraform-key.pem
cp /tmp/terraform-key.pem /
Change permissions of terraform-key.pem
file
chmod 400 /tmp/terraform-key.pem
Clone this repo to /
directory in dev-Control
node
cd /
git clone git@github.com:odennav/terraform-kubernetes-aws-ec2.git
Install Ansible in dev-Control
node
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
Bootstrap EC2 Private Instances
All the nodes need to be bootstrapped.
Once the bootstrap is complete, you will only be able to log in as odennav-admin
.
Confirm SSH access to k8snode-1
ssh -i /tmp/terraform-key.pem odennav-admin@<k8snode-1 ipv4 address>
To return to dev-Control
, type exit
and press Enter
or use Ctrl+D
.
Confirm SSH access to k8snode-2
ssh -i /tmp/terraform-key.pem odennav-admin@<k8snode-2 ipv4 address>
Confirm SSH access to k8snode-3
ssh -i /tmp/terraform-key.pem odennav-admin@<k8snode-3 ipv4 address>
Now you can now bootstrap them
cd ../bootstrap
ansible-playbook bootstrap.yml --limit k8s_master,k8s_node
Set up Kubernetes Cluster
Your kube nodes are now ready to have a Kubernetes cluster installed on them.
Execute playbooks in this particular order:
cd ../k8s
ansible-playbook k8s.yml --limit k8s_master
ansible-playbook k8s.yml --limit k8s_node
Check status of your nodes and confirm they're ready
kubectl get nodes
Bootstrap the NFS Server
Bootstrap this server.
Once the bootstrap is complete you will only be able to log in as odennav-admin
cd ../ansible/bootstrap
ansible-playbook bootstrap.yml --limit nfs_server
Create NFS share
cd ../nfs
ansible-playbook nfs.yml
/pv-share/
directory is created and made available to all nodes, but its not mounted yet by the nodes
Login to 1st node in cluster
ssh -i /tmp/terraform-key.pem odennav-admin@<k8snode-1 ipv4 address>
Confirm nfs client is installed
dpkg -l | grep nfs-common
If not available:
sudo apt install nfs-common
Create shared directory and mount nfs share
This directory will be mounted to /pv-share/
created in NFS server.
cd /
sudo mkdir /shared
sudo chmod 2770 /shared
sudo mount -t nfs <db-1 ipv4 address>:/pv-share /shared
Confirm NFS share is implemented
Make a test file in /shared/
dir on the cluster node. It should be present in /pv-share/
dir on nfsserver.
sudo touch test-k8smaster
Repeat process for the other kubernetes cluster nodes.
Persistent storage is required to store very important data and avoiding total loss of data.
Kubernetes deals with pods that have short life span, they could be stopped at any time and restarted on a different node, causing the container's filesystem to be lost with the pod.
This is not reliable, hence the need for filesystem that is available and accessibleirrespective of pod actions.
PV is configured to use different types of storage technology such as:
- CephFS
- iSCSI
- NFS
- Azure File
We will use Network File System (NFS) which is a way of sharing a centralised filesystem across multiple nodes.
Although persistent storage is managed by kubernetes in the cluster, the actual storage is on nfs server which is not part of the kubernetes cluster and it is on different subnet.
Creating a PV within your cluster, tells Kubernetes that pods should have access to persistent storage that will outlive the pod and possibly the cluster itself.
We want pods to access the PV created. To do this, a Persistent Volume Claim or PVC is required.
When PVC is created within a namespace, only pods in that namespace can mount it. However, it can be bound to any PV as these are not namespaced.
It is possible that Kubernetes cannot bind the PVC to a valid PV and that the PVC remains unbound until a PV becomes available.
This will lead to instances of pods in Pending
state instead of Running
state and PVC having Unbound
status.
Here access to PVC in the pod is done by mounting the storage as a volume within the container.
Once PVC is mounted by the pod, the application within the Pod’s container(s) now have access to the persistent storage.
Upon reschedule of pod(s), it will be reconnected to the same PV and will have access to the data it was using before it died, even if this is on another node.
Login to k8smaster
and Confirm Helm
is installed
Helm is an effective package manager for kubernetes
helm version
If not installed
sudo snap install helm --classic
Confirm persistent volume provisioner installed
kubectl get all -n nfs-provisioner
kubectl get sc -n nfs-provisioner
This should show dynamic provisioner setup and ready.
If PV provisioner
not installed, do it manually:
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
helm install -n nfs-provsioner --create-namespace nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=<db-1 ipv4 address> --set nfs.path=/pv-share
Setup PVC for Wordpress
Our PV provisioner installed will dynamically provision PVs when PVCs are created.
We'll use kubens to switch between kubernetes namespaces.
sudo snap install kubectx --classic
kubens --version
Create wordpress namespace
Assuming you've kubectl
installed along with kubernetes cluster.
kubectl create namespace wordpress
kubens wordpress
Create PVC request on k8smaster
kubectl create -f wp-pvc.yaml
Configure Wordpress Parameters
Match this parameters and replace the values, so we have an account to access Wordpress
-
wordpressUsername
-
wordpressPassword
-
wordpressEmail
-
wordpressFirstName
-
wordpressLastName
-
wordpressBlogName
-
wordpressScheme
sed -i '/wordpressUsername: user/wordpressUsername: odennav/' values.yaml
sed -i '/wordpressPassword: ""/wordpressPassword: odennav/' values.yaml
sed -i '/wordpressEmail: user@example.com/wordpressEmail: contact@odennav.com/' values.yaml
sed -i '/wordpressFirstName: FirstName/wordpressFirstName: odennav/' values.yaml
sed -i '/wordpressLastName: LastName/wordpressLastName: odennav/' values.yaml
sed -i '/wordpressBlogName: User's Blog!/wordpressBlogName: The Odennav Blog!/' values.yaml
sed -i '/wordpressScheme: http/wordpressScheme: https/' values.yaml
Configure Persistence and Database Parameters
Enable persistence using persistence volume claims and peristence volume access modes.
Match and replace values for persistence and database parameters below:
-
persistence.storageClass
-
persistence.existingClaim
-
mariadb.primary.persistence.storageClass
-
mariadb.auth.username
-
mariadb.auth.password
sed -i '/persistence:/,/volumePermissions:/ {/storageClass: ""/s/""/nfs-client}' values.yaml
sed -i '/persistence:/,/volumePermissions:/ {/existingClaim: ""/s/""/pvc-wordpress}' values.yaml
sed -i '/mariadb:/,/externalDatabase:/ {/storageClass: ""/s/""/nfs-client}' values.yaml
sed -i '/mariadb:/,/externalDatabase:/ {/username: bn_wordpress/s/bn_wordpress/odennav_wordpress}' values.yaml
sed -i '/mariadb:/,/externalDatabase:/ {/password: ""/s/""/odennav}' values.yaml
Configure PVC Access Modes
To access the /admin
portal and enable WordPress scalability, a ReadWriteMany
Persistent Volume Claim (PVC) is required.
-
persistence.accessModes
-
persistence.accessMode
sed -i 's/ReadWriteOnce/ReadWriteMany/g' values.yaml
Configure Replica Count
Number of Wordpress replicas to deploy
- replicaCount
sed -i '/replicaCount: 1/replicaCount: 3/' values.yaml
Configure Auto Scaling
Enable horizontal scalability of pod resources for Wordpress when traffic load is increased
- autoscaling.enabled
sed -i '/autoscaling:/,/metrics:/ {/enabled: false/s/"false"/true}' values.yaml
Configure htaccess
For performance and security reasons, configure Apache with AllowOverride None and prohibit overriding directives with htaccess files
- allowOverrideNone
sed -i '/allowOverrideNone: false/allowOverrideNone: true/' values.yaml
Install Wordpress and MariaDB
Use Helm charts to bootstrap wordpress and mariadb deployment on kubernetes cluster.
helm repo update
Install the chart with release-name,my-wordpress
helm install -f values.yml my-wordpress oci://registry-1.docker.io/bitnamicharts/wordpress
After installation, instructions will be printed to stdout.
Add Wordpress Secrets
We'll add wordpress credentials as a kubernetes secret.
From stdout above, Export the wordpress password to environment variable, WORDPRESS_PASSWORD
export WORDPRESS_PASSWORD=$(kubectl get secret --namespace wordpress my-wordpress -o jsonpath="{.data.wordpress-password}" | base64 -d)
Then create secret:
kubectl create secret generic db-user-pass \
--from-literal=username=wordpress \
--from-literal=password=$WORDPRESS_PASSWORD
Delete environment variable, to prevent non-admin users viewing it's value.
unset WORDPRESS_PASSWORD
Confirm PVCs are bound
This confirms the applications installed will have access to persistent storage
kubectl get pvc -n wordpress
Check service created
kubectl get svc -n wordpress
HTTP access to Wordpress pods
Export IPv4 address and port
export NODE_PORT=$(kubectl get --namespace wordpress -o jsonpath="{.spec.ports[0].nodePort}" services my-wordpress)
export NODE_IP=$(kubectl get nodes --namespace wordpress -o jsonpath="{.items[0].status.addresses[0].address}")
echo "WordPress URL: http://$NODE_IP:$NODE_PORT/"
echo "WordPress Admin URL: http://$NODE_IP:$NODE_PORT/admin"
HTTP request to Wordpress site
curl http://$NODE_IP:$NODE_PORT/
Set up a port forward from the Service to the host on the master node.
kubectl port-forward — namespace wordpress
Set up a port forward from the host machine to the development machine.
ssh -L 54321:localhost:5432 k8snode-1@<k8snode-1 ipv4-address> -i /tmp/terraform-key.pem
Check pods running
Confirm mariadb pods are in 'Ready' state
kubectl get pods -n wordpress
Delete pods
kubectl delete pod <pod name> -n wordpress
Restart port forwards
kubectl port-forward — namespace wordpress
ssh -L 54321:localhost:5432 k8snode-1@<k8snode-1 ipv4-address> -i ~/.ssh/id_rsa
Upon deletion of pod, another instance is automatically scheduled.
You'll still be able to access your database with data still intact.
The Bitnami WordPress Helm chart includes native support for Ingress routes and certificate management via cert-manager. This simplifies TLS configuration by enabling the use of certificates from various providers, such as Let's Encrypt.
Create namespace for ingress controller Then switch to ingress-nginx namespace
kubectl create namespace ingress-nginx
kubens ingress-nginx
Pull the chart sources:
helm pull oci://ghcr.io/nginxinc/charts/nginx-ingress --untar --version 1.2.0
Change working directory to nginx-ingress:
cd nginx-ingress
Upgrade the CRDs:
kubectl apply -f crds/
Install the chart with the release name, ingress-nginx
helm install ingress-nginx .
Next, check if the Helm installation was successful by running command below:
helm ls -n ingress-nginx
Configure DNS
with a domain
that you own and create the domain A
record for the wordpress site.
Next, you will add the required A
record for the wordpress application.
Please note, you need to identify the load balancer external IP
created by the nginx
deployment:
kubectl get svc -n ingress-nginx
First, add the jetstack
Helm repo, and list the available charts:
helm repo add jetstack https://charts.jetstack.io
helm repo update jetstack
Next, install Cert-Manager using Helm:
helm install cert-manager jetstack/cert-manager --version 1.8.0 \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
Finally, check if Cert-Manager installation was successful by running below command:
helm ls -n cert-manager
The output looks similar to STATUS
column should print deployed
:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
cert-manager cert-manager 1 2024-04-08 18:02:08.124264 +0300 EEST deployed cert-manager-v1.15.0 v1.15.0
A cluster issuer is required first, in order to obtain the final TLS certificate. Open and inspect the cluster-manifest/letsencrypt-issuer-values.yaml
file provided in this repository:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: wordpress
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: odennav@gmail.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# Secret resource used to store the account's private key.
name: prod-issuer-account-key
# Add a single challenge solver for Cloudflare
solvers:
- dns01:
cloudflare:
email: odennav@gmail.com
apiTokenSecretRef:
name: cloudflare-token-secret
key: cloudflare-token
selector:
dnsZones:
- <YOUR DOMAIN> # odennav.com
Apply via kubectl:
cd wordpress-mariadb-helm/
kubectl apply -f cluster-manifest/letsencrypt-issuer-values.yaml
Create a certificate for the wordpress
namespace
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: local-odennav-com
namespace: wordpress
spec:
secretName: local-odennav-com-tls
issuerRef:
name: prod-issuer-acount-key
kind: ClusterIssuer
commonName: "*.odennav.com"
dnsNames:
- "<YOUR DOMAIN>" # odennav.com
- "*.<YOUR DOMAIN>" # *.odennav.com
To secure WordPress traffic, open the helm values.yaml
file in the cluster-manifest
directory and add the following:
# Enable ingress record generation for WordPress
ingress:
enabled: true
certManager: true
tls:
secretName: local-odennav-com-tls
hostname: <YOUR_WORDPRESS_DOMAIN_HERE>
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
extraTls:
- hosts:
- <YOUR_WORDPRESS_DOMAIN_HERE>
Upgrade via helm
:
helm upgrade my-wordpress bitnami/wordpress \
--namespace wordpress \
--version 22.0.0 \
--timeout 10m0s \
--values /wordpress-mariadb-helm/cluster-manifest/values.yaml
This automatically creates a certificate through cert-manager. You can then verify that you've successfully obtained the certificate by running the following command:
kubectl get certificate -n wordpress
If successful, the output's READY column reads True:
NAME READY SECRET AGE
local-odennav-com-tls True local-odennav-com-tls 24h
Now, you can access WordPress using the domain configured earlier. You will be guided through the installation
process.
In this section, you will learn how to enable metrics for monitoring your WordPress instance.
First, open the wordpress-values.yaml
created earlier in this tutorial, and set metrics.enabled
field to true
.
# Prometheus Exporter / Metrics configuration
metrics:
enabled: true
Apply changes using Helm:
helm upgrade my-wordpress bitnami/wordpress \
--create-namespace \
--namespace wordpress \
--version 22.0.0 \
--timeout 10m0s \
--values /wordpress-mariadb-helm/cluster-manifest/values.yaml
Next, port-forward the wordpress service to inspect the available metrics:
kubectl port-forward --namespace wordpress svc/wordpress-metrics 9150:9150
Browse to localhost:9150/metrics
to see all WordPress metrics.
Finally, you need to configure Grafana and Prometheus to visualise metrics exposed by your new WordPress instance.
Plugins serve as the foundational components of your WordPress site, enabling crucial functionalities ranging from contact forms and SEO enhancements to site speed optimization, online store creation, and email opt-ins. Whatever your website requirements may be, plugins provide the necessary tools to fulfill them.
Here is a curated list of recommended plugins:
-
LiteSpeed Cache: is a comprehensive site acceleration tool, offering an exclusive server-level cache and a suite of optimization features to enhance website performance.
-
Contact Form by WPForms: enables you to design visually appealing contact forms, feedback forms, subscription forms, payment forms, and various other types of forms for your website.
-
MonsterInsights: is regarded as the premier Google Analytics solution for WordPress. It facilitates seamless integration between your website and Google Analytics, providing detailed insights into how visitors discover and interact with your site.
-
Query Monitor: serves as a developer tools panel for WordPress. It allows for debugging of database queries, PHP errors, hooks, and actions.
-
All in One SEO: aids in driving more traffic from search engines to your website. While WordPress is inherently SEO-friendly, this plugin empowers you to further enhance your website traffic by implementing SEO best practices.
-
SeedProd: This plugin stands out as the premier drag-and-drop page builder for WordPress. It simplifies the process of customizing your website design and crafting unique page layouts effortlessly, eliminating the need for manual code writing.
-
UpdraftPlus: Facilitates backups and restoration. Backup your files and database backups into the cloud and restore with a single click.
For more plugins, visit https://wordpress.org/plugins/
Content Delivery Network (CDN) is a straightforward method to accelerate a WordPress website.
A CDN consists of servers strategically positioned to optimize the delivery of media files, thereby enhancing the loading speed of web pages.
Many websites encounter latency issues when their visitors are located far from the server location.
By utilizing a CDN, content delivery can be expedited by relieving the web server of the task of serving static content such as images, CSS, JavaScript, and video streams.
Additionally, caching static content minimizes latency. Overall, CDN serves as a dependable and effective solution for optimizing websites and enhancing the global user experience.
Cloudflare is a renowned provider of content delivery network (CDN), DNS, DDoS protection, and security services. Leveraging Cloudflare can significantly accelerate and bolster the security of your WordPress site, making it an excellent solution for website optimization and protection.
Cloudflare account is required for this configuration. Visit the Cloudflare website and signup for a free account.
Below are the steps to configure Cloudflare for your WordPress site:
-
Log in to the Cloudflare dashboard using your account credentials and click on the
+ Add Site
button. -
Enter your WordPress site's domain and click
Add Site
. -
Choose the
Free
plan and clickGet Started
. -
From
Review DNS records
and clickAdd record
. Add anA
record with your desired name and theIPv4 address
of your cloud provider load balancer. ClickContinue
. -
Follow instructions to change your domain registrar's nameservers to Cloudflare's nameservers.
-
After updating nameservers, click
Done, check nameservers
. -
Cloudflare may offer configuration recommendations; you can skip these for now by clicking
Skip recommendations
.
An email will confirm when your site is active on Cloudflare. Use the Analytics page in your Cloudflare account to monitor web traffic on your WordPress site.
If you're taking the option to remove both applications, implement the following:
Delete PVC
This removes and unbounds PVC from PV.
kubectl delete -f pg-pvc.yml
Delete Namespaces
kubectl delete ns wordpress
kubectl delete ns ingress-nginx
Destroy AWS resources
From your local machine:
terraform destroy
Enjoy!