10 Things you should start using in your Ansible Playbook

Abhijeet Kamble
9 min readFeb 17, 2018

I have been working a lot on Ansible these days as we are working on migrating all our org. level Windows deployments from present tool to Ansible. We wrote many custom roles to support windows deployments and came through many interesting Ansible features. Out of many features I decided to share 10 interesting things that you should start using in your Ansible Playbook. I hope you will like these features and start using it in your day to day playbooks. So, Let’s start with the features.

1. with_items:

Sometimes we want to do many things with single tasks like installing many packages with the same tasks just by changing the arguments. This can be achieved using the with_items clause.

By using the with_items, ansible creates a temporary variable called {{item}} which consist the value for the current iteration. Let’s have some example to understand this. We will install few packages with below playbook.

# Installing packages individually ( Slower Process )- name: Installing Git
apt:
name: git
update_cache: yes
- name: Installing nginx
apt:
name: nginx
update_cache: yes
- name: Installing memcached
apt:
name: memcached
update_cache: yes

The above playbook will run 3 tasks each for installing individual package. Rather than specifying three different tasks, we can use with_items and specify the list of packages that we need to install.

# Installing Packages with one Task ( Faster Process )- name: Installing Packages
apt:
name: "{{ item }}"
update_cache: yes
with_items:
- git
- nginx
- memcached

Here, while executing the task “Installing packages” Ansible will read the list from with_items and install packages one by one. You can also use with_items with roles as well. So if you have any custom role defined and you want to execute that role multiple times, rather than defining it multiple times you can use with_items and just pass your elements.

2. Facts Gathering

In Ansible, Facts are nothing but information that we derive from speaking with the remote system. Ansible uses setup module to discover this information automatically. Sometime this information is required in playbook as this is dynamic information fetched from remote systems.

192.168.56.7 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"172.17.0.1",
"10.0.2.15",
"192.168.56.7"
],
( many more facts)...

It becomes a time consuming process in Ansible as it has to gather information from all the hosts listed in your inventory file. We can avoid this situation and speed up our play execution by specifying gathering_facts to false in playbook.

---- hosts: web
gather_facts: False

We can also filter the facts gathering to save some time.This case is mainly useful when you want only hardware or network information that you want to use in your playbook. So rather than asking for all facts, we can minimize this by only asking network or hardware facts to save some time. To do this, you have to keep gather_facts to True and also pass one more attribute named gather_subset to fetch specific remote information. Ansible supports network, hardware, virtual, facter, ohai as subset. To specify subset in your playbook you have to follow the below example.

- hosts: web
gather_facts: True
gather_subset: network

To specify multiple subsets , you can combine then using comma (ex. network, virtual)

- hosts: web
gather_facts: True
gather_subset: network,virtual

Sometimes there might be requirement for creating local custom facts on remote machines. This can be achieved by creating .fact file under /etc/ansible/facts.d/ location on remote machine. The .fact file can have JSON, INI or executable file returning JSON. For example, I have created a file called local.fact under /etc/ansible/facts.d/local.fact and defined with following value.

[general]
sample_value=1
sample_fact=normal

By default, you will get these facts whenever you gather fact on the remote server you defined this. If you want to filter the facts, you can use the below command.

ansible all -i local -m setup -a "filter=ansible_local"
192.168.56.7 | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"prefrences": {
"general": {
"sample_fact": "normal",
"sample_value": "1"
}
}
}
},
"changed": false,
"failed": false
}

3. any_errors_fatal

Sometime it is desired to abort the entire play on failure of any task on any host. This can be helpful in a scenario where you are deploying any service on group of hosts and if any failure occurred on any server should fail the entire play because we don’t want the deployments to be partial on any server.

---
- hosts: web
any_errors_fatal: true

The any_error_fatal option will mark all the hosts as failed if fails and immediately abort the playbook execution.

4. max_fail_percentage

Ansible is designed in such a way that it will continue to execute the playbook until and unless there are any hosts in the group that are not yet failed. Sometimes it becomes issue while doing deployments because its not maintaining consistency. Consider scenario’s where you have 100+ servers attached to load balancer and you are targeting for zero downtime with rolling updates. As Ansible supports rolling updates and you can define the batch size( Batch size is nothing but the number of servers you want to target for deployment in rolling updates, you can also provide %), you have to monitor these deployments for failure and take decision when to call it off. max_fail_percentage allows you to abort the play if certain threshold of failures have been reached.

---
- hosts: web
max_fail_percentage: 30
serial: 30

If 30 of the servers to fail out of 100. Ansible will abort the rest of the play.

5. run_once

There are condition where we have to write our playbook in such a way that will run some tasks or perform some action only on single host from group. If you are thinking of Handlers do the same thing. Think twice because even though multiple tasks notify to perform some action, handlers will only gets executed after all tasks completed in play but on all hosts and not on single.

---
- hosts: web
tasks:
- name: Initiating Database
script: initalize_wp_database.sh
run_once: true

To achieve this, Ansible provided run_once module, which will run only on single host from group of hosts. By default, Ansible will select the first host from the group to execute. This can also be used with delegate_to to run the task on specific server. When executed with serial, task marked as run_once gets executed on one host from each batch.

6. Ansible Vault

There are scenarios where we have to keep sensitives information in playbook, like database username and password. Keeping such sensitive information open is not a good idea as we are going to keep our playbook in version control system. To Keep such information, Ansible provided Vault to store such information in encrypted format.

Ansible Vault can be used to encrypt binary files, group_vars, host_vars, include_vars and var_files. Ansible vault can be used with command line tool named ansible-vault.You can create encrypted file using following command.

ansible-vault create encryptme.yml

If you are running this command for first time, it will ask you for setting vault password. Later you have to provide the same while running ansible-playbook command using--ask-vault-pass.

To encrypt any file, you can use the following command

ansible-vault encrypt filename.yml

This will encrypt all your content and can only be decrypted using vault password.

Since Ansible 2.4 you can have multiple vault passwords. The reason for allowing multiple passwords is because you cannot encrypt dev and prod passwords using single vault password. The passwords for environments dev will be different, than prod environment. Now, if anyone who has dev vault password would not be able to decrypt prod password which makes it more secure. The Vault credentials are encrypted through vault-id. Please follow below examples for creating multiple vault passwords.

ansible-vault --new-vault-id dev --new-vault-password-file=development encrypt dev_config.yml
ansible-vault --new-vault-id prod --new-vault-password-file=production encrypt prod_config.yml

The vault-id is used for decrypting the passwords. The vault-id dev is used to encrypt dev_config.yml whereas vault-id prod is used to encrypt prod_config.yml. To decrypt, we use the following command.

ansible-vault --vault-id prod@prompt decrypt prod_config.yml

This command will ask vault password for vault-id prod.

7. No_logs

In previous section, we covered how we can encrypt the data using Ansible Vault and share it publicly. But this encrypted data is exposed when we run the playbook in -v(verbose) mode. Anyone who has access to controller machine or Ansible Tower jobs, they can easily identify the encrypted data by running the playbook in verbose mode.

Playbook ran with verbose mode to show how encrypted data can be seen.

In above screenshot, you can see that the encrypted data is exposed in ansible_facts. To secure or censor such information, Ansible has provide a keyword named no_log which you can set to true to keep any task’s information censored.

---
- hosts: web
tasks:
- include_vars: group_vars/encrypted_data.yml
no_log: true
- name: Printing encrypted variable
debug:
var: vault_db_password
no_log: true

This way you can keep verbose output but hide sensitive information from others. This can also be applied to play but it becomes difficult for debug and not recommended.

Note that when debugging Ansible with ANSIBLE_DEBUG, the no_logs cannot stop ansible from showing the information.

8. tags

Sometimes while writing the playbook we never think about dividing the playbook logically and we end up with a long playbook. But what if we can divide the long playbook into logical units. We can achieve this with tags, which divides the playbook into logical sections.

Ansible Tags are supported by tasks and play. You can use tags keyword and provide any tag to task or play. Tags are inherited down to the dependency chain which means that if you applied the tags to a role or play, all tasks associated under that will also get the same tag.

---
- hosts: web

tasks:
- name: Installing git package
apt:
package: git
tags:
- package
- name: Running db setup command
command: setupdb.sh
tags:
- dbsetup

To run your playbook with specific tag, you have to provide command line attribute --tags and name of your tag.

ansible-playbook -i local site.yml --tags package

Above command will run tasks which are tagged as package and skip all other tasks. Sometimes you have to play complete playbook and skip some part. This can be achieved with--skip-tag attribute.

ansible-playbook -i local site.yml --skip-tags package

The above command will run all tasks and skip tasks tagged with package.If you want to list all tags, that can be done with--list-tag attribute.

Ansible also provided some special tags as always, tagged, untagged and all.

ansible-playbook -i local site.yml --skip-tags always

By default , ansible runs with--tags all which will execute all tasks.

9. command module idempotent (optional)

As we know that all modules are idempotent, which means if we are running a module multiple times should have the same affect as running it just once. To implement idempotency, you have to check module whether its desired state has been achieved or not. If its already achieved then just exit or else perform the action specified. Mostly all Ansible modules are idempotent but there are few modules which are not. Command and Shell modules of Ansible are not idempotent. But there are ways to make them idempotent. Let’s see how we can make them idempotent.

Command module runs same command again and again. To make it idempotent we can use the attribute create or remove. When used with create attribute, Ansible will only run the command task if the file specified by the pattern does not exists. Alternatively you could use remove, which will only execute the task if the file specified exists.

tasks:- name: Running command if file not present
command: setup_db.sh
args:
creates: /opt/database

As Ansible supports idempotent, make sure you use all such modules in your playbook to make your play idempotent so that re-running should be safe.

10. Debugging Playbook on Run

Debugging an ansible playbook is one of the coolest feature that ansible has introduced(in 2.1 version). While we develop any playbook sometimes we see failures and to debug we usually run the playbook again, identify the error that Ansible throws and modify the playbook and then rerun the playbook. What if your playbook takes 30 minutes to run and your play is failing for last few tasks and after debugging you will again run your playbook for almost 30 minutes. I guess that’s ideal way to debug, You can used the ansible debug strategy.

Ansible provides a debug strategy which will help to enable the debugger when a task fails. It will provide access to all features of the debugger in the context of failed task. This way if you encounter a failed task, you can set the values of the variables, update the module arguments and re-run the failed task with new arguments and variables.

To use the debug strategy in your playbook, you have to define the strategy as debug.

---
- hosts: web
strategy: debug
tasks: ...

With Debugger, it provides multiple commands to debug your failed tasks.

P task/host/result/vars ->Prints the value to executed a module

task.args[key] = value — upgrade the module arguments

vars[args]=value — set argument value

r(edo) — run the task again

c(continue) — Just continue

q(uit) — quit from debugger

Let’s run the following playbook to demonstrate this feature.

---
- hosts: web
strategy: debug
vars:
package: "nginx"
tasks:
- name: Install Git Package
apt:
name: "{{ new_package }}"
Video showing demo of how you can use debug for above playbook

Note: the debugger doesn’t work with debug module, so if you have any undefined variables it will not work.

Conclusion:

So these are the few things that you should start using in your day to day ansible playbook. Hope you liked it.

--

--

Abhijeet Kamble

DevOps Consultant, ALM Consultant, CloudOps Consultant