Cloud Native 32 min read

Deploying a Highly Available WordPress Application on Kubernetes with Rolling Updates, HPA, and Ingress

This guide walks through deploying WordPress on Kubernetes with persistent storage, multi‑replica deployments, high‑availability configurations, health checks, pod anti‑affinity, PodDisruptionBudgets, QoS settings, rolling updates, automatic scaling via HPA, secure Secrets, and HTTPS exposure through Ingress, providing complete YAML manifests and command‑line steps.

DevOps Cloud Academy
DevOps Cloud Academy
DevOps Cloud Academy
Deploying a Highly Available WordPress Application on Kubernetes with Rolling Updates, HPA, and Ingress

In this tutorial we use a WordPress example to demonstrate how to achieve high availability, zero‑downtime rolling updates, persistent data, automatic scaling, and HTTPS access for a production‑grade application deployed on Kubernetes.

Principle

WordPress runs on PHP and MySQL, so we need a Docker image for WordPress (official wordpress:5.3.2-apache ) and a MySQL image. Both containers can be placed in the same Pod to share the network namespace, but for scalability we later separate them.

apiVersion: v1
kind: Namespace
metadata:
  name: kube-example

We then create a Deployment manifest for WordPress and MySQL:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: kube-example
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - name: wordpress
        image: wordpress:5.3.2-apache
        ports:
        - containerPort: 80
          name: wdport
        env:
        - name: WORDPRESS_DB_HOST
          value: localhost:3306
        - name: WORDPRESS_DB_USER
          value: wordpress
        - name: WORDPRESS_DB_PASSWORD
          value: wordpress
      - name: mysql
        image: mysql:5.7
        imagePullPolicy: IfNotPresent
        args:
        - --default_authentication_plugin=mysql_native_password
        - --character-set-server=utf8mb4
        - --collation-server=utf8mb4_unicode_ci
        ports:
        - containerPort: 3306
          name: dbport
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: rootPassW0rd
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wordpress
        - name: MYSQL_PASSWORD
          value: wordpress

Because the two containers share a network namespace, the WordPress container can reach MySQL via localhost:3306 . To expose the service we create a NodePort Service:

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  namespace: kube-example
spec:
  selector:
    app: wordpress
  type: NodePort
  ports:
  - name: web
    port: 80
    targetPort: wdport

After applying the manifests with kubectl apply -f , the Pods start and can be accessed through http://<nodeIP>:30892 . However, this single‑replica setup has a single‑point‑of‑failure and does not scale.

High Availability

We split WordPress and MySQL into separate Deployments so that WordPress can be scaled while MySQL remains a single instance (or later a StatefulSet). The MySQL Service is created to give WordPress a stable DNS name:

apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  namespace: kube-example
  labels:
    app: wordpress
spec:
  ports:
  - port: 3306
    targetPort: dbport
  selector:
    app: wordpress
    tier: mysql

The WordPress Deployment is updated to use replicas: 3 and to point to the MySQL Service DNS name wordpress-mysql:3306 :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: kube-example
  labels:
    app: wordpress
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - name: wordpress
        image: wordpress:5.3.2-apache
        ports:
        - containerPort: 80
          name: wdport
        env:
        - name: WORDPRESS_DB_HOST
          value: wordpress-mysql:3306
        - name: WORDPRESS_DB_USER
          value: wordpress
        - name: WORDPRESS_DB_PASSWORD
          value: wordpress

Now three WordPress Pods run on different nodes, providing redundancy.

Stability

To avoid single‑node failures we add pod anti‑affinity so that replicas are spread across hosts:

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      podAffinityTerm:
        topologyKey: kubernetes.io/hostname
        labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - wordpress

We also define a PodDisruptionBudget to ensure at most one replica is unavailable during voluntary disruptions:

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wordpress-pdb
  namespace: kube-example
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wordpress
      tier: frontend

Readiness probes are added so that a Pod is removed from the Service only after it is truly ready to receive traffic:

readinessProbe:
  tcpSocket:
    port: 80
  initialDelaySeconds: 5
  periodSeconds: 5

QoS and Resource Management

Kubernetes classifies Pods into Guaranteed, Burstable, and Best‑Effort based on CPU/memory requests and limits. For WordPress we set explicit limits and requests to achieve a Guaranteed QoS:

resources:
  limits:
    cpu: 200m
    memory: 100Mi
  requests:
    cpu: 200m
    memory: 100Mi

These settings influence OOM scores and eviction order.

Rolling Updates

The Deployment controller performs rolling updates by default. We can fine‑tune the strategy:

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

To achieve true zero‑downtime we add a preStop hook that sleeps long enough for the Service endpoints to be updated before the container exits:

lifecycle:
  preStop:
    exec:
      command: ["/bin/bash", "-c", "sleep 20"]

Horizontal Pod Autoscaling (HPA)

We enable automatic scaling based on CPU usage:

kubectl autoscale deployment wordpress --namespace kube-example --cpu-percent=20 --min=3 --max=6

During a load test with Fortio the replica count grows to six and shrinks back after the load subsides.

Security

Sensitive data such as the database password is stored in a Kubernetes Secret and referenced in the Deployment:

kubectl create secret generic wordpress-db-pwd --from-literal=dbpwd=wordpress -n kube-example
env:
- name: WORDPRESS_DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: wordpress-db-pwd
      key: dbpwd

Persistence

We create a PVC for MySQL using the rook-ceph-block StorageClass and a PVC for WordPress using a CephFS StorageClass ( csi-cephfs ) that supports ReadWriteMany for multiple replicas.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: kube-example
spec:
  storageClassName: rook-ceph-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-pvc
  namespace: kube-example
spec:
  storageClassName: csi-cephfs
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 2Gi

The Deployments mount these PVCs at /var/www/html (WordPress) and /var/lib/mysql (MySQL).

Ingress (HTTPS)

For production exposure we replace the NodePort with a Traefik IngressRoute that terminates TLS using ACME and redirects HTTP to HTTPS:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: wordpress-https
  namespace: kube-example
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`wordpress.qikqiak.com`)
    kind: Rule
    services:
    - name: wordpress
      port: 80
  tls:
    certResolver: ali
    domains:
    - main: "*.qikqiak.com"
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: kube-example
spec:
  redirectScheme:
    scheme: https
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: wordpress-http
  namespace: kube-example
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`wordpress.qikqiak.com`)
    kind: Rule
    services:
    - name: wordpress
      port: 80
    middlewares:
    - name: redirect-https

After applying these resources, the domain wordpress.qikqiak.com resolves to a secure WordPress site, completing a production‑ready deployment.

high availabilityKubernetesIngressWordPressrolling updatePersistent StorageHorizontal Pod Autoscaling
DevOps Cloud Academy
Written by

DevOps Cloud Academy

Exploring industry DevOps practices and technical expertise.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.