Ansible tips

2014-10-13 by Deni Bertović

Here at GoodCode we’re big fans of Ansible. We use it to provision our production servers as well as our dev/vagrant instances. We’re also big fans of docker containers and Ansible fits very nicely with that as well.

While working on a couple of projects, we’ve come across various small pitfalls that could have been avoided if we knew what we know today. So we decided to document small tips we’ve learned so that others may avoid the troubles we’ve had.

It’s worth mentioning that this article isn’t a tutorial on how to use Ansible. There is plenty of documentation on the official Ansible docs about that. We definitely recommend you read through all of it, especially the “Ansible Best Practices” article.

Tip #1 – Use more roles

Although you can write your playbooks in one single yaml file, it’s very much considered a bad practice. And for good reason: It’s harder for someone new to read and it’s harder to maintain. The first thing you need to think about when designing your playbooks (ie. for deloying your webapp) is trying to split the various components in roles, no matter how small a particular role may be. For instance, in a typical webapp playbook you would have these roles:

  • Common
  • Nginx
  • App server
  • Database
  • Memcached

The Common Role is something where you would probably put your most basic prerequisites that you like having or need on your machines (ie. vim, build-essential, git…etc). The other roles are pretty self explanatory as they describe a separate service that your stack requires.

You can then include the roles in your site.yml file like so:

- { role: common, tags: "common" }
- { role: nginx, tags: "nginx" }
- { role: appserver, tags: appserver }
- { role: database, tags: database }
- { role: memcached, tags: memcached }

The benefit of doing this is that you can re-run your playbook for a specific role only:

ansible-playbook site.yml --tags=database

This is increasingly useful in the beginning when still fine tuning the playbook when you know there is no reason to re-run the entire playbook as you just want to tweak the database. It is beneficial later on as well as you can easily upgrade separate roles without the need to re-run the entire playbook. In principal a role may depend on the previous roles to have run already, but in practice this is only true for roles like “Common”, and you don’t couple you’re roles that much.

Tip #2 – Use more includes in roles

Sometimes however, grouping tasks into roles isn’t enough. Imagine that within a role you need to group tasks into somewhat of a “sub-role”. This can be done using includes like so:

file: roles/database/tasks/main.yml

- include: database_install.yml tags=database_install
- include: database_init.yml tags=database_init

This approach enables you to re-run just part of a role like so:

ansible-playbook site.yml --tags=database_init

While still enabling you to re-run the complete roles as well:

ansible-playbook site.yml --tags=database

This is useful for situations where you want to initialize lots of databases as time goes on (via external variables for instance) but don’t want to go through the install procedure at all because you know the database is in fact installed and configured.

Tip #3 – Use Ansible Modules

Sometimes when you want to do something quickly and are not sure how to do it correctly you will decide to do it manually via the shell module like so:

- name: Installing node modules via shell
  shell: npm install some_gem chdir=/home/user/{{ my_user }}

This is a bad idea most of the time as for 99% of the cases there will be an Ansible Module that does this for you. It just takes a bit of time to find it in the index and get familiar with it.

For instance the above example rewritten using the npm module:

- name: Install some node module
  npm: package=some_package global=yes

There are countless other examples where we were calling out to the shell instead of checking for a module first. Some examples are: gem, pip, rabbitmq_user, postgresql_user and postgresql_db modules.

Tip #4 – Don’t wait for user input

On more then one occasion we considered it was a good idea to use the Pause Module sprinkled randomly in our playbook/roles. This is a bad idea because it slows down the execution of the playbook – you will forget where those “waiting for user inputs” are and will start the playbook, go make coffee, only to come back later and see it stuck on a pause block somewhere in the middle of the playbook. A much better idea is to use var_prompt at the beggining of the playbook.

Tip #5 – Use defaults for var_prompts

When using var_prompts it’s a really good idea to set default values for each prompt. For instance, let’s say you require the user to enter paths for some certificates that need to get uploaded to the nodes that we are provisioning. Var prompts get prompted every time you run the playbook even if you just run a specific role or tag, so it gets tedious very fast to enter those paths every time. A better way is to define defaults like /tmp/ssl/ssl1 so anyone who’s running the playbook just makes sure that directory structure is in place and they can just skip the var prompts with a few strikes on the enter key. Little things likes this prove very handy especially while debugging when you just want to re-run a specific task and don’t need to bother with user input.

Tip #6 – Use cloud modules

Ansible supports a vast set of modules that you can use out of the box and among them are a specific set of modules designed to help you provision cloud instances on various providers such as Amazon EC2 or Digital Ocean and others.

Even if you don’t need to be elastic (scale up and down) with your node instances it’s a good idea to use the power of these modules to get your nodes up and running, even you just need one instance. As the cloud modules make this task so easy, there’s no reason to manually start nodes on various cloud providers and fetch their IP’s to put them into the ansible invetory file. As with every other tip in this post the goal is minimize the amount of “human input” as much as possible and automate everything.

Deni Bertović
We’re small, experienced and passionate team of web developers, doing custom app development and web consulting.