Docker, AWS and Ansible (Part 1 of 3)
In this article I am going to highlight how Docker, AWS and Ansible can come together to do the DevOps aspects of a software engineer’s job. If you are already familiar with the technology then just skip to the end of this article if you want to see the complete example, but I’d recommend you at least read the next two headings to understand the context.
Because there is a lot to talk about this article is split into 3 parts:
Firstly, what’s the example challenge:
We want to be able to create a complete application stack on our laptop, get it working, then with very minimal effort deploy it into another environment (e.g. production) with a completely automated setup. The application must require zero changes to work in another environment, save any environment specific configuration.
For this example we are going to consider a typical micro-service which will have:
- Reverse proxy realised by an Nginx server;
- Application server realised by Akka HTTP;
- Microservice realised as a Scala application.
You don’t need any special Scala knowledge, if you can understand Java that will be enough. You could easily replace the Application server with Tomcat and write a Java application.
In this article I am only going to provide one solution to the above challenge, however, it can be solved in multiple ways, so this isn’t a single truth solution.
The core technologies that will be used are as follows:
- Scala as the application language;
- Akka HTTP as the application server;
- Nginx as a reverse proxy;
- Docker as the container server;
- AWS for public cloud IaaS (Infrastructure as a Service) and PaaS (Platform as a Service);
- Ansible to automate the provisioning/decommissioning of all the technologies.
We could easily add on to this solution with things monitoring, alerting, auto-scaling policies. But it will complicate this example, which is to highlight how Docker, AWS and Ansible can come together to make things good.
Microservice and Application server
A microservice architecture allows us to build complex solutions from simple components. For more detailed information on microservices see Martin Fowler’s article or read Building Microservices by Sam Newman.
A microservice is a single small-ish component built to realise a necessary business function. It is built to be managed in isolation fully through automation. Microservices breakdown monolithic applications. The benefit being that individual components can be versioned, deployed, scaled and re-used independently. Microservices are designed to be as decoupled and cohesive as possible. In other words they own their own domain business logic.
Microservices are typically exposed via REST over HTTP API. With a common easy to use interface, they can be built using any programming language.
We will build our example service using the popular programming language Scala and run it using Akka HTTP. Scala is a good choice, it’s syntax is familiar to Java or C# developers whilst supporting both object-oriented and functional paradigms. Generally you write less code in Scala than you would with those other languages I mentioned. Akka HTTP is a toolkit for exposing (Scala) Actors (concurrent processes) via HTTP using message-driven patterns.
Following traditional example approaches, our microservice will simply say “Hello World”. Scala/Akka is obviously over-kill for this purpose, but once it is working it will be easy to build real functionality - the hardest bit is always integration, writing code to realise new functionality is relatively easy.
A reverse proxy acts on behalf of a client (the thing wanting to access the service) to retrieve resources from a back-end service. This has the following benefits in our use case:
- Protects the origin server (micro-service);
- Compress response data if required;
- Access and error logging;
- Application performance monitoring.
In some deployments they are also used for caching, web application firewalls, load balancers and SSL termination. But we’ll be using AWS services to handle these requirements.
Nginx is a great option for a lightweight reverse proxy. It is used by the majority of leading technology companies like Spotify and Netflix. It has taken over popularity from the long standing (and still good) Apache HTTPD server.
By container we mean virtualised container, that is a virtualisation of a server’s underlying operating system, which typically runs on a virtualisation of the physical server’s hardware (known as a virtual machine).
OK, but what does that actually mean? The basic idea is that you want to run multiple applications on a server, but you want to make sure they are isolated. So you package up your application along with everything it needs to run, including stuff that needs to be running/available on the operating system. Then you can run multiple isolated containers on a server, maybe they are copies of same application, different versions of the same application or different applications entirely.
This concept of isolation is compelling on its own, but there is more. Because you package it up including all dependencies as run the container “as is”, it means that if it works in a development environment you can guarantee it will work in another environment (e.g. production) without any modification (save environment specific configuration and any external dependencies). With this it means you there is no need for a human to spend time making the application work in production - as soon as it works in one environment it will work in all others.
Docker is a great choice for virtualised containers running on a Linux host operating system, it is one of the most actively used software frameworks in the world.
In our example we will use Docker tools to build our containers. But we won’t directly use Docker outside of our development environment, we will leverage an AWS service called EC2 Container Service (ECS) to manage Docker on our behalf.
Cloud IaaS and PaaS
Cloud is the future of technology. But I see it as a model not a location (e.g. it can be public or private). With cloud technology models you are buying (renting) a service that somebody has to design, build, operate and improve. The expectation is that you should be able to provision and decommission on-demand any number of that service that you require, without paperwork, phone calls or approvals (within sensible limits). All tasks should be executed immediately and automatically using self-service interfaces, e.g. web, mobile, API or command line (CLI).
The anti-pattern to this approach are services that you have to design, build, operate and improve. Examples include:
- Running your own datacentre;
- Managing physical infrastructure;
- Installing and maintaining industry standard platforms, e.g. DNS, Mail, Docker, Hadoop;
- Building or configuring monitoring tools to keep and eye on those services.
Doing any of these things well is hard work. So from a business perspective, if you aren’t directly making money from doing these things, then outsource it - pay somebody else to do these things better than your business can on its own.
Cloud service models are typically categorised as IaaS or PaaS. But like all thing technology, there is no official line between what service belongs in what category. To be honest there usually isn’t much point to separating them, but it can be useful when you are evaluating different cloud vendors.
IaaS (Infrastructure as a Service) is concerned with abstracting the low-level infrastructure things, for example:
- Physical Machines;
- Virtual Machines;
- Networking (including core services like Load Balancing).
PaaS (Platform as a Service) focuses on a layer above, for example:
- Message Queues;
- Distributed Container Virtualisation;
- Centralised Logging;
- Object Storage;
- Push Messages;
- Big Data Platforms (e.g. Hadoop/Spark);
- Identity Management.
The list goes on and will be forever growing. These are the really valuable services that can drastically reduce the complexity of running the latest technology.
In our example we are going to make use of the following IaaS solutions within AWS:
- Autoscaling Virtual Machines: EC2 (with Autoscaling Groups);
- Virtual Network: VPC (Virtual Private Cloud;
- DNS: Route 53;
- Load Balancer: Elastic Load Balancer.
- Block Storage: EBS;
Since this is a fairly simple example, we don’t need many PaaS solutions, but here are the useful ones we need right now:
- Virtualised Container Platform: ECS for managing our Docker containers.
I could add logging, auditing, monitoring, etc to this, but it would needlessly complicate this example.
I will go through the details on how each of these IaaS and PaaS solutions will be used in the last section of this article.
“Insanity: doing the same thing over and over again and expecting different results” Attributed to Albert Einstein.
Humans are just bad at doing the same things over and over again. Machines on the other hand are very good at it. In the world of software engineering we need to deploy our efforts to multiple environment types, e.g. dev, test, staging and productions. It is further reinforced in the cloud model, because once we have finished with an environment we destroy it, so it doesn’t cost us any more than for the time we need to run it for. So all this means we need to get really good at automation.
This automation will allow us to effectively run a one line command which will then go on to provision (or decommission) an environment from scratch in an extremely short amount of time. And each time we run the exact same command we should see the exact same results (at least most of the time - we’ll come on this later).
There are many tools out there in the automation space, like with most things, there are some broad categories that they unofficially align towards:
- Code - IDEs;
- Build - Source control, Continuous Integration;
- Test - Unit and integration testing;
- Package - Artifact repositories;
- Release - Change management, release orchestration;
- Configure - Configuration management, service discovery;
- Monitor - Logging, alerting.
For example you have Packer which is good at building VM images, Terraform for provisioning infrastructure, Chef/Puppet/Ansible for configuring VMs. But many sit in multiple categories.
Without getting into a debate on which tool is best for what purpose. For this example, I’m going to use the following tools:
- Build: SBT;
- Test: ScalaTest;
- Package: Docker;
- Release: Ansible;
- Configure: Ansible.
I’m not going to bother with monitoring for this example, although you obviously would for a real service. You can use whatever IDE you like, I use IntelliJ, but there is absolutely nothing specific to it in this example you could easily use
You can also use your own tools or even your own application. As long as you end up with a working Docker image, the Ansible automation will do the rest without modification.
Continued on Part 2.