If you’ve ever spent ages waiting for an Ansible playbook to get through a bunch of tasks so yours can be tested, then this article is for you.
Ansible can be pretty tedious to debug and obscure to develop at times (“What’s the array I need to access the IP address on the en2 interface again?”), so I went looking for various ways to speed up the process, and make it easier to figure out what is going on.
Eventually I found five tools or techniques that can help, so here they are.
These tips go in order from easiest to hardest to implement/use.
1) --step
This is the simplest of the techniques to implement and follow. Just add --step
to your ansible-playbook
command, and for each task you run you will get a prompt that looks like this:
PLAY [Your play name] ****************************************************************************************
Perform task: TASK: Your task name (N)o/(y)es/(c)ontinue:
For each task, you can choose to run the task (yes
), not run the task (no
, the default), or run the rest of the play (continue
).
Note that continue
will run until the end of the play, not the end of the entire run. Quite handy if you know you want to say yes
to everything in the current playbook.
The downside is that if there are many tasks to get through, you have to be careful not to keep your finger on the return key and accidentally go to far.
It would be a nice little open source project for someone to make this feature more powerful, adding ‘back’ and ‘skip this playbook’ features.
2) Inline logging
In addition to runtime control, you can use old-fashioned log lines to help determine what’s going on. The following snippet of code will ‘nicely’ dump out json representations of the variables set across all the hosts. This is really handy if you want to know where Ansible has some information you want to reference in your scripts.
- name: dump all
hosts: all
tasks:
- name: Print some debug information
vars:
msg: |
Module Variables ("vars"):
--------------------------------
{{ vars | to_nice_json }}
================================
Environment Variables ("environment"):
--------------------------------
{{ environment | to_nice_json }}
================================
Group Variables ("groups"):
--------------------------------
{{ groups | to_nice_json }}
================================
Host Variables ("hostvars"):
--------------------------------
{{ hostvars | to_nice_json }}
================================
debug:
msg: "{{ msg.split('\n') }}"
tags: debug_info
As you’ll see later, you can also interrogate the Python environment interactively…
3) Run ansible-lint
As with most linters, ansible-lint
can be a great way to spot problems and anti-patterns in your code.
Its output includes lines like this:
roles/rolename/tasks/main.yml:8: risky-file-permissions File permissions unset or incorrect
You configure it with a .ansible-lint
file, where you can suppress classes of error, or just tell you to warn.
The list of rules are available here, and more documentation is available here.
If you like this, you might like one of my books:
Learn Bash the Hard Way
Learn Git the Hard Way
Learn Terraform the Hard Way

4) Run ansible-console
This can be a huge timesaver when developing your Ansible code, but unfortunately there isn’t much information or guidance out there on how to use it, so I’m going to go into a bit more depth here.
The simplest way to run it is just as you would a playbook, but with console
instead of playbook
:
$ ansible-console -i hosts.yml
Welcome to the ansible console.
Type help or ? to list commands.
imiell@all (1)[f:5]$
You are greeted with a prompt and some advice. If you type help
, you get a list of all the commands and modules available to you to use in the context in which you have run ansible-console
:
Documented commands (type help <topic>):
========================================
EOF dpkg_selections include_vars setup
add_host exit iptables shell
apt expect known_hosts slurp
apt_key fail lineinfile stat
apt_repository fetch list subversion
assemble file meta systemd
assert find package sysvinit
async_status forks package_facts tempfile
async_wrapper gather_facts pause template
become get_url ping timeout
become_method getent pip unarchive
become_user git raw uri
blockinfile group reboot user
cd group_by remote_user validate_argument_spec
check help replace verbosity
command hostname rpm_key wait_for
copy import_playbook script wait_for_connection
cron import_role serial yum
debconf import_tasks service yum_repository
debug include service_facts
diff include_role set_fact
dnf include_tasks set_stats
You can ask for help
on these. If it’s a built-in command, you get a brief description, eg:
imiell@all (1)[f:5]$ help become_user
Given a username, set the user that plays are run by when using become
or, if it’s a module, you get a very handy overview of the module and its parameters:
imiell@all (1)[f:5]$ help shell
Execute shell commands on targets
Parameters:
creates A filename, when it already exists, this step will B(not) be run.
executable Change the shell used to execute the command.
chdir Change into this directory before running the command.
cmd The command to run followed by optional arguments.
removes A filename, when it does not exist, this step will B(not) be run.
warn Whether to enable task warnings.
free_form The shell module takes a free form command to run, as a string.
stdin_add_newline Whether to append a newline to stdin data.
stdin Set the stdin of the command directly to the specified value.
Where the console comes into its own is when you want to experiment with modules quickly. For example:
imiell@basquiat (1)[f:5]$ shell touch /tmp/asd creates=/tmp/asd
basquiat | CHANGED | rc=0 >>
imiell@basquiat (1)[f:5]$ shell touch /tmp/asd creates=/tmp/asd
basquiat | SUCCESS | rc=0 >>
skipped, since /tmp/asd exists
If you have multiple hosts, it will run across all those hosts. This is a great way to broadcast commands across a wide range of hosts.
If you want to work on specific hosts, then use the cd
command, which (misleadingly) changes your host context rather than directory. You can choose a specific host, or a group of hosts. By default, it uses all
:
imiell@all (4)[f:5]$ cd basquiat
imiell@basquiat (1)[f:5]$ command hostname
basquiat | CHANGED | rc=0 >>
basquiat
If a command doesn’t match an Ansible command or module, it assumes it’s a normal shell command and runs it through one of the Ansible shell modules:
imiell@basquiat (1)[f:5]$ echo blah
basquiat | CHANGED | rc=0 >>
blah
The console has autocomplete, which can be really handy when you’re playing around:
imiell@basquiat (1)[f:5]$ expect <TAB><TAB>
chdir= command= creates= echo= removes= responses= timeout=
imiell@basquiat (1)[f:5]$ expect
5) The Ansible Debugger
Ansible also contains a debugger that you can use to interrogate a running Ansible process. In this example, create a file called playbook.yml
, add this play to an existing one, or modify an existing play:
- hosts: all
debugger: on_failed
gather_facts: no
tasks:
- fail:
$ ansible-playbook playbook.yml
PLAY [all] ***************************
TASK [fail] **************************
Friday 27 August 2021 12:16:24 +0100 (0:00:00.282) 0:00:00.282 *********
fatal: [Ians-Air.home]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}
[Ians-Air.home] help
EOF c continue h help p pprint q quit r redo u update_task
From there, you can execute Python commands directly to examing the context:
[Ians-Air.home] TASK: wrong variable (debug)> dir()
['host', 'play_context', 'result', 'task', 'task_vars']
Or use the provided commands to help you debug. For example, p
maps to a pretty-print command:
[Ians-Air.home] TASK: wrong variable (debug)> p dir(task)
['DEPRECATED_ATTRIBUTES',
'__class__',
'__delattr__',
'__dict__',
'__doc__',
[...]
'tags',
'throttle',
'untagged',
'until',
'validate',
'vars',
'when']
If you like this, you might like one of my books:
Learn Bash the Hard Way
Learn Git the Hard Way
Learn Terraform the Hard Way

If you enjoyed this, then please consider buying me a coffee to encourage me to do more.

Good stuff – again :)
Which license is that nifty code in 2) under? Would love to be able to use it at work!
Thanks,
Georg
It’s just a snippet, it won’t be under license. Can’t remember where I got it from. If you’re worried, just change the format.
Nice stuff! How do you control play order in playbooks?
They’re ordered top-down in the yaml recipes.