Provision Dev Environments with Ansible

The Problem

Deploying a code project on a local machine tend to be a tedious task. For one it requires right the runtimes and libraries installed correctly and cleanly, in addition to build tools and further toolchains, and on top of that a IDE and other development tools are indispensable to rapidly develop on a project.

Setting up all these things by hand is a tedious and error-prone process, and it gets even worse if the development is remote, for example in a open source setting, where a contact for help in case of misconfiguration is not available. Countless hours can be spend to find bugs, even before any actual coding started. It also needs some deep knowledge about the used tools, which is of course good to have, but not feasible for every case.

Even worse, a small deviance in the configuration can easily be overseen but lead to a change of unexpected behavior, that can get difficult to track down.

Old solutions

To tackle this problem, three different solutions are most common, each have their own drawbacks and advantages.

The Textfile

The simplest way is to setup an installation guide either in the Git Repository or an adjuvant Wiki, and they usually consists of different steps to follow for creating an uniform development environment. It often comes with more or less helpful commentary and in ideal case includes solutions for different cases, like numerous operating systems.

They can be as flexible as it gets, and they can precisely follow the needs of the project. The should be the easiest human-readable and understandable of the presented options.

The draw back of the text best guide is that they tend to be outdated the moment they are written. They can easily create errors in both writing and following them and the only real way to test them is to follow them manually. Their overall quality heavily depends on the technical writing skill of the author. To catch as many edge-cases as possible they need to get verbose and confusing.

The Script

The next step to formalize the written down steps into a scripting language. Most commonly used for that is bash-script, but variants of perl, python, batch etc. can be found as well. In the ideal case it is sufficient to download the script and executes it on the local machine, as the computer executes all needed steps automatically.

These approach is much less error prone, as chances for operating error are almost nil, and the script can be tested through various methods. It also requires minimal work and usually also very little preset infrastructure. It can be used in various automation settings.

The main problem with scripts is that they work in a imperative fashion and are hard to port into different cases. Bash is notoriously bad at keeping code safe and flexible. Creating a script that runs on as many machines as possible while not accidentally breaking things almost automatically will result in code that is hard to read and hard to change.

The Virtual Machine

While its usually not practical to just copy the required files from one machine to another, with virtualisation these constraints are lifted. Nowadays we typically see two approaches to this, either in the form of classic VMs deployed to tools like quemu or vagrant or in omitting the hypervisor by utilizing docker or other Linux container. Both solutions and their differences justify their own articles, but the general experience stays the same.

The idea is pretty daunting. It works on my machine, so here is my machine. And it indeed circumvent many pitfalls the other solutions have. In best case its downloading the needed image, start it with the correct tool and viola, we have the dev environment exactly as envisioned.

The first problem arises when we realize that we need the correct tool on the machine. While docker and vagrant made things easier, to this day getting an VM running can be frustrating. Even with these new tools we have to teach new developers how to operate them correctly and safely, which reinforces the original problem. We also have to provide architecture to supply our Images as well as building them. All this creates a huge load of complexity to our project that might be not appropriate.

The Provision Manager

After I lined out the problem we want to solve and how traditional ways might fail us, I want to present to you my prefered alternative, by using an configuration manager.

Configuration Managers are well tested tools in the server infrastructure provisioning field of DevOps. The most common used are Salt, Chef, Puppet and Ansible, and I will focus on the latter for this post.

While Ansible is usually used to setup the right configuration for our remote machine to run the code we want to run there, it can easily do the same for our local machine as well, and its an quite powerful way to control our development environment.

What is Ansible

The basic idea of Ansible is to read-in a yaml file containing desired states, compares these against the actual state of the given machine and executes commands to achieve the desired state. All what is needed for that is a local Ansible installation and ssh access to the remote machine, as well as said yaml file.

The important components of Ansible are the CLI, the playbook yaml file, the inventory, roles and modules. The CLI uses python which should be a given on any modern unix machine and not too obscure on other fringe operating systems. To see it running enter

ansible --version

I assume version 2.8 and later for this blogpost.

All steps we want to execute are stored into a so called playbook, which follows an preset structure. We can define the machines we want to run it on, define and load different variables and set the tasks done in this play. The command we use to execute a playbook is

ansible-playbook playbook.yml -i inventory_file

In order to do the tasks we need to use the modules provided by ansible or the community. There is a long list a modules that the have at our hands, in addition to any custom module we write or add. Note that the structuring of modules in ansible changed in the new version 3.0.

Roles can be seen as a set of ordered modules to achive a generalized goal. A typical role would be something like “Install and configure nginx” or “Setup an firewall”, we usually give variables into a role that control the behavior and change the outcome. Ansible provides a gigantic repository of user created roles in ansible galaxy, however I advice you to be careful to what roles you choose since the quality can vary widely.

With these we have everything together we need to start building our dev environment setup tool.

Creating a Dev Environment

So, to provide a dev environment to other developers, we first and foremost need to create an new playbook. But before that, we should make a short note what are the tasks that need to be done.

The example

In the following we develop the playbook for an example project. It is a small react frontend which we want to edit in VS Code. We want to test-deploy the project in a docker container and be able to push it to aws ecr. For that, we need the following steps done:

  1. Provide node with npm
  2. Provide react with the version of the project
  3. Provide docker
  4. Clone the project to a preset directory
  5. Provide vs-code including all needed plugins and settings
  6. Provide aws-cli
  7. [Optional] Setup credentials and configure aws-cli to push to ecr

Now we have a plan.