Deploy Wordpress Multisite on Kubernetes

Published: August 23, 2020 by Author's Photo Shane Rainville | Reading time: 6 minutes
Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

In this tutorial you will learn how to deploy WordPress on Kubernetes with a multisite configuration. WordPress Multisite allows blog owners to host multiple sites from a single installation. This allows you manage all sites through a single pane of glass, minimizing your infrastructure spend and operational costs.

WordPress Configurations

We’re going to use the official WordPress Docker image in this tutorial. WordPress offers images based on Apache and FPM, but we will be using the Apache image as a demonstration.

WordPress Docker images are configurable by setting environment variables. These environment variables must be set at container run-time to be available to WordPress.

Database Configuration

The primary configuration of a WordPress installation:

  • WORDPRESS_DB_HOST
  • WORDPRESS_DB_USER
  • WORDPRESS_DB_PASSWORD

An optional, but strongly recommended, configuration is the table prefix.

  • WORDPRESS_DB_PREFIX

Multisite Configuration

Multisite does not have a dedicated environment variable. Instead, we will need to enable it via the WORDPRESS_CONFIG_EXTRA environment variable.

WORDPRESS_CONFIG_EXTRA="define('WP_ALLOW_MULTISITE'), true);"

Storing Configs in Kubernetes

Secret Manifest

The database username and password should not be stored in clear text. Kubernetes has a secret resource that is specifically for storing sensitive information.

Notice that we are not creating a YAML file hosting our secrets. It is strongly recommended you DO NOT store secret manifests in version control. In this tutorial, we are creating the secret from command-line to prevent a manifest from accidently being added to our Git repository.
kubectl create secret generic wordpress-secret \
--from-literal=wordpress.db.user="wordpress_user" \
--from-literal=wordpress.db.password='my-super-secret-password'

ConfigMap Manifest

Information that is not sensitive can be stored in a configMap. Let’s create a configMap to hold our database name, database prefix, and extra configurations.

Create a new manifest file named wordpress-configmap.yaml and add the following contents:

apiVersion: v1
kind: ConfigMap
metadata:
  name: wordpress-configmap
data:
  wordpress.db.host: mysql.host:3306
  wordpress.db.name: wordpress_site
  wordpress.db.prefix: blog_
  wordpress.config.extra: |
    define('WP_ALLOW_MULTISITE', true);

To create the ConfigMap in your Kubernetes cluster use the kubectl apply command.

kubectl apply -f wordpress-configmap.yaml

Persistent Storage

Containers are ephemeral by design. This is problematic for long lived applications like WordPress, who’s state changes frequently with uploaded content, updated themes, and updated plugins. To ensure our state persists beyond the life of the container running our site, we will need to create and mount a storage volume.

Create a PersistentVolumeClaim manifest file named wordpress-pvc.yaml and add the following contents.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Create the PersistentVolumeClaim in your cluster by applying the manifest file.

kubectl apply -f wordpress-pvc.yaml

Service Account

Every Kubernetes Pod has an identity assigned to it. By default, if not service account is specified, a pod will be assigned the default service account as its identity. As a best practice, every service you run in Kubernetes should be assigned it own service account.

Create a new service account for your WordPress pods using the following as an example.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: wordpress-multisite-sa
automountServiceAccountToken: false

Tokens

Every account in Kubernetes is assigned a unique token, which used by the API server to authenticate RESTful API requests. Tokens are stored as Secrets in the namespace a Service Account is created in, and it is mounted as file of every pod that uses the service account.

If a pod has no need to make API requests directly to Kubernetes, which is the case for nearly all workloads, the token should not be automatically mounted. In the example ServiceAccount manifest above, we’ve set automountServiceAccountToken to false, so that every deployment or pod that uses the wordpress-multisite service account does not mount the token automatically.

WordPress Multisite Deployment Manifest

Finally, with our configuration information stored in secrets and configMaps, and a persistentVolumeClaim defined for our storage requirements, we can now create our deployment manifest for WordPress.

A deployment manifest will ensure there are always at least 1 replicas of a pod running. If a pod fails a new one will be scheduled to replace it.

Create a new file named wordpress-deployment.yaml and add the following contents.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-multisite
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      serviceAccountName: wordpress-multisite-sa
      containers:
      - name: wordpress
        image: wordpress:5.6.0-php7.3-apache
        securityContext:
          capabilities:
            drop: ["ALL"]
            add: ["NET_BIND_SERVICE", "CHOWN"]
        ports:
        - containerPort: 80
        env:
        - name: WORDPRESS_DB_HOST
          valueFrom:
            configMapKeyRef:
              name: wordpress-configmap
              key: wordpress.db.host
        - name: WORDPRESS_DB_USER
          valueFrom:
            secretKeyRef:
              name: wordpress-secret
              key: wordpress.db.user
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: wordpress-secret
              key: wordpress.db.password
        - name: WORDPRESS_DB_NAME
          valueFrom:
            configMapKeyRef:
              name: wordpress-configmap
              key: wordpress.db.name
        - name: WORDPRESS_DB_PREFIX
          valueFrom:
            configMapKeyRef:
              name: wordpress-configmap
              key: wordpress.db.prefix
        - name: WORDPRESS_CONFIG_EXTRA
          valueFrom:
            configMapKeyRef:
              name: wordpress-configmap
              key: wordpress.config.extra
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim

SecurityContext

While Docker and Containerd apply some sane defaults to protect containerized workloads, OCI containers are still largely vulnerabily and place your host and other workloads at risk. Even more can be and should be done to harden your processes to protect against malicious users.

Malicious users who find their way into your container via a flaw in your application will quickly realize they are in a container. Knowing this, they will attempt to escape the container in order to attack the host and all other containerized workloads. The key to container escapes is root privileges within the container and, unfortunately, the official WordPress images must run as root. While not good, it’s not completely terrible.

One way to protect a container and mitigate risks of escapes is to remove all Kernel capabilities granted to the parent processes’ UID (0, root), except those absolutely required to run our app.

The deployment manifest above includes the following securityContext for our Wordpress container. With it we instruct Kubernetes to drop all Linux kernel capabilities granted to the parent process (1) who runs as root (0), and then add only NET_BIND_SERVICE and CHOWN.

  • The NET_BIND_SERVICE capability is required in order for the web server to bind to a privileged port (1-1024). In the case of a typical web application, that’s port 80.
  • The CHOWN capability is required as part of the Wordpress container’s startup process, where it applies new ownership to all Wordpress files.
        securityContext:
          capabilities:
            drop: ["ALL"]
            add: ["NET_BIND_SERVICE", "CHOWN"]

By dropping all other Linux kernel capabilities we effectively neuter the root user’s privileges within the container. Since all processes are limited to the kernel capabilities granted to the parent process (PID 1), even new processes are limited to the capabilities of the parent. Therefore, a malicious user would not be able to create a new process with elevated kernel capabilities permissions within the container.

Create your deployment by applying the manifest above to your cluster.

kubectl apply -f wordpress-deployment.yaml
Author Photo
Blogger, Developer, pipeline builder, cloud engineer, and DevSecOps specialist. I have been working in the cloud for over a decade and running containized workloads since 2012, with gigs at small startups to large financial enterprises.

How to Set PHP Options for Wordpress in Docker

Publised August 27, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

How to Solve Wordpress Redirects to Localhost 8080

Publised August 27, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

Deploy a WordPress Kubernetes Pod

Publised August 22, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

How to Deploy WordPress and MySQL on Kubernetes

Publised August 19, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

How to create Kubernetes Network Policies for WordPress

Publised August 7, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

How to Deploy Jekyll on Kubernetes

Publised September 15, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

How to Update Kubernetes Deployments

Publised September 11, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.

How to Configure Node-based apps in Kubernetes

Publised September 9, 2020 by Shane Rainville

Learn how to deploy WordPress with multisite enabled on Kubernetes with persistent storage, secrets for senstivie values, and configMaps.