The Ansible Way

When I talk about how to develop automation solutions with Ansible, I begin by highlighting the philosophy behind its design. All Ansible best practices relate back to this thinking in one way or another.



Complexity kills productivity

That’s not just a marketing slogan. We really mean it and believe it. We strive to reduce complexity in how we’ve designed Ansible tools and encourage you to do the same. Strive for simplification in what you automate.

Optimize your Ansible content for readability

If done properly, it can be the documentation of your workflow automation.

Think declaratively

Ansible is a desired state engine by design. If you’re trying to “write code” in your plays and roles, you’re setting yourself up for failure. Our YAML-based playbooks were never meant to be for programming.

Ansible is like the Swiss Army Knife of DevOps

Ansible is capable of handling many powerful automation tasks with the flexibility to adapt to many environments and workflows. Not all approaches are created equal though. Don’t let yours undermine the simplicity and power of Ansible.

Enough philosophy though. Let’s get down to brass tacks. Here I’ll cover some of the most important and impactful best practices you can apply to your work developing automation solutions with Ansible.

1. “Name” Your Plays and Tasks

Always name your plays and tasks. Adding name with a human meaningful description better communicates the intent to users when running a play. Consider this example play and its standard Ansible output.

YAML

- hosts : web tasks : - yum : name : httpd state : latest - service : name : httpd state : started enabled : yes

PLAY [web] ****************************************************** TASK [setup] **************************************************** ok: [web1] TASK [yum] ****************************************************** ok: [web1] TASK [service] ************************************************** ok: [web1]

Without knowing what’s in the play, another user can see the play is doing something with yum and something with service, but what exactly? They aren’t even sure what the purpose of the play is except for maybe the filename or some external documentation source. Let’s look at that same example with name declared on the play and all of its tasks.

YAML

- hosts : web name : installs and starts apache tasks : - name : install apache packages yum : name : httpd state : latest - name : starts apache service service : name : httpd state : started enabled : yes

PLAY [install and starts apache] *********************************** TASK [setup] ******************************************************* ok: [web1] TASK [install apache packages] ************************************* ok: [web1] TASK [starts apache service] *************************************** ok: [web1]

Much better. It is now much more apparent what this playbook is doing right in the output of your playbook run. It also aids the usage of the --list-tasks switch in ansible-playbook.

2. Use Prefixes and Human Meaningful Names with Variables

Ansible has a powerful variable processing system that collects metadata from various sources and manages their merge and context as a play runs on your hosts. A lot of effort goes into making that power as easy and transparent as possible to users.

Variable scoping and namespacing was intentionally limited and shallow by design to make it easier to understand and debug.

A limitation to this approach is that you run the risk of variable collisions if two different pieces of information, like a port number, are stored in the same variable name. (Having a web server listen to port 22 is not the sort of fun you want to have.)

As a best practice, we recommend prefixing variables with the source or target of the data it represents. Prefixing variables is particularly vital with developing reusable and portable roles.

apache_max_keepalive : 25 apache_port : 80 tomcat_port : 8080

You can also dramatically improve the readability of your plays for a bit of extra verbosity with using human-meaningful names that communicate their purpose and usage to others or even yourself at a later date.

3. Use Native YAML Syntax

At its core, the Ansible playbook runner is a YAML parser with added logic such as commandline key=value pairs shorthand. While convenient when cranking out a quick playbook or a docs example, that style of formatting reduces readability. We recommend you refrain from using that shorthand (even with YAML folded style) as a best practice.

Here is an example of some tasks using the key=value shorthand:

YAML

- name : install telegraf yum : name : telegraf -{{ telegraf_version }} state=present update_cache=yes disable_gpg_check=yes enablerepo=telegraf notify : restart telegraf - name : configure telegraf template : src=telegraf.conf.j2 dest=/etc/telegraf/telegraf.conf

notify: restart telegraf - name : start telegraf service : name=telegraf state=started enabled=yes

Now here is the same tasks using native YAML syntax:

YAML

- name : install telegraf yum : telegraf -{{ telegraf_version }} state : present update_cache : yes disable_gpg_check : yes enablerepo : telegraf notify : restart telegraf - name : configure telegraf template : src : telegraf.conf.j2 dest : /etc/telegraf/telegraf.conf notify : restart telegraf - name : start telegraf service : name : telegraf state : started enabled : yes

Native YAML has more lines; however, those lines are shorter, reducing horizontal scrolling and line wrapping. It lets the eyes scan straight down the play. The task parameters are stacked and easily distinguished from the next. Native YAML syntax also has the benefit of improved syntax highlighting in virtually any modern text editor out there. Being native YAML, editors such as vim and Atom will highlight YAML keys (module names, directives, parameter names) from their values further aiding the readability of your content. Many of our own docs use this shorthand for legacy reasons though we’re progressively changing that. (Documentation pull requests accepted.)

4. Use Modules Before Run Commands

Run commands are what we collectively call the command, shell, raw and script modules that enable users to do commandline operations in different ways. They’re a great catch all mechanism for getting things done, but they should be used sparingly and as a last resort. The reasons are many and varied.

The overuse of run commands is often a symptom of TL;DR in Ansible and common amongst those just becoming familiar with Ansible for automating their work. They use shell to fire off a bash command they already know without stopping to look at the Ansible docs. That works well enough initially, but it undermines the value of automating with Ansible and sets things up for problems down the road.

The most important thing to consider is that these run commands have little logic to them and no concept of desired state like a typical Ansible module.

That shell that succeeded the first time you ran your play may fail the next time when something already exists. That’s unless you ignore_errors on that task. But how do you catch a real error like wrong permissions? Now you have to register the result of that first command and follow it with another task that implements conditional logic to check if an error occurred in the first and handle it.

Starting to get confused? Sounding complex? Right.

When you’re starting to write code in your Ansible play, then your descent into code hell is underway. As a best practice, always check the hundreds of Ansible shipping modules for what you need and use those first and run commands as a last resort.

Sometimes you may have a need not covered by an existing module and cannot avoid using a run command. Consider developing your own Ansible module.

Modules abstract users away from the complexity of implementation details and benefit from having full access to a proper programming language where they can do whatever heavy lifting is needed.

5. Clean Up Your Debugging Messages

When developing Ansible content, it can be useful to drop in a debug task to display the content of a variable while your play runs. That’s not so nice, even downright disconcerting, when your Playbook goes into production and some unwitting ops manager runs a play and sees your debugging messages on their screen.

That’s why it is a best practice to clean up those debug statement in your Ansible content.

In the past, you had to delete or comment out your debug tasks. That was a suboptimal approach though.

Starting in version 2.1, the Ansible debug module supports a verbosity parameter that suppresses output unless the play is being run with a sufficiently high enough verbosity level.

YAML

- debug : msg: "This always displays" - debug : msg : "This only displays with ansible-playbook -vv+" verbosity : 2

Now you users only see all of that debugging information unless they really want it.





Ready for more? Check out more insights and best practices in our Resource Library.