Creating a Dockerized Magnolia CMS Environment using Docker Compose and Vagrant


Introduction

This article describes how to create a complete Magnolia environment that contains an Author environment and two Public environments which are served via a HAProxy load balancer. The purpose of this setup is to be able to do a full system test from a local development machine.

Developers tend to use a single local virtual machine that contains all of the components of a system. This is often architecturally very different to how the system is eventually deployed to production (live) servers, where the system is most likely spread across multiple servers, such as a database server, backend server, services layer, load balancer, etc. Spinning up multiple local virtual machines to create a more accurate representation of the production environment can be a complicated and resource intensive process. Most developers will shy away from this and instead use some form of pre-production environment for their integration testing, rather than their local environment. But it’s always beneficial, and sometimes even necessary, to test early and test often using a more accurate local representation of the production environment.

Containerisation engines, such as Docker, can help here as they allow developers to define containers for each logical component of a system. Containers make efficient use of the capacity of a host machine by separating the system architecture from the underlying operating system. This makes it feasible to model all the components of a system using multiple containers running within a single virtual machine instance.

Recreating a Typical Magnolia Setup

The following diagram (borrowed from Magnolia’s documentation) describes a typical Magnolia implementation.

A typical Magnolia setup
A typical Magnolia setup

This is the target setup to model using Docker containers, with the slight variation that the database nodes (labelled “JCR” in the diagram for the Java Content Repository) are configured as standalone MySQL containers.

The following sever containers can be created:

  • Author web server running Tomcat
  • Author database running MySQL
  • Two public web servers running Tomcat
  • Two public database servers running MySQL
  • HAProxy load balancer

Docker Compose files

Docker compose allows developers to define a system with multiple containers using a YAML file. The Docker compose file for the Magnolia setup described above is as follows:

version: '3'

services:

publicDB1:
container_name: mysql-public1-container
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: xxxxxxx
MYSQL_DATABASE: magnoliaPublic
MYSQL_USER: magnoliaPublic
MYSQL_PASSWORD: xxxxxxx
ports:
- "3307:3307"
volumes:
- ./mysql-config/public1/:/etc/mysql/conf.d
- ./db:/docker-entrypoint-initdb.d

public1:
container_name: tomcat-public1-container
hostname: public1
build: .
ports:
- "8081:8080"
expose:
- "8081"
volumes:
- ./war/ROOT.war:/opt/apache-tomcat-9.0.10/webapps/ROOT.war
links:
- publicDB1:mysql-public1-container

publicDB2:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: xxxxxxx
MYSQL_DATABASE: magnoliaPublic
MYSQL_USER: magnoliaPublic
MYSQL_PASSWORD: xxxxxxx
ports:
- "3308:3308"
volumes:
- ./mysql-config/public2/:/etc/mysql/conf.d
- ./db:/docker-entrypoint-initdb.d

public2:
container_name: tomcat-public2-container
hostname: public2
build: .
ports:
- "8082:8080"
expose:
- "8082"
volumes:
- ./war/ROOT.war:/opt/apache-tomcat-9.0.10/webapps/ROOT.war
links:
- publicDB2:mysql-public2-container

authorDB:
container_name: mysql-author-container
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: xxxxxxx
MYSQL_DATABASE: magnoliaAuthor
MYSQL_USER: magnoliaAuthor
MYSQL_PASSWORD: xxxxxxx
ports:
- "3306:3306"
volumes:
- ./authorDB:/docker-entrypoint-initdb.d

author:
container_name: tomcat-author-container
build: .
ports:
- "8080:8080"
volumes:
- ./war/ROOT.war:/opt/apache-tomcat-9.0.10/webapps/ROOT.war
links:
- authorDB:mysql-author-container
links:
- public1
- public2

haproxy:
container_name: haproxy
image: haproxy:latest
volumes:
- ./haproxy:/haproxy-override
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
links:
- public1
- public2
ports:
- "8000:8000"
- "70:70"
expose:
- "8000"
- "70"

The Tomcat instances are built from a Dockerfile. This file also takes care of deploying the application file (WAR file) to the Tomcat root. The application used for this demonstration was the Magnolia Travel Demo.

The two public ports are being forwarded from 8081 and 8082 to 8080. And the three MySQL instances (2 public databases and 1 author) are running on ports 3306, 3307, and 3308. This is because we are running the application on one virtual machine and therefore must use different ports to expose the services outside of the containers.

In order to run MySQL on different ports requires the port to be changed in the MySQL configuration file. This line in the Docker Compose file copies the my.cnf file to the right place:
/mysql-config/public1/:/etc/mysql/conf.d

The my.cnf file simply contains:
[mysqld]
port=3307

The “links” definition connects containers to each other. For example: publicDB1:mysql-public1-container links the Public 1 web servers to the PublicDB1 MySQL instance.

The web servers have their hostnames explicitly defined, e.g. hostname: public1. This is so that the correct Magnolia initialisation file (magnolia.properties) will be read. By default, Magnolia reads the magnolia.properties file from: WEB-INF/config/${servername}/magnolia.properties, where ${servername} is the hostname.

In the Jackrabbit configuration, the database URL is set to the Docker instance name, e.g.:
<param name=”url” value=”jdbc:mysql://publicDB1:3307/magnoliaPublic” />

HAProxy configuration

The following line maps the HAProxy configuration file:
– ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro

The haproxy.cfg is as follows:

global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
maxconn 4096

defaults
log global
mode http
option httplog
option dontlognull
timeout connect 10000ms
timeout client 50000ms
timeout server 50000ms

listen stats
bind 0.0.0.0:70
mode http
stats enable
stats hide-version
stats scope .
stats realm Haproxy\ Statistics
stats uri /
stats auth admin:admin

frontend balancer
bind *:8000
mode http
default_backend web_backends

backend web_backends
mode http
option forwardfor
balance roundrobin
server public1 public1:8080 check id 1
server public2 public2:8080 check id 2

bind *:8000 maps the load balancer to port 8000.

The last few lines configure the two public servers into the load, to be served using a round robin process:
balance roundrobin
server public1 public1:8080 check id 1
server public2 public2:8080 check id 2

Vagrant machine

To wrap the multiple Docker containers into a single virtual machine that can be created, destroyed and recreated at will, Vagrant was used to orchestrate the creation the VM, the installation of Docker and Docker Compose, and to start the Docker containers.

The Vagrantfile that does all this is as follows:

Vagrant.configure("2") do |config|

config.vm.box = "centos/7"
config.vm.provider "virtualbox" do |vb|
vb.memory = 8048
end
config.ssh.insert_key = 'true'

# Open Ports
config.vm.network "forwarded_port", guest: 70, host: 70
config.vm.network "forwarded_port", guest: 8000, host: 8000
config.vm.network "forwarded_port", guest: 8080, host: 1111
config.vm.network "forwarded_port", guest: 8081, host: 1112
config.vm.network "forwarded_port", guest: 8082, host: 1113

# Install Docker using Ansible script
config.vm.provision "ansible_local" do |ansible|
ansible.playbook = "docker-install.yml"
ansible.limit = "all,localhost"
ansible.become_user = "root"
end

# Start Docker
config.vm.provision "shell", inline: "docker-compose -f /vagrant/docker/docker-compose.yml up"

end

Multiple ports are forwarded on the virtual machine. These map to the Magnolia, MySQL and HAProxy servers that are configured in the Docker Compose file.

To install Docker, Ansible is first installed, and this Ansible playbook is then called.

The following line starts the Docker Compose process:
config.vm.provision “shell”, inline: “docker-compose -f /vagrant/docker/docker-compose.yml up”

The output of all of this is a process to start up the seven containers that make up the full Magnolia environment with a single command:

vagrant up

After a few minutes the following instances are available:

Author Server
Author Server
HAProxy Endpoint
HAProxy Endpoint
Public Server 1
Public Server 1
Public Server 2
Public Server 2
HAProxy Admin
HAProxy Admin
 

Conclusion

This framework provides an easy way for a developer to set up and test a complete Magnolia solution in a local development environment. With a simple command they can spin up a complete replica of a production environment within their local machine. By running the application locally, developers are able to debug issues with their standard tools, unencumbered by network connectivity, security policies, or other issues that may effect real-world infrastructure, and can replicate and resolve common issues that can occur when running multiple public servers behind a load balancer.

Leave a Comment

(required)