Understanding Ansible Patterns

Patterns is a set of expressions in Ansible that lets us specify concisely which systems a playbook or an ad hoc command must be applied to. In this post we will consider most useful pattern expressions and demonstrate them on examples.

In examples we will use module ping, which doesn’t produce much output. If you never worked with it before, ping module just pings all systems selected by pattern. Also we will make use of -o option, which condenses output to one line per system.

In general, our ad hoc commands will look like:

ansible <pattern> -m ping -o
Ansible inventory file

In one of previous posts we learned how to create and configure a cluster of VirtualBox VMs. Today we’ll piggy back on the cluster and use the following inventory (/etc/ansible/hosts) for examples:

node101 ansible_host=127.0.0.1 ansible_port=3022
node102 ansible_host=127.0.0.1 ansible_port=4022
node201 ansible_host=127.0.0.1 ansible_port=5022
node202 ansible_host=127.0.0.1 ansible_port=6022
node203 ansible_host=127.0.0.1 ansible_port=7022

[frontend]
node101
node102
node201

[backend]
node201
node202

[db]
node203
All

Ansible has a special group if you need to address all systems. The group is named (surprise! surprise!) all.

ansible all -m ping -o

Output:

node102 | SUCCESS => {"changed": false, "ping": "pong"}
node201 | SUCCESS => {"changed": false, "ping": "pong"}
node101 | SUCCESS => {"changed": false, "ping": "pong"}
node202 | SUCCESS => {"changed": false, "ping": "pong"}
node203 | SUCCESS => {"changed": false, "ping": "pong"}

Note that all systems have been pinged successfully, demonstrating that the cluster is up and running.

Globbing

Similar to shell, you can use * to specify glob patterns. Just don’t forget to escape the pattern with single quotes to avoid shell globbing.

ansible 'node1*' -m ping -o

Output:

node102 | SUCCESS => {"changed": false, "ping": "pong"}
node101 | SUCCESS => {"changed": false, "ping": "pong"}

Note that '*' pattern does exactly the same as all.

Union

Union (:) is a way to join together multiple groups or hosts. E.g., you can use union to select systems belonging to frontend or backend or both:

ansible frontend:backend -m ping -o

Output:

node101 | SUCCESS => {"changed": false, "ping": "pong"}
node102 | SUCCESS => {"changed": false, "ping": "pong"}
node202 | SUCCESS => {"changed": false, "ping": "pong"}
node201 | SUCCESS => {"changed": false, "ping": "pong"}

You can use , instead of : in new versions of Ansible, which could improve readability in some cases.

Intersection

Intersection (:&) is helpful when you need to select hosts that belong to both groups. E.g., we can select hosts that belong to both frontend and backend:

ansible 'frontend:&backend' -m ping -o

Output:

node201 | SUCCESS => {"changed": false, "ping": "pong"}

Note that the expression is escaped with single quotes.

Similarly to union, you can use ,& instead of :& in new versions of Ansible.

Exclusion

Sometimes you need to exclude (:!) host(s) from selection. E.g., we can apply a command to all servers in frontend group, but not in backend:

ansible 'frontend:!backend' -m ping -o

Output:

node102 | SUCCESS => {"changed": false, "ping": "pong"}
node101 | SUCCESS => {"changed": false, "ping": "pong"}

Similarly to union, you can use ,! instead of :! in new versions of Ansible.

If you still hasn’t guessed it, you can substitute : with , in all pattern expressions :)

Complex patterns

Ansible does not limit us to using a single expression in a pattern. Feel free to mix and match them, feel the power!

ansible 'frontend:db:!backend' -m ping -o

Output:

node101 | SUCCESS => {"changed": false, "ping": "pong"}
node102 | SUCCESS => {"changed": false, "ping": "pong"}
node203 | SUCCESS => {"changed": false, "ping": "pong"}