To run these examples the following must be configured on the local host:
All solutions were implemented on OS X Sierra and due to time constraints have not been tested on any other platforms. The benefit of using virtualisation technologies here means it should work on other platforms with no changes, however YMMV.
For this example, a local docker registry will be used for speed and ease of development.
This won't be covered by the Ansible automation so will need to be setup before running any commands.
If you already have a suitable docker registry, go ahead and skip this step, but change the docker_registry
variable in the Ansible playbook files.
To set up a docker registry on the host machine run
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
Note: for speed, this registry is not configured securely and is not production-ready in this form. In a real-world scenario, the registry should be secured with TLS and require authentication if it's externally accessible.
This is the solution to task 2, to see the solution to task 1 checkout tag task1
in this repository.
Alternatively you can view it in the browser here https://github.com/hipyhop/LG201/tree/task1.
The solution to Task 2 involves building the two application servers as Docker images, and running them as containers on the web server hosts.
I've separated this into two sections to simulate a typical CI/CD pipeline building the docker images when changes are published, and a manual deployment stage using Ansible/vagrant automation.
In lieu of a CI system, such as Jenkins, I've added build.sh scripts to the forked application repository https://github.com/hipyhop/Hello.
To simulate a build you can clone the repository and run ./build.sh
in the respective directories to build the application images. Then, if successful, push the resulting image to the specified Docker registry.
EXPORT DOCKER_TAG=latest
EXPORT DOCKER_REGISTRY=localhost:5000
./build.sh
Ideally these would be added to the CI/CD pipeline as build steps, hopefully with additional testing/validation steps between the building the image, and pushing it to the remote registry.
Then on a successful build the new image(s) will be ready to be deployed.
Launching the three required vagrant guests and provisioning them with the required configuration is as simple as running:
vagrant up
What does this do?
- Launches three
ubuntu/trusty64
guests within the same private network and creates an Ansible inventory file. - This inventory files groups two hosts as docker-hosts, and the other as a load balancer.
- Then the Ansible playbook
common.yml
is used to provision these hosts. As Ansible files are designed to be human-readable, I recommend reading this file for more information on the provisioning process. - The application servers are deployed as containers from the docker registry using the tag
latest
. Each docker_host runs one go_app, and one node_app instance. - The load balancer is configured with two ACLs to route incoming L7 traffic to the appropriate backend based on the request path.
If it any point in the future more docker hosts need to be added, just edit the N_DOCKERHOSTS
variable in the Vagrantfile
.
The next time vagrant up is run the Ansible inventory file will be updated.
See lines 46-48 and 54-56 of haproxy/haproxy.cfg.j2
to see how the HAProxy configuration is dynamically generated from the Ansible inventory file.
In the current setup, vagrant runs the Ansible playbook after each host definition, resulting in a lot of duplicated effort.
This isn't a huge problem, since Ansible modules are smart about the defined tasks and can check if anything needs doing, however this still introduces a significant delay to the up-provision loop.
It looks like I'm not the only person to face this issue: hashicorp/vagrant#1784 A few suggestions were offered, but I couldn't find a decent solution within a sensible time..
The best way around it appears to be separating the spin-up and provisioning steps:
$ vagrant up --no-provision && vagrant provision
There are a number of possible improvements that could be added in the future to improve the efficiency and speed of builds, while simplifying the deployment stage.
-
Kubernetes! Currently there is no orchestration. Deployment is manual using
vagrant up
orvagrant provision
. Containers are restarted on failure, and the load balancer performs health checks to ensure the host is alive before routing traffic to it. But Kubernetes would handle restarting the services on new hosts automatically if required. -
No logging or notifications in the current implementation. It's all good having a fail-over system, but nobody is notified about a failure, you may get to a point where all fail-over systems have failed, resulting in downtime.
-
Vagrant is slower and more resource intensive than containers. The whole solution could be implemented with docker-compose or kubernetes more efficiently. However that's not what the solution asked for.
-
Initial vagrant host provisioning time is quite slow. In a real-world scenario I would look into building a suitable Vagrant box with the latest updates and dependencies pre-installed to reduce provisioning time on each launch.
-
The docker build stage doesn't go in to too much details about how this would work in a CI/CD pipeline. Given time I would:
- Keep a local cache of the git repository, to speed up clone/git pull times.
- mount the source directory into a docker container and run any tests in there.
- Build the docker image inside the container, by mounting the the host's docker socket to the CI container
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
. This is much better than running docker-in-docker.
-
The applications are stateless. Not data storage on the local filesystem or a database is required making this example reasonably trivial.