Run WordPress + MySQL on Kubernetes

Seandon Mooy
Mar 27, 2019

TL;DR: Press "apply" on each of the configs below and you'll be running a new WordPress blog.

WordPress is a nice example app for learning Kubernetes basics, because it involves a handful of moving pieces which most WordPress administrators are familiar with:

  1. You need to deploy the WordPress code
  2. You'll need a database system to store your blog's data
  3. You'll need some persistent storage for the database's data
  4. Finally, you need to expose the service to the internet

Kubernetes exposes a few high level resources which will help us get this job done:

  • A PersistentVolumeClaim, which asks the system for a bit of disk storage
  • A Secret, which will store our database password
  • A MySQL Deployment, which controls the DB containers and their configuration
  • Another Deployment for WordPress, similiarly controlling the containers running PHP + WordPress
  • A Service which allows WordPress to talk to our DB
  • Finally, another Service, which exposes WordPress to the internet!

These are some of the high level resources that make up Kubernetes, and are the building blocks of your infrastructure. KubeSail provides an easy way to both learn and iterate on k8s resources. Click the "Apply" button to instantly deploy to your own KubeSail.com namespace, or hover over any section to learn more!

For this tutorial we're going to stick fairly close to this article, and the YAML below will work on any Kubernetes cluster, from AWS or Google, to KubeSail Hobby Tier! There are no KubeSail-specific details, no OS-specific details, and no Cloud-specific details! We will discuss the one point where you may want to make a different decision, which is usually when it comes to how you expose your application to the internet.

Configuring MySQL

Let's start with the PersistentVolumeClaim, which asks Kubernetes for some disk space:

Apply
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Mi
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.apiVersionKube resource version to use
.kindWhat kind of resource is this yaml doc defining
.metadata.nameName of your resource
.specUsed to define how your deployment behaves. Some example properties might be: * selector * minReadySeconds

Not so bad! We've asked for a very small slice of storage (50mb) which we'll use for our Database.

Before creating our Database, let's first create a secret password, which we'll tell our Database and WordPress to use. K8s has a resource for encrypted things, called (naturally) Secrets. We can create one very simply like so:

NOTE: You should change the password below before clicking "apply"

Apply
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
type: Opaque
stringData:
password: SECRET_PASSWORD_GOES_HERE
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.apiVersionKube resource version to use
.kindWhat kind of resource is this yaml doc defining
.metadata.nameName of your resource

Next step, let's spin up a MySQL database by way of a Kubernetes Deployment. These resources control how k8s deploys our applications, and allow us to specify things like what container image to use, how many instances to spin up, what disks should be attached, etc. Almost every application deployment option you can imagine is supported out of the box. See the documentation for more.

In this deployment, we're setting the environment variable MYSQL_ROOT_PASSWORD from the secret you created earlier.

Apply
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: kubesail/mysql:5.6
imagePullPolicy: Always
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
# Import our mysql-pass.password
# as MYSQL_ROOT_PASSWORD
name: mysql-pass
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
# Mount our PersistentVolumeClaim
# to /var/lib/mysql
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
resources:
requests:
cpu: 5m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.apiVersionKube resource version to use
.kindWhat kind of resource is this yaml doc defining
.metadata.nameName of your resource
.specUsed to define how your deployment behaves. Some example properties might be: * selector * minReadySeconds
.spec.selector.matchLabelsSelector for the user defined labels to identify your template
.spec.strategySpecifies the strategy used to replace old Pods by new ones. `.spec.strategy.type` can be one of: * Recreate * RollingUpdate "RollingUpdate" is the default value
.spec.templateRequired object that describes the Pods Kubernetes should create as part of the deployment

Excellent! Now we have a fully fledged MySQL server up and running! Not so bad right? All of this can be added to version control, just remember to encrypt or ignore your Secrets!

Note that we used the image kubesail/mysql which is tuned to use less memory. but if you are running this on your own cluster or on the KubeSail hobby tier, you may want to use the standard mysql image.

The next step is to define a way for us to reach our MySQL server. This is one of the most often confused parts when learning Kubernetes, so it's important to understand the terms clearly. We're going to be defining a Service for our MySQL Deployment.

  • Deployments control how applications are started, stopped, and updated.
  • Services control how clients access a given Deployment.

So, we'll be creating a MySQL service which points at our MySQL deployment.

Services and Deployments

You can think of a Service as a combination of a DNS record, a port-forwarding rule, and a load-balancer. Like Deployments, almost every concievable option relating to exposing an application to a network can be defined in a Service. See the Services Documentation for more. Let's create one!

NOTE: For now, KubeSail manages Service objects for you, and you'll currently have read only access to this resource. For the purpose of this demo, the "Create Service" button below will create the following service in your namespace for you. Of course, this service config will also work on any other k8s cluster!

create service
---
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
# Note that these match the "labels" in MySQL
app: wordpress
tier: mysql
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.apiVersionKube resource version to use
.kindWhat kind of resource is this yaml doc defining
.metadata.nameName of your resource
.specUsed to define how your deployment behaves. Some example properties might be: * selector * minReadySeconds

Configuring WordPress

Next, we'll configure our WordPress Deployment:

The service above was created for us, and was given a name automatically. The value for WORDPRESS_DB_HOST comes from the name of the service that is generated by KubeSail, but if you are running this on your own cluster, you'll want to update this value to be the name of the service you created in the last step.

Apply
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: wordpress:4.8-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql-wordpress-mysql-internal
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 80
name: wordpress
resources:
requests:
cpu: 15m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.apiVersionKube resource version to use
.kindWhat kind of resource is this yaml doc defining
.metadata.nameName of your resource
.specUsed to define how your deployment behaves. Some example properties might be: * selector * minReadySeconds
.spec.selector.matchLabelsSelector for the user defined labels to identify your template
.spec.strategySpecifies the strategy used to replace old Pods by new ones. `.spec.strategy.type` can be one of: * Recreate * RollingUpdate "RollingUpdate" is the default value
.spec.templateRequired object that describes the Pods Kubernetes should create as part of the deployment

And finally, we need to expose our WordPress application to the world! There are many many many ways to do this - KubeSail makes it very easy by allowing you to simply expose your app through our infrastructure (we'll handle HTTPS, load balancing, high availablility, etc.) - click "Create External Service" to expose your WordPress instance! On other Kubernetes Clusters, you may want to create your own Load Balancer, which would look like this:

create external service
---
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: LoadBalancer
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.apiVersionKube resource version to use
.kindWhat kind of resource is this yaml doc defining
.metadata.nameName of your resource
.specUsed to define how your deployment behaves. Some example properties might be: * selector * minReadySeconds

Unfortunately, you may notice this does not include HTTPS! We'll leave adding that to you as it is out of the scope of this article. KubeSail gives you HTTPS for free, but does not yet support Custom Domains — stay tuned for news on that!

Stay in the loop!

We're working hard to make Kubernetes easy to learn and use! Give us a shout on twitter or gitter, checkout some of our GitHub repos and be sure to join our mailing list!