I'm a web developer in Norfolk. This is my blog...

An Introduction to Managing Your Servers With Ansible

If, like me, you’re a web developer who sometimes also has to wear a sysadmin’s hat, then you’ll probably be coming across the same set of tasks each time you set up a new server. These may include:

Provisioning new servers on cloud hosting providers such as Digital Ocean

Setting up Cloudflare

Installing a web server, database and other required packages

Installing an existing web application, such as Wordpress

Configuring the firewall and Fail2ban

Keeping existing servers up to date

These can get tedious and repetitive fairly quickly - who genuinely wants to SSH into each server individually and run the updates regularly? Also, if done manually, there’s a danger of the setup for each server being inconsistent. Shell scripts will do this, but aren’t easy to read and not necessarily easy to adapt to different operating systems. You need a way to be able to manage multiple servers easily, maintain a series of reusable “recipes” and do it all in a way that’s straightforward to read - in other words, a configuration management system.

There are others around, such as Chef, Puppet, and Salt, but my own choice is Ansible. Here’s why I went for Ansible:

Playbooks and roles are defined as YAML, making them fairly straightforward to read and understand

It’s written in Python, making it easy to create your own modules that leverage existing Python modules to get things done

It’s distributed via pip, making it easy to install

It doesn’t require you to install anything new on the servers, so you can get started straight away as soon as you can access a new server

It has modules for interfacing with cloud services such as Digital Ocean and Amazon Web Services

Ansible is very easy to use, but you do still need to know what is actually going on to get the best out of it. It’s intended as a convenient abstraction on top of the underlying commands, not a replacement, and you should know how to do what you want to do manually before you write an Ansible playbook to do it.

Setting up

You need to have Python 2 available. Ansible doesn’t yet support Python 3 (Grr…) so if you’re using an operating system that has switched to Python 3, such as Arch Linux, you’ll need to have Python 2 installed as well. Assuming you have pip installed, then run this command to install it:

$ sudo pip install ansible

Or for users on systems with Python 3 as the main Python:

$ sudo pip2 install ansible

For Windows users, you’ll want to drop sudo. On Unix-like OS’s that don’t have sudo installed, drop it and run the command as root.

Our first Ansible command

We’ll demonstrate Ansible in action with a Vagrant VM. Drop the following Vagrantfile into your working directory:

In this case the remote user is vagrant because we’re using Vagrant, but to manage remote machines you would need to change this to the name of the account that you use on the server. The value of private_key_file will also normally be something like /home/matthew/.ssh/id_rsa.pub, but here we’re using the Vagrant-specific key.

Note the hostfile entry - this points to the list of hosts you want to manage with Ansible. Let’s create this next. Save the following as inventory:

testserver ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222

Note that we explicitly need to set the port here because we’re using Vagrant. Normally it will default to port 22. A typical entry for a remote server might look like this:

example.com ansible_ssh_host=192.168.56.101

Note also that we can refer to hosts by the name we give it, which can be as meaningful (or not) as you want.

We called Ansible with the hosts set to all, therefore every host in the inventory was contacted. We used the -m flag to say we were calling a module, and then specified the ping module. Ansible therefore pinged each server in turn.

You could then call the following to run the uptime command on all the servers in the webservers group:

$ ansible webservers -a 'uptime'

If you want to run the command as a different user, you can do so:

$ ansible webservers -a 'uptime' -u bob

Note that for running uptime we haven’t specified the -m flag. This is because the command module is the default, but it’s very basic and doesn’t support shell variables. For more complex interactions you might need to use the shell module, as in this example:

Here we specify that a particular package should be state=present or state=absent. Also, note the --become flag, which allows us to become root. If you’re using an RPM-based Linux distro, you can use the yum module in the same way.

Finally, let’s use the git module to check out a project on the server:

Here we check out a Git repository. We specify the repo, destination and version.

You can call any installed Ansible module in an ad-hoc fashion in the same way. Refer to the documentation for a list of modules.

Playbooks

Ad-hoc commands are useful, but they don’t offer much extra over using SSH. Playbooks allow you to define a repeatable set of commands for a particular use case. In this example, I’ll show you how to write a playbook that does the following:

Installs and configures Nginx

Clones the repository for my site into the web root

This is sufficiently complex to demonstrate some more of the functionality of Ansible, while also demonstrating playbooks in action.

Create a new folder called playbooks, and inside it save the following as sitecopy.yml:

Note the name fields - these are comments that will show up in the output when each step is run. First we use the apt module to install Nginx, then we copy over the config file and activate it, then we empty the existing /var/www and clone the repository, and finally we restart Nginx.

Also, note the following fields:

hosts defines the hosts affected

become specifies that the commands are run using sudo

We also need to create the config for Nginx. Create the files directory under playbooks and save this file as playbooks/files/nginx.conf:

If we had a playbook that we wanted to run on only a subset of the hosts it applied to, we could use the -l flag, as in this example:

$ ansible-playbook playbooks/sitecopy.yml -l testserver

Using these same basic concepts, you can invoke many different Ansible modules to achieve many different tasks. You can spin up new servers on supported cloud hosting companies, you can set up a known good fail2ban config, you can configure your firewall, and many more tasks. As your playbooks get bigger, it’s worth moving sections into separate roles that get invoked within multiple playbooks, in order to reduce repetition.

Finally, I mentioned earlier that you can use Ansible to update all of your servers regularly. Here’s the playbook I use for that:

This connects to all hosts using the all shortcut we saw earlier, and upgrades all existing packages. Using this method is a lot easier than connecting to each one in turn via SSH and updating it manually.

Summary

Ansible is an extremely useful tool for managing servers, but to get the most out of it you have to put in a fair bit of work reading the documentation and writing your own playbooks for your own use cases. It’s simple to get started with, and if you’re willing to put in the time writing your own playbooks then in the long run you’ll save yourself a lot of time and grief by making it easy to set up new servers and administer existing ones. Hopefully this has given you a taster of what you can do with Ansible - from here on the documentation is worth a look as it lists all of the modules that ship with Ansible. If there’s a particular task you dread, such as setting up a mail server, then Ansible is a very good way to automate that away so it’s easier next time.

My experience is that it’s best to make an effort to try to standardise on two or three different stacks for different purposes, and create Ansible playbooks for those stacks. For instance, I’ve tended to use PHP 5, Apache, MySQL, Memcached and Varnish for Wordpress sites, and PHP 7, Nginx, Redis and PostgreSQL for Laravel sites. That way I know that any sites I build with Laravel will be using that stack. Knowing my servers are more consistent makes it easier to work with them and identify problems.