Orchestrating High-Availability WordPress with Kubernetes and AWS EKS: A Deep Dive into Load Balancing, Persistent Storage, and Auto-Scaling
Kubernetes Cluster Setup on AWS EKS
Establishing a robust, high-availability WordPress deployment necessitates a well-configured Kubernetes cluster. AWS Elastic Kubernetes Service (EKS) simplifies this by abstracting away the complexities of managing the Kubernetes control plane. We’ll focus on the essential components for our WordPress setup: networking, IAM roles, and node group configuration.
First, ensure you have the AWS CLI and kubectl configured to interact with your AWS account. The EKS cluster creation itself can be initiated using the AWS Management Console or programmatically via the AWS CLI or SDKs. For this guide, we’ll assume a cluster named wordpress-cluster is already provisioned.
Crucially, your EKS cluster needs appropriate IAM permissions to interact with other AWS services, particularly for load balancing and persistent storage. When creating an EKS cluster, EKS automatically creates an IAM role for the control plane. You’ll also need an IAM role for your worker nodes. This role requires policies like AmazonEKSWorkerNodePolicy, AmazonEC2ContainerRegistryReadOnly, and AmazonEKS_CNI_Policy.
For worker nodes, we’ll utilize EKS Managed Node Groups. This approach simplifies node provisioning, scaling, and patching. When creating a managed node group, specify the Kubernetes version matching your cluster, instance types suitable for your WordPress workload (e.g., m5.large or c5.large for a balance of CPU and memory), and the desired number of nodes. Ensure the subnet configuration for your node group spans multiple Availability Zones for high availability.
Ingress Controller and AWS Load Balancer Integration
To expose our WordPress application to the internet, we need an Ingress controller. The AWS Load Balancer Controller is the recommended solution for EKS, as it provisions and manages AWS Application Load Balancers (ALBs) or Network Load Balancers (NLBs) based on Kubernetes Ingress resources. This controller integrates seamlessly with AWS IAM and VPC resources.
Installation typically involves applying a Kubernetes manifest. First, you need to create an IAM OIDC provider for your EKS cluster and then an IAM role for the controller with the necessary permissions. The AWS documentation provides detailed steps for this. Once the IAM role is set up, you can deploy the controller using Helm or by applying its YAML manifests.
Here’s a typical Helm installation command:
helm upgrade --install aws-load-balancer-controller eks/aws-load-balancer-controller \ --namespace kube-system \ --set clusterName=wordpress-cluster \ --set serviceAccount.create=false \ --set serviceAccount.name=aws-load-balancer-controller \ --set region=us-east-1 \ --set vpcId=vpc-xxxxxxxxxxxxxxxxx
After installation, you’ll define your WordPress Ingress resource. This resource tells the AWS Load Balancer Controller how to route external traffic to your WordPress service. We’ll configure it to use an ALB, which is suitable for HTTP/S traffic and offers features like path-based routing and SSL termination.
Example Ingress resource:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
namespace: default
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/your-certificate-id
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
rules:
- host: your-wordpress-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80
Persistent Storage for WordPress and MySQL
WordPress requires persistent storage for its uploads, themes, and plugins. Similarly, the MySQL database needs durable storage. AWS Elastic Block Store (EBS) volumes are a natural fit for this within EKS. We’ll leverage Kubernetes PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs) to manage this storage.
The aws-ebs-csi-driver is the modern and recommended way to provision EBS volumes in EKS. It provides dynamic provisioning of EBS volumes as PersistentVolumes when PVCs are created. Ensure this CSI driver is installed in your cluster. It’s often installed by default with recent EKS versions, but verification is prudent.
We’ll define StorageClasses that specify the type of EBS volume (e.g., gp3 for general purpose, io2 for high IOPS) and the desired performance characteristics. For high availability, consider using EBS volumes that are replicated across Availability Zones if your application architecture demands it, though standard EBS volumes are zonal. For true multi-AZ resilience at the storage layer, consider AWS EFS or a distributed database solution.
Example StorageClass for GP3 EBS volumes:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ebs-gp3 provisioner: ebs.csi.aws.com parameters: type: gp3 fsType: ext4 reclaimPolicy: Retain volumeBindingMode: WaitForFirstConsumer
Now, we define PVCs for both WordPress and MySQL. The wordpress-pvc will be mounted by the WordPress pods, and mysql-pvc by the MySQL pods.
WordPress PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-gp3
resources:
requests:
storage: 20Gi
MySQL PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-gp3
resources:
requests:
storage: 50Gi
Deploying WordPress and MySQL with High Availability
We’ll deploy WordPress and MySQL as separate Deployments within Kubernetes. For MySQL, we’ll configure a StatefulSet to ensure stable network identifiers and ordered deployment/scaling, which is crucial for databases. For WordPress, a Deployment is sufficient, allowing for easy rolling updates and scaling.
MySQL StatefulSet configuration:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: default
spec:
serviceName: "mysql"
replicas: 1 # For HA, consider a managed RDS instance or a clustered DB solution
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-persistent-storage
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "ebs-gp3"
resources:
requests:
storage: 50Gi
Note on MySQL HA: A single MySQL pod in a StatefulSet with EBS is not inherently highly available. For true database HA, consider using AWS RDS with Multi-AZ deployment or a Kubernetes-native distributed database solution like Percona XtraDB Cluster or Vitess. For simplicity in this example, we use a single pod, but production systems demand more robust database HA.
WordPress Deployment configuration:
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: default
spec:
replicas: 3 # Scale this based on traffic
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:latest
ports:
- containerPort: 80
env:
- name: WORDPRESS_DB_HOST
value: "mysql.default.svc.cluster.local"
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: wordpress-secret
key: db-user
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: wordpress-secret
key: db-password
- name: WORDPRESS_DB_NAME
value: "wordpress"
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wordpress-pvc
We also need Kubernetes Services to expose these applications within the cluster and to the Ingress controller. A ClusterIP service for MySQL allows WordPress pods to connect to it. A ClusterIP service for WordPress will be targeted by the Ingress.
MySQL Service:
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: default
spec:
selector:
app: mysql
ports:
- protocol: TCP
port: 3306
targetPort: 3306
clusterIP: None # For StatefulSet, headless service is common
WordPress Service:
apiVersion: v1
kind: Service
metadata:
name: wordpress-service
namespace: default
spec:
selector:
app: wordpress
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
Implementing Auto-Scaling for WordPress
To handle fluctuating traffic demands, Kubernetes provides Horizontal Pod Autoscaler (HPA). The HPA automatically scales the number of pods in a Deployment or StatefulSet based on observed metrics like CPU utilization or custom metrics.
For HPA to function, your Kubernetes nodes must have the metrics-server deployed. EKS typically includes this or makes it easy to install. Once available, you can define an HPA resource targeting your WordPress Deployment.
Example HPA configuration for WordPress:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: wordpress-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: wordpress
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
This HPA will monitor the average CPU utilization across all WordPress pods. If it exceeds 70% for a sustained period, the HPA will trigger scaling events, increasing the number of WordPress pods up to a maximum of 10. Conversely, if CPU utilization drops, it will scale down to the minimum of 3 pods.
For more advanced scaling scenarios, such as scaling based on custom metrics (e.g., requests per second, queue length), you can integrate with solutions like Prometheus and the Prometheus Adapter for Kubernetes, which can then expose these custom metrics to the HPA.
Monitoring, Logging, and Health Checks
A production-ready WordPress deployment on Kubernetes requires robust monitoring, logging, and health checking. This ensures the application remains available, performs optimally, and issues are detected and resolved quickly.
Health Checks: Kubernetes Liveness and Readiness probes are essential. Liveness probes determine if a container is running and healthy; if not, Kubernetes will restart it. Readiness probes determine if a container is ready to serve traffic; if not, Kubernetes will remove it from service endpoints.
Add these to your WordPress container spec:
livenessProbe:
httpGet:
path: /wp-cron.php # Or a custom health check endpoint
port: 80
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /wp-cron.php # Or a custom health check endpoint
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Monitoring: Deploy Prometheus and Grafana for comprehensive monitoring. Prometheus can scrape metrics from your WordPress pods (e.g., via `wp-cli` or custom exporters), the MySQL database, and Kubernetes itself. Grafana can then visualize these metrics in dashboards.
Logging: A centralized logging solution is critical. Deploy a log aggregation agent like Fluentd or Fluent Bit as a DaemonSet on your worker nodes. These agents collect logs from all containers and forward them to a central store such as Amazon CloudWatch Logs, Elasticsearch, or Loki.
Example Fluent Bit configuration snippet for outputting to CloudWatch Logs:
[OUTPUT]
Name cloudwatch
Match kube.*
region us-east-1
log_group_name wordpress-eks-logs
log_stream_prefix wordpress-
auto_create_stream true
role_arn arn:aws:iam::123456789012:role/FluentBitCloudWatchRole
Ensure the IAM role used by Fluent Bit has permissions to write to CloudWatch Logs.