Learning Ops Tooling, Part Two - Starting with Ansible

Learning Ops Tooling, Part Two - Starting with Ansible

In my last post I talked about setting up a Vagrant script to create a VM in which to host my blog. The next step I want to discuss is how to install Ansible and, in turn, use that to pull down all the dependencies required to run the blog engine.

Ansible is another tool from Hashicorp that is designed to let you automate the provisioning of a server via what it calls playbooks. A playbook is a simple yaml file the denotes a number of tasks that are to be run on the server. Then each task makes use of a module and a set of variables to accomplish that task.

When you install ansible you get 100s of predefined modules for completing task such as adding users to mysql database or setting up the firewall or even copying files from your local machine to the VM.

Installing Ansible

In order to install ansible on the VM we created using vagrant we first need to ssh on to it using the vagrant ssh ghost command. We can then update our apt-get repositories:

vagrant@ghost:~$ sudo apt-get update
Hit:1 http://archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]
Fetched 24.7 MB in 7s (3430 kB/s)
Reading package lists... Done

And then use it to install ansible:

vagrant@ghost:~$ sudo apt-get install ansible
Reading package lists... Done
Building dependency tree
Reading state information... Done
Setting up ansible ( ...
Setting up python-selinux (2.4-3build2) ...

This will bring down a number of dependencies with it, including python. But that's it. Once this command has run, Ansible is now installed on your VM and you can log out by typing exit.

Installing Ansible, the sensible way

Excuting the instructions above work fine. But what happens if you destroy your VM and want to rebuild it? Well you're going to have to manually reinstall ansible again and that's quickly going to become annoying. What we really want is a way that when we recreate our VM it comes with ansible pre-installed.

To accomplish this we can make use of a feature in Vagrant that will run a script against your VM once it's been created. You can use it by adding the line ghost.vm.provision as below:

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

    config.vm.define "ghost" do |ghost|
        ghost.vm.box = "ubuntu/xenial64"
        ghost.vm.hostname = "ghost"
        ghost.vm.network "private_network", ip: ""
        ghost.vm.provision :shell, path: "bootstrap.sh"


This tells Vagrant to run a shell script usimg the file bootsrap.sh, which we need to create. Then add the following:

apt-get update
apt-get install -y ansible

The -y argument passed into the install command just tells it to silently accept any agreements.

Now at this point you may be asking "Can't you just add the all the commands to set up the blog in here?". Well you could. If there's one thing I'm starting to see it's that there is more than one way to do anyting in the ops world, each with advantages and disadvantges.

The advantage of using Ansible is that I can run it as many times as I like once the server has been created and if anything has been removed or a setting changed then Ansible will revert it back to how it was defined in the playbook. Also, although it doesn't apply here, with ansible you can run the same playbook against multiple servers. Perhaps if I wanted a fail over...

Connecting to your VM with Ansible

To connect to your VM with Ansible you first have to make sure that it is also installed on your local machine. Again, I'm on a mac so I use brew to install it, but the instructions are as above.

Once it's installed you need to create what Ansible calls an inventory file. This is where you list all the servers that you want to manage via Ansible. You can then group and sub group them so you can execute a playbook against multiple servers at the same time. I'll show an example of that later but for now we just have the one:

ghost ansible_ssh_host= ansible_ssh_user=vagrant ansible_ssh_pass=vagrant

I've called the file Inventory.

Here we are telling ansible we have a server located at "" and that we want to log on, via ssh, using the username "vagrant" and the password "vagrant". We have also given this server the name of "ghost".

Keeping your password as plain text in this file isn't the best idea, but because we are only testing at the moment then it's ok for now. Ansible has ways to encrypt your passwords and that's something for me to look into in the future.

To check it's all working we can execute a ping module against it from the command line:

ansible -i inventory all -m ping

Here I'm telling ansible to run using the inventory file, that I created earlier, with the -i argument. I'm then saying to run against all the servers listed in the inventory file. You could also just specify to run against ghost. And then finally I'm telling to run the ping module which will just return pong if its successful:

$ ansible -i inventory all -m ping
ghost | SUCCESS => {
    "changed": false,
    "ping": "pong"

And that's it for now. Next we'll need to define the playbook to install my blog.

Bonus Material

Running Against Multiple Servers

Ansible wouldn't be much good if you could only run it against a single server. Within the inventory file you can define as many servers as you like:

ghost ansible_ssh_host= ansible_ssh_user=vagrant ansible_ssh_pass=vagrant
ghost_db ansible_ssh_host= ansible_ssh_user=vagrant ansible_ssh_pass=vagrant

But you can also group them and run your ansible script against a group:


And then group those groups under groups:


In this case when we define a group of groups we need to suffix the group name with :children so it knows to look for groups and not servers.

And then just run the command:

$ ansible -i inventory everything -m ping
ghost | success >> {
    "changed": false,
    "ping": "pong"

ghost_db | success >> {
    "changed": false,
    "ping": "pong"

Using Variables in the Inventory File

You may have noticed above that we are using the same username and password for both the webserver and db server. It'd be a bit of a pain if we had to define this for every server. Instead we can specify a series of variables for a given group using the following code:


And ansible will use this for all servers in the group.