Ansible: Checking and Controlling Task Execution, Orchestration and Scope
Ansible is a powerful tool that gives you great control over your infrastructure. When I developed the Ansible code to install and maintain my infrastructure at home, I wrote most scripts first as single files. This was good for some time — until you realized that the structure is getting complex and you find yourself looking for the script you needed for a considerable amount of time. Then I refactored my code base to a proper layout: A central playbook site.yml
that contains all plays and roles. But now I was struggling how to run only specific tasks on specific nodes.
In this article, I will show you how to use the Ansible command line to control which tasks to execute, which hosts to target, and even custom orchestration of tasks. If you need a refresher on Ansible concepts, read the footnote1.
This article originally appeared at my blog.
Directory Layout of my Infrastructure at Home Project
As the running example, let me show you the directory layout and files from my infrastructure at home project.
At the top level, I have the following directories and files:
├── group_vars
├── host_vars
├── roles
├── scripts
├── site.yml
The roles
define the basic software that gets installed on each node.
├── roles
│ ├── consul
│ └── nomad
│ └── dns
│ ├── docker-arch
│ ├── docker-arm
│ ├── nfs-client
│ ├── nfs-server
├── site.yml
And scripts
include smaller tasks that I execute on a selected number of hosts, for example deploying nomad jobs or system maintenance tasks on the nodes:
├── scripts
│ ├── consul
│ ├── nomad
│ ├── tutorial
│ └── uninstall
│ ├── system
│ │ ├── clean_docker.yml
│ │ ├── connection_test.yml
│ │ ├── install_package_on_arch.yml
│ │ ├── install_package_on_debian.yml
│ │ ├── reboot.yml
│ │ ├── rotate_ssh_key.yaml
│ │ ├── shutdown.yml
│ │ └── update_packages.yml
All roles, and some scripts, are defined site.yml
. This is the idempotent playbook that sets up all nodes with Docker, Nomad, Consul, DNS and NFS services.
So, how can I execute just a small set of tasks from the site.yml
playbook, without running it completely? Read on...
Check Playbooks before Execution
Before running a long list of tasks, it’s a good idea to check if your playbook works and that will happen. There are four different types of checks that I strongly recommend to do before you execute a script.
Syntax Check
First, you should check the syntax with the flag --check-syntax
if all scripts that will be used when executing a playbook.
>> ansible-playbook site.yml --syntax-checkOK playbook: site.yml
If you would have any error, for example in a role, you will get helpful information.
>> ansible-playbook site.yml --syntax-check
ERROR! Syntax Error while loading YAML.
mapping values are not allowed in this contextThe error appears to be in '[...]/roles/dns/tasks/main.yml': line 7, column 15, but may
be elsewhere in the file depending on the exact syntax problem.The offending line appears to be: - name: Configure dnsmasq
template:
^ here
List Affected Hosts
Second, take a look at the hosts that will be affected.
>> ansible-playbook site.yml --list-hostsplaybook: site.yml play #1 (all): Install consul TAGS: [consul]
pattern: ['all']
hosts (6):
raspi-3-1
raspi-0
raspi-4-1
minas
raspi-4-2
raspi-3-2
List all Tasks
Third, take a detailed look into the tasks that will be executed with the flag --list-tasks
.
>> ansible-playbook site.yml --list-tasksplaybook: site.yml play #1 (all): Install consul TAGS: [consul]
tasks:
command TAGS: [consul]
consul : Check if current consul version is installed TAGS: [consul]
consul : Set vars when architecture is armv7l TAGS: [consul]
consul : Set vars when architecture is armv6l TAGS: [consul]
consul : Set vars when architecture is x86_64 TAGS: [consul]
consul : Create consul group TAGS: [consul]
consul : Create consul user TAGS: [consul]
consul : Create consul dir TAGS: [consul]
consul : Get consul binary TAGS: [consul]
consul : Unzip consul binary TAGS: [consul]
consul : Create symlink TAGS: [consul]
Dry Run to Detail all Changes
Finally, you can make a dry run and see each the result of each step applied to your nodes with --check --diff
. This check is especially important! Run it whenever you are modifying important system files, for example as shown here to add a new line to the /etc/fstab
file.
>> ansible-playbook site.yml --check --diff
TASK [nfs-server : Install software] ****************************************
The following NEW packages will be installed:
nfs-kernel-server
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
changed: [minas]TASK [nfs-server : Create mount dir] ****************************************
--- before
+++ after
@@ -1,4 +1,4 @@
{
"path": "/mnt/nfs",
- "state": "absent"
+ "state": "directory"
}changed: [minas]TASK [nfs-server : Create fstab entry] ****************************************
--- before: /etc/fstab (content)
+++ after: /etc/fstab (content)
@@ -8,3 +8,4 @@
UUID=BB43-B9B6 /boot/efi vfat defaults,noatime 0 2
UUID=1ece07a6-5072-4f25-aed0-27e01b0ded17 / ext4 defaults,noatime 0 1
192.168.2.203:/mnt/nfs /mnt/nfs nfs defaults,soft,bg,noauto,rsize=32768,wsize=32768,noatime 0 0
+/dev/sda1 /mnt/nfs ext3 defaults,user 0 1changed: [minas]TASK [nfs-server : Configure nfs access] ****************************************
--- before: /etc/exports (content)
+++ after: /etc/exports (content)
@@ -0,0 +1 @@
+/mnt/nfs 192.168.2.0/24(rw,sync,no_subtree_check,no_root_squash)changed: [minas]
Ok! At this point, you have a good understanding of what your playbook will change. Lets continue.
Controlling Task Execution
Once you are sure that the tasks do exactly what they should do, and they target the correct hosts, lets execute them. For example, inside my site.yml
, I have a play to configure the DNS resolution on all nodes.
- name: Configure DNS
hosts:
- all
tags:
- dns
become: true
roles:
- dns
However, this play is placed in the middle of the file. How can I just execute this specific play?
Executing Specific Plays
The best option is to apply and use tags. Tags are any, well tags, that you apply to roles or tasks inside the plays. As shown above, I use a plain “tag to role” declaration. To only execute plays with a specific tag, use the --tags
flag. You can pass a single or a list of tags to this flag.
Now, if I only want to run the DNS play, I execute ansible-playbook site.yml --tags consul
. Another option is to use its name, like ansible-playbook site.yml --start-at-task="Install consul"
, but tags are preferred.
Executing Specific Tasks inside a Play
What if the play you want to start has several subtasks, and you don’t want to execute them all? Just add the flag --step
. Before any task is performed, a prompt is opened and will ask you if you want to continue. Answer the question, or stop the playbook execution anytime by entering ctrl-c
.
ansible-playbook -i hosts site.yml --tags "dns" --stepPLAY [Install consul]
****************************************
PLAY [Install nomad]
****************************************
PLAY [Configure DNS]
****************************************Perform task: TASK: dns : Install dnsmasq on raspi-3-1 (N)o/(y)es/(c)ontinue:
Executing Tasks only on specific Hosts
When you want to live test a play, it is useful to just modify one host before making a change to your complete inventory. The helpful --limit
is what you need to use.
>> ansible-playbook -i hosts site.yml --tags "dns" --limit minasTASK [dns : Install dnsmasq]
****************************************ok: [minas]TASK [dns : Configure dnsmasq]
ok: [minas]
Now you have learned all about picking a set of tasks from a central playbook. There is only one thing remaining.
Orchestrating Tasks
When you execute a playbook, in each play, all roles will be executed first, followed by the tasks, top down in the order that they are written. If you want to orchestrate them in a different way, tags can help you as well. You can apply multiple tags to any task and any role. For example, all tasks that will install software could be tagged as install
, and all tasks that will copy config files could be tagged config
. Then, just execute the playbook with those tags: ansible-playbook site.yml --tags "install, config"
.
Conclusion
This article showed you how to work with complex Ansible playbooks more effectively. First, how to check a playbook before execution. You learned how to perform a syntax check, how to see the list of tasks and affected hosts, and how to make a dry run. The dry run is especially important when your tasks affect important system configuration files. Develop a healthy habit to apply the --check
flag often. Second, you learned how the execute only specific plays and tasks inside plays. Tags give you great flexibility. Apply them, similarly named as the tasks and roles, inside your playbook. And then think one level above, identify the tasks that modify config files, tasks that install software, and give them grouped tags. Once you have tags in place, you can execute them specifically. Also, combine this with the --step
flag to be prompted about the tasks that are executed. And when you define tags consistently, you can orchestrate the playbook execution like you want.
Footnotes
- Here are the details: ↩
- Playbook: A collection of plays
- Plays: A set of tasks that run on a set of hosts
- Tasks: Concrete actions that are applied to a host
- Role: A set of tasks that provision a host to perform a self-defined responsibility