Allgemein

Ansible Best-Practices

I’m working with Ansible since March 2014, starting with version 1.1. At work I wrote many playbooks and roles to configure operating systems, applications and continuous delivery pipelines.
I managed AWS instances, VMWare-Cluster and Xen-Hosts with Ansible.

I also maintain some Open Source Ansible roles on GitHub and wrote some Dockerfiles for operating system images that include Ansible (mainly to test the roles).

Over time my team and I gathered some best practices that we try to follow when writing and running Ansible code in production. The following post will show you what I think are these best practices and why.

Of course I’m not the first to write about Ansible Best-Practices. There are other resources that were very informative and helped me immensely:

On writing Ansible playbooks

Name your tasks ands plays

When writing tasks and plays in Ansible naming them is optional. However you should always give useful names to your tasks ands plays. When you run a playbook without named tasks, you’ll see the following output:

When trying to debug failed tasks it’s really helpful to actually know what task failed and what the task should have been doing. Assigning names to your taks will give the following output.

That’s more helpful, isn’t it?

Variables in your task names

Try to be expressive when writing task names. Include as many information as necessary. A good way to do this is to use variables in your task names. For example if you want to determine the host a task is currently running against, you can include a variable in your task name.

Suppose you have the following task, that includes the inventory_hostname variable in its name:

Running it on a host will produce the following output:

That’s a neat way to determine what the playbook is currently doing.

Omitting superfluous information

One thing you don’t have to do, is to include the name of the role in the task-name. That’s done automatically. See here:

Playbook:

Tasks-file of the role:

And the output of the play:

Observe how it includes the role-name in the task description without it being explicitly defined!

Use Modules Before Run Commands

This one should be obvious, but for people that come from a classic admin-background and are new to Ansible it often is not:
Ansible is batteries-included and comes with more than 1000 modules to help manage systems. Most times it’s not needed (nor useful!) to fall back to shell commands instead of using modules.

Here’s a simple example. Instead of doing this:

do this:

Ansible is helpful in detecting when you should use modules instead of commands. It detects these uses and prints a warning. When running the above task with command Ansible prints:

Use copy or template-module instead of lineinfile

It’s often necessary to change single lines in files. When having to do this, many people will use the lineinfile or blockinfile modules to change the file.

However over the years I learned that most times you should in fact not use these modules when wanting to changes files. You should rather use the template– or copy-module to manage not only single lines but the whole file itself.

The reason for that is twofold. First when using lineinfile you often have to use regex. Now you have two problems. More seriously, using regex is often okay, if the regex is simple (or you and the people using your playbooks are experienced with regex)!
The second reason is that you have to know and remember that this particular line in this config-file is managed by Ansible. If you manage the whole file with template you can use the ansible_managed-variable to show that the file is under Ansible control.
Here’s an example. Instead of this:

use this:

or this:

with the template file looking like this:

Bonus: you can use a variable for the selinux-state and simply change it on servers where selinux should not be in enforcing state.

Be explicit when writing tasks

When I’m saying that you should be explicit when writing Ansible tasks it’s best to use an example to show what I mean.
Instead of writing this:

better write it like this:

Again there are two reasons for this. The first is of technical nature: When you don’t explicitly declare the owner and group of the file, the owner will be the user that executed Ansible. That’s something that is not always desirable and can be easily avoided by being explicit.

The second reason is more of an organizational or “people-reason”. When people use your playbook or role, they may not always know the defaults of the modules you use or what you want to achieve with the tasks. When being explicit in your tasks, there’s less room for guessing and interpretations.

On documenting tasks

Naming your tasks is important to understand what they are doing but often it is more important to document why the task is doing what it does. If it’s not directly obvious what the task does, simply write some comments on top of the task to explain in more detail what’s happening and why:

If you have to use the command , shell or raw -modules instead of the “correct” modules, document why you cannot use the correct modules:

Thanks mikeoquinn for this suggestion!

How to write variables

Prefix your variables

There are some things you should consider when writing variables for your roles. The first thing is that you should prefix them with the name of the role. This makes it easier to know where the variable is used.

Here’s an example. Imagine you’re writing a role to install and configure the Apache web-server (you probably don’t have to). The role is named apache. Now you want to create a variable that configures the default Listen-port.

You’ll probably do it like this:

However you should do it like this:

Other than the reason mentioned earlier, there’s no ambiguity here. You definitely know that this variable belongs to the apache-role. There could be another role for some other kind of software that also defines a listen-port. With prefixed variables this is not a problem since variables have their own namespace now.

By the way, Puppet and Chef are on the advantage here, having namespaces for their roles. Ansible is not designed this way.

On writing and using variables

If you use a variable in Ansible it has to be quoted.

The following example won’t work:

This however works:

You could also use single ticks and omit the spaces between the curly braces and the variable name. However I found the above to be the most readable style. The most important thing is to stick to one style.

Do not show sensitive data in Ansible output

If you use the template-module and there are passwords or other sensitive data in the file, you do not want these to be shown in the Ansible output. That’s what the no_log-option is for. If added to a task, the output will not be logged.

Here’s an example playbook:

Without no_log: true the output will look like this:

With no_log: true it will look like this:

To keep sensisitve data in your playbooks and roles secret, use ansible-vault. There’s extensive documentation from Ansible with good examples so I won’t cover this topic here.

On writing and (re-)using roles

Before writing playbooks and roles it’s always a good idea to check if somebody else already did the work for you. For most common software there’s already a role in Ansible Galaxy. When searching for roles there, sort for Stargazers (and maybe downloads) to find the most popular (and hopefully well maintained) roles.
There are some people and organizations that provide many high-quality roles. geerlingguy, jdauphant, ANXS and (shameless plug) dev-sec provide some great roles.

When you create your role, use ansible-galaxy init to create the initial directory layout and stick to it. Then, if you follow all best practices mentioned here, your roles should be good to publish them on Ansible Galaxy and Github.

On documenting roles

When documenting roles, it’s best to use the template created by ansible-galaxy init. There you have to describe the role and its function, list and explain the variables used, the needed dependencies and provide examples. I always try to add some more documentation of the variables in the form of a table, providing the variable name, the default value and a explanation of the variable:

Name Default Value Description
network_ipv6_enable false true if IPv6 is needed
ssh_remote_hosts [] one or more hosts and their custom options for the ssh-client. Default is empty. See examples in defaults/main.yml.
ssh_allow_root_with_key false false to disable root login altogether. Set to true to allow root to login via key-based mechanism.

Other best practice considerations

The Ansible directory structure

When structuring your Ansible directory, you’re really free to do what you want. Ansible provides some sane examples in its documentation. This directory can also be a git-repository that gets used by Jenkins or AWX.

In every project we try to use the same structure which looks something like this:

The ansible.cfg has mostly default values. The ones that need to be changed to accompany the above directory structure are the following:

There are more topics I’d like to cover, and I’ll update this article when I wrote down my thoughts on these topics:

  • mono-repo vs. one repo a role
  • encryption
  • Testing roles
  • On making every value a variable
  • and auditing.

2 comments

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.