> ## Content Index
> Fetch the complete content index at: https://blog.mebooks.co.nz/llms.txt
> Use this file to discover other available public pages before exploring further.

# Running Wordpress using Kubernetes locally
- URL: https://blog.mebooks.co.nz/running-wordpress-using-kubernetes/
- Published: 2017-10-08T04:58:00.000Z
- Updated: 2017-10-08T05:11:07.000Z
- Author: Jason Darwin
- Tags: #Import 2026-09-23 10:11

Our aim with this post is to describe the process to run [Kubernetes](https://kubernetes.io/?ref=blog.mebooks.co.nz) locally, by demonstrating running MySQL and Wordpress on the Kubernetes cluster.

We won't explain what Kubernetes is, suffice to say it is the container orchestration solution that everyone seems to be leaning towards; normally you'd want to run up a Kubernetes cluster on a cloud provider such as AWS or GCE, however for the purposes of training / evaluation, we run it up locally using a cluster of Vagrant virtual machines.

## Prerequisites

We assume we're on Mac OSX, though the `Vagrantfile` is OS-agnostic, so you should be able to get things working on both Windows and Linux relatively easily.

We need to install a few things, and we'll do so using the [brew package manager](https://brew.sh/?ref=blog.mebooks.co.nz):

```bash
brew install vagrant
brew install kubectl
brew install wget

```

## Creating the Kubernetes cluster

Our first step is to clone the [kubernetes-vagrant-coreos-cluster](https://github.com/pires/kubernetes-vagrant-coreos-cluster?ref=blog.mebooks.co.nz) repo, which bills itself as a:

> Turnkey Kubernetes cluster setup with Vagrant 1.8+ and CoreOS.

This project provides a comprehensive `Vagrantfile` which will, by default, run up a cluster of three CoreOS Vagrant virtual machines.

```bash
# clone the repo
git clone https://github.com/pires/kubernetes-vagrant-coreos-cluster.git
cd kubernetes-vagrant-coreos-cluster

```

We can then `vagrant up` the cluster:

```bash
# remove Virtualbox's DHCP server
VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0

# run up the cluster, specifying that we want the web ui
USE_KUBE_UI=true vagrant up

```

Note that we specify that we want the web-based user interface; other settings are described in the project's [README.md](https://github.com/pires/kubernetes-vagrant-coreos-cluster?ref=blog.mebooks.co.nz), but do take a look through the [Vagrantfile](https://github.com/pires/kubernetes-vagrant-coreos-cluster/blob/master/Vagrantfile?ref=blog.mebooks.co.nz) to get a better idea about what values can be supplied.

The cluster startup will likely take some time, as vagrant will most likely need to pull a number of images; the Vagrantfile does allow the following providers, though we're using Virtualbox here:

- **[Virtualbox](https://www.virtualbox.org/?ref=blog.mebooks.co.nz)** (the default)
- **[Parallels Desktop](http://www.parallels.com/eu/products/desktop/?ref=blog.mebooks.co.nz)**
- **[VMware Fusion](http://www.vmware.com/products/fusion?ref=blog.mebooks.co.nz)** or **[VMware Workstation](http://www.vmware.com/products/workstation?ref=blog.mebooks.co.nz)**

Once Vagrant has pulled the necessary VM images, you should be able to inspect them at:

```bash
# display the virtualbox image directories
ls -la ~/VirtualBox\ VMs/

# display the uncompressed sizes of the virtualbox image directories
du -sh ~/VirtualBox\ VMs/*

2.8G	/Users/jasondarwin/VirtualBox VMs/kubernetes-vagrant-coreos-cluster_master_1507421853719_25399
4.4G	/Users/jasondarwin/VirtualBox VMs/kubernetes-vagrant-coreos-cluster_node-01_1507422227192_50843
2.7G	/Users/jasondarwin/VirtualBox VMs/kubernetes-vagrant-coreos-cluster_node-02_1507422346442_20718

```

## Inspecting the cluster

Once the cluster has been created, we can then get some information about it.

We'll be using `kubectl`, and there's [a cheatsheet](https://kubernetes.io/docs/user-guide/kubectl-cheatsheet/?ref=blog.mebooks.co.nz) that lists the useful `kubectl` commands.

```bash
# view the three nodes using vagrant
vagrant status

Current machine states:

master                    running (virtualbox)
node-01                   running (virtualbox)
node-02                   running (virtualbox)

# get info about our cluster
kubectl cluster-info

Kubernetes master is running at https://172.17.8.101
KubeDNS is running at https://172.17.8.101/api/v1/namespaces/kube-system/services/kube-dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

# display nodes
kubectl get nodes

NAME           STATUS                     AGE       VERSION
172.17.8.101   Ready,SchedulingDisabled   3h        v1.7.5
172.17.8.102   Ready                      3h        v1.7.5
172.17.8.103   Ready                      3h        v1.7.5

```

At this point we might like to ssh into our cluster nodes and have a look around; we can either use `vagrant ssh` or we can `ssh` directly by referring to the vagrant `insecure_private_key`:

```bash
# ssh into the master
vagrant ssh master

# ssh directly into a machine
ssh -i ~/.vagrant.d/insecure_private_key core@172.17.8.101

```

## The Kubernetes dashboard

During the creation of the cluster, you'll see messages regarding the creation of the `kubernetes-dashboard`:

```bash
==> master: Configuring Kubernetes dashboard...
deployment "kubernetes-dashboard" created
service "kubernetes-dashboard" created
==> master: Kubernetes dashboard will be available at http://172.17.8.101:8080/ui

```

Visting the specified URL (in our case [http://172.17.8.101:8080/ui](http://172.17.8.101:8080/ui?ref=blog.mebooks.co.nz)) will present you with the dashboard:

![thumb](http://res.cloudinary.com/mebooks/image/upload/v1507435152/kubernetes-ui_wygi6s.png)

Note that visiting the base URL ([http://172.17.8.101:8080](http://172.17.8.101:8080/?ref=blog.mebooks.co.nz)) will present a JSON file listing all exposed endpoints, including some very handy ones:

```json
{
    "paths": [
        "/api",
        "/api/v1",
        "/apis",
        "/apis/",
        "/apis/admissionregistration.k8s.io",
        "/apis/admissionregistration.k8s.io/v1alpha1",
        "/apis/apiextensions.k8s.io",
        "/apis/apiextensions.k8s.io/v1beta1",
        "/apis/apiregistration.k8s.io",
        "/apis/apiregistration.k8s.io/v1beta1",
        "/apis/apps",
        "/apis/apps/v1beta1",
        "/apis/authentication.k8s.io",
        "/apis/authentication.k8s.io/v1",
        "/apis/authentication.k8s.io/v1beta1",
        "/apis/authorization.k8s.io",
        "/apis/authorization.k8s.io/v1",
        "/apis/authorization.k8s.io/v1beta1",
        "/apis/autoscaling",
        "/apis/autoscaling/v1",
        "/apis/batch",
        "/apis/batch/v1",
        "/apis/batch/v2alpha1",
        "/apis/certificates.k8s.io",
        "/apis/certificates.k8s.io/v1beta1",
        "/apis/extensions",
        "/apis/extensions/v1beta1",
        "/apis/networking.k8s.io",
        "/apis/networking.k8s.io/v1",
        "/apis/policy",
        "/apis/policy/v1beta1",
        "/apis/rbac.authorization.k8s.io",
        "/apis/rbac.authorization.k8s.io/v1alpha1",
        "/apis/rbac.authorization.k8s.io/v1beta1",
        "/apis/settings.k8s.io",
        "/apis/settings.k8s.io/v1alpha1",
        "/apis/storage.k8s.io",
        "/apis/storage.k8s.io/v1",
        "/apis/storage.k8s.io/v1beta1",
        "/healthz",
        "/healthz/autoregister-completion",
        "/healthz/ping",
        "/healthz/poststarthook/apiservice-registration-controller",
        "/healthz/poststarthook/apiservice-status-available-controller",
        "/healthz/poststarthook/bootstrap-controller",
        "/healthz/poststarthook/ca-registration",
        "/healthz/poststarthook/extensions/third-party-resources",
        "/healthz/poststarthook/generic-apiserver-start-informers",
        "/healthz/poststarthook/kube-apiserver-autoregistration",
        "/healthz/poststarthook/start-apiextensions-controllers",
        "/healthz/poststarthook/start-apiextensions-informers",
        "/healthz/poststarthook/start-kube-aggregator-informers",
        "/healthz/poststarthook/start-kube-apiserver-informers",
        "/logs",
        "/metrics",
        "/swagger-2.0.0.json",
        "/swagger-2.0.0.pb-v1",
        "/swagger-2.0.0.pb-v1.gz",
        "/swagger.json",
        "/swaggerapi",
        "/ui",
        "/ui/",
        "/version"
    ]
}

```

## Installing MySQL and Wordpress

Once our cluster is running, we then need to use it; for this demonstration we'll run up MySQL and Wordpress as two separate services.

We're working from the [Kubernetes example using MySQL and Wordpress](https://github.com/kubernetes/kubernetes/tree/master/examples/mysql-wordpress-pd?ref=blog.mebooks.co.nz), which is described in detail [on the Kubernetes site as a tutorial](https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/?ref=blog.mebooks.co.nz), though we did find we needed to make a few tweaks to get everything working.

Create our directory and download the yaml files:

```bash
mkdir mysql-wordpress
cd mysql-wordpress

wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/examples/mysql-wordpress-pd/local-volumes.yaml
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/examples/mysql-wordpress-pd/mysql-deployment.yaml
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/examples/mysql-wordpress-pd/wordpress-deployment.yaml

```

Amend local-volumes.yaml to add `annotations`, otherwise we get problems with Kubernetes not being able to connect the persistent volumes:

```yaml
# Before:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
    app: wordpress

# After:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
annotations:
    volume.beta.kubernetes.io/storage-class: ""
labels:
    app: wordpress

```

Create our local persistent volume mounts.

Note: Any other Type of PersistentVolume would allow you to recreate the Deployments and Services at this point without losing data, but `hostPath` loses the data as soon as the Pod stops running. However, for the purposes of this demonstration, we're not worried about persisting data.

```bash
mkdir -p /tmp/data/pv-1
mkdir -p /tmp/data/pv-2

```

Create two PersistentVolumes from the `local-volumes.yaml` file:

```bash
kubectl create -f local-volumes.yaml

# display the persistent volumes:
kubectl get pv

NAME         CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                    STORAGECLASS   REASON    AGE
local-pv-1   20Gi       RWO           Retain          Bound     default/wp-pv-claim                               1h
local-pv-2   20Gi       RWO           Retain          Bound     default/mysql-pv-claim                            1h

```

### Creating secrets for MySQL Password

The Kubernetes site has [documentation about handling secrets](https://kubernetes.io/docs/concepts/configuration/secret/?ref=blog.mebooks.co.nz); in our case we need to handle the MySQL password.

One option is to create a secret in a password file:

```bash
echo -n "WHATEVER" > ./password.txt
kubectl create secret generic mysql-pass --from-file=password.txt

# Display the existence of the secret
kubectl get secrets

```

In the `mysql-deployment.yaml` we'll then need to ensure the container spec is set accordingly:

```yaml
# mysql-deployment.yaml
- name: MYSQL_ROOT_PASSWORD
  valueFrom:
    secretKeyRef:
      name: mysql-pass
      key: password.txt

```

Alternatively, we can create a literal secret, which is supplied directly on the command line:

```bash

# kubectl create secret generic mysql-pass --from-literal=password=WHATEVER

# Display the existence of the secret
kubectl get secrets

```

```yaml
# mysql-deployment.yaml
- name: MYSQL_ROOT_PASSWORD
  valueFrom:
    secretKeyRef:
      name: mysql-pass
      key: password

```

## Deploy MySQL

At this point we're ready to deploy MySQL:

```bash
# deploy MySQL
kubectl create -f mysql-deployment.yaml

# Show the pod as deployed
kubectl get pods

NAME                               READY     STATUS    RESTARTS   AGE
wordpress-mysql-2917821887-m4cgc   1/1       Running   0          1h

# Describe the deployment, including the assigned persistent volume
kubectl describe -f mysql-deployment.yaml

```

Find the node running MySQL via the pods in the Kubernetes UI; amongst other things, it will indicate which node the service is running on (172.17.8.103 in this case):

![thumb](http://res.cloudinary.com/mebooks/image/upload/v1507436906/kubernetes-ui-mysql_srxgve.png)

## Inspect MySQL

```bash
# ssh into the node
ssh -i ~/.vagrant.d/insecure_private_key core@172.17.8.103

# find the running mysql container
docker ps -l

CONTAINER ID        IMAGE                                             COMMAND                  CREATED             STATUS              PORTS               NAMES
0c24ee28b333        mysql                                             "docker-entrypoint..."   29 seconds ago      Up 29 seconds                           k8s_mysql_wordpress-mysql-2917821887-r67gk_default_c269b585-abcb-11e7-bd0b-08002751ec84_0

# docker exec into the running mysql container
# and run a interactive bash shell
docker exec -it $(docker ps -ql) /bin/bash

# login to mysql with the previously-configured password
mysql -u root -p

```

### Rolling back

If you encounter problems, it's quite easy to rollback the deployment:

```bash
# if you need to rollback:
kubectl delete service wordpress-mysql
kubectl delete deployment wordpress-mysql
kubectl delete pvc mysql-pv-claim

```

If you get really stuck, you can also delete the local persistant volume mounts, though you'll need to then recreate them if you want to re-deploy:

```bash
# Delete the local pv mounts
kubectl delete pv local-pv-1 local-pv-2

# Receate the pv mounts using local-volumes.yaml file:
kubectl create -f local-volumes.yaml

# Redeploy MySQL
kubectl create -f mysql-deployment.yaml

```

## Deploy Wordpress

Deploying Wordpress is quite similar

```bash
# deploy wordpress
kubectl create -f wordpress-deployment.yaml

# Show the pod as deployed
kubectl get pods

NAME                               READY     STATUS    RESTARTS   AGE
wordpress-559664747-h22qq          1/1       Running   0          1h
wordpress-mysql-2917821887-m4cgc   1/1       Running   0          1h

# Describe the deployment, including the assigned persistent volume
kubectl describe -f wordpress-deployment.yaml

```

Check the Kubernetes UI to ensure that the Wordpress pod has deployed successfully.

![thumb](http://res.cloudinary.com/mebooks/image/upload/v1507437520/kubernetes-ui-wordpress_eoz2cg.png)

We need to take note of the `Cluster IP` assigned to Wordpress, which can be seen on the UI under `Services`, or can be found via `kubectl`:

```bash
# find the internal ip
kubectl get services wordpress

NAME        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
wordpress   10.100.111.132   <pending>     80:32367/TCP   5m

```

Note that the Wordpress service will be shown as `pending`; this is nothing to worry about.

We'll also need to take note from the Kubernetes UI of Node that it's been assigned to; in our case it was `172.17.8.102`.

## Inspect Wordpress

```bash
# ssh into the node
ssh -i ~/.vagrant.d/insecure_private_key core@172.17.8.102

# then curl the page using the CLUSTER-IP from
# 'kubectl get services wordpress'.
# Note we follow the redirect
curl -i -L 10.100.111.132

# lots of HTML then follows...

```

Use the node ip, and the port reported in `kubectl get services wordpress` to get to the wordpress install screen in a browser; in our case the URL is [http://172.17.8.102:32367](http://172.17.8.102:32367/?ref=blog.mebooks.co.nz)

You should then see the familiar Wordpress install screen:

![thumb](https://raw.githubusercontent.com/kubernetes/examples/master/mysql-wordpress-pd/WordPress.png)

### Rolling back

If you encounter problems, it's quite easy to rollback the deployment:

```bash
# if you need to rollback:
kubectl delete deployment wordpress
kubectl delete service wordpress
kubectl delete pvc wp-pv-claim

```

If you get really stuck, you can also delete the MySQL deployment and the local persistent volume mounts, though you'll need to then recreate them if you want to re-deploy:

```bash
# Delete the MySQL deployment
kubectl delete service wordpress-mysql
kubectl delete deployment wordpress-mysql
kubectl delete pvc mysql-pv-claim

# Delete the local pv mounts
kubectl delete pv local-pv-1 local-pv-2

# Receate the pv mounts using local-volumes.yaml file:
kubectl create -f local-volumes.yaml

# Redeploy MySQL
kubectl create -f mysql-deployment.yaml

# Redeploy Wordpress
kubectl create -f wordpress-deployment.yaml

```

## Cleanup

Once you tire of your kubernetes cluster, cleanup is quite straightforward:

```bash
# Destroy the vagrant cluster
vagrant destroy

# Delete the local persistent volumes
kubectl delete pv local-pv-1 local-pv-2

# Remove the local mounts
rm -rf /tmp/data/pv-1/
rm -rf /tmp/data/pv-2/

```

## Summary

There are plenty of other [Kubernetes examples](https://github.com/kubernetes/kubernetes/tree/master/examples?ref=blog.mebooks.co.nz) to play around with and plenty of other [tutorials on the Kubernetes site](https://kubernetes.io/docs/tasks/?ref=blog.mebooks.co.nz), and possibly in a future post we'll look at deployment on a cloud service such as AWS.