How to Build High Availability WordPress Site with Docker

In our previous tutorial we demonstrated how to deploy simple application packaged as Docker container to production environment using Kontena. If you are doing some serious business and need to serve visitors at massive scale, you'll need to avoid single-point-of-failure and create scalable high availability solution. With Kontena, it is easy. Before diving in to our solution, let's talk a bit about Kontena and what is happening under the hood.

Kontena includes advanced scheduler that will automatically orchestrate Docker containers into different host nodes. There is also built-in network overlay powered by Weave. This technology is used to create a virtual LAN between application services. The network overlay enables connections between containers no matter on what host nodes they are deployed.

Kontena is also gathering logs and stats from all running containers and aggregates them to service level log and stats streams. This allows easy viewing of logs and statistics for your application CPU, memory, disk and network usage.

To demonstrate Kontena in action, in this tutorial we will build high availability WordPress site using Docker that is scaled up to three instances and backed up by database cluster. To avoid single point of failure, all WordPress instances and database cluster nodes are running on a different hosts.

High Availability WordPress with Docker

In this tutorial we will be building a WordPress setup that is running on three hosts. All the requests are handled by a set of load balancers (HA Proxy). The load balancers will then forward the traffic to WordPress instances. We will also use MySQL database cluster to serve the WordPress site. This database cluster is also behind a set of load balancers.

All the WordPress media files like photos are by default uploaded and stored on local file system of single machine. Naturally, this is not going to work for us since we have three instances of WordPress running. One solution is to turn-off the local storage and upload media files to CDN. While this is a great solution, it's adding requirement to have CDN service available. Therefore, we are using another solution: synchronizing filesystem with BitTorrent Sync (btsync).

BitTorrent Sync service is used to synchronize uploaded files between WordPress containers. When a new file is uploaded into some WordPress instance, BitTorrent Sync will transfer it automatically to all WordPress instances and it's visible to all users instantly no matter what WordPress instance is serving the file.

Let's get started! First, we'll need to define our application.

Define application

Prerequisites: You'll need to have Kontena CLI installed and access to Kontena infrastructure. See Kontena quick start guide.

Kontena applications can be described in YAML file (kontena.yml). The format is almost identical to docker-compose.yml format.

The difference is that docker-compose.yml defines your application in container level so you will have one container for each defined service.

In kontena.yml application is defined in service level and you can define how many containers you want to deploy for each service. Additionally there can be also defined where and how containers will be deployed.

kontena.yml

At first, a stateful seed database service is defined for our MySQL cluster:

galera-seed:  
   image: jakolehm/galera-mariadb-10.0-xtrabackup:latest
   stateful: true
   command: seed
   env_file: galera-seed.env

Environment variables are stored in galera-seed.env env_file in the same directory:

XTRABACKUP_PASSWORD=abc  
MYSQL_ROOT_PASSWORD=secret  

Then a MySQL cluster will be defined. We want to have three stateful instances of MySQL databases in our cluster.

galera:  
   image: jakolehm/galera-mariadb-10.0-xtrabackup:latest
   stateful: true
   instances: 3
   command: "node wordpress-cluster-galera-seed-1.kontena.local,wordpress-cluster-galera.kontena.local"
   env_file: galera.env

In galera.env file is defined the same XTRABACKUP_PASSWORD variable than in seed database.

XTRABACKUP_PASSWORD=abc  

Now we have described a MySQL cluster, but some load balancer is needed on the front of it. To do this HAProxy service is defined. It will be scaled up to three instances and our galera service will be linked to it:

galera-ha:  
   image: kontena/haproxy:latest
   instances: 3
   links:
     - galera:wordpress-cluster-galera
   environment:
     - BACKEND_PORT=3306
     - FRONTEND_PORT=3306
     - MODE=tcp

MySQL cluster and it will start listen to port 3306 on wordpress-cluster-galera-ha.kontena.local address.

Now we can define a WordPress service that will use MySQL cluster as a database. We want it to be stateful service, so uploaded files don’t get lost every time the service is re-deployed. Also it is scaled up to three instances, one for each host node:

wordpress:  
   image: wordpress:4.1
   stateful: true
   instances: 3
   env_file: wordpress.env

WordPress config parameters are stored in a separate wordpress.env env_file:

WORDPRESS_DB_HOST=wordpress-cluster-galera-ha.kontena.local  
WORDPRESS_DB_PASSWORD=secret  
WORDPRESS_AUTH_KEY=secret  
WORDPRESS_SECURE_AUTH_KEY=secret  
WORDPRESS_LOGGED_IN_KEY=secret  
WORDPRESS_NONCE_KEY=secret  
WORDPRESS_AUTH_SALT=secret  
WORDPRESS_SECURE_AUTH_SALT=secret  
WORDPRESS_LOGGED_IN_SALT=secret  
WORDPRESS_NONCE_SALT=secret  

Next, we'll define BitTorrent Sync service. But first, we'll need to create a secret key that is shared between out btsync clients. You can create the secret key from your command line like this:

$ docker run -i --rm --entrypoint=/usr/bin/btsync jakolehm/btsync:latest --generate-secret

This will generate you with the secret key you can use with your btsync setup. Now, let's define the btsync service:

wordpress-btsync:  
   image: jakolehm/btsync:latest
   volumes_from:
     -     "wordpress-cluster-wordpress-%%s"
   environment:
    -     SYNC_DIR=/var/www/html/wp-content
   instances: 3
   command: "YOUR_SECRET_KEY_HERE"

What is defined here is that we want three instances of btsync service so that each container instance will follow one of the WordPress containers. This is done by setting volumes_from option to wordpress-cluster-wordpress-%%s.

So far we have defined WordPress service, but in order to route traffic correctly, some load balancing is still needed. Let’s add another haproxy service here and scale it up to three instances and link our WordPress service to it. Also port 80 is exposed so our haproxy service will listen standard http port on a host node:

wordpress-ha:  
   image: kontena/haproxy:latest
   instances: 3
   ports:
     -     80:80
   links:
     -     wordpress:wordpress-cluster-wordpress
   environment:
     -     BALANCE=source

This completes our high availablity WordPress site setup. See the final kontena.yml file.

Deploy

You can deploy your whole application with just single command:

$ kontena deploy

After the application is deployed we can reach it with a web browser:

http://some_node_ip_address  

You might want to configure a domain name and point it to all host nodes.

Checking Stats and Logs

When application is deployed, stats and logs can be seen by following commands:

$ kontena service stats wordpress-cluster-wordpress
$ kontena service logs wordpress-cluster-wordpress

Get this example project from Github.

Credits: Lauri Nevala for writing the first draft of this article! Jari Kolehmainen for finding that amazing photo and btsync secret generation via docker :)

Image credits: The Art of Wordpress by StartBloggingOnline.com.