Introduction

Playbook are a key element of Ansible. You specify which roles and tasks will run on your hosts and groups.
Each play of the playbook must have its parameter hosts set.

Here is a quick example of different hosts values that you might be familiar with:

---
- name: My play targeting all hosts 
  hosts: all
  tasks:
    - debug:
        msg: 'Running on all hosts'

- name: Targeting multiple groups called dbservers and webservers
  hosts:
  - dbservers
  - webservers  
  tasks:
    - debug:
        msg: 'Running on all dbservers and webserver'

- name: Targeting only one host called dbserver-01
  hosts: dbserver-01
  tasks:  
    - debug:
        msg: 'Running on one hosts'

- name: Running on the Ansible server
  hosts: localhost
  connection: local
  tasks:
    - debug:
        msg: 'Running on localhost'

If you are developing with Ansible, you probably already have written a playbook. You might also have duplicated a playbook to target different groups and hosts due to:

  • Some use cases
  • The organization of your Ansible and inventories files
  • Any others reasons

This article will focus on the different ways we can provide the hosts parameter to avoid handling multiple playbook files.


For all the following examples, you can use this ‘hosts’ file and replace all machine hostnames with your own.

# hosts
[dbservers]
dbserver-01
dbserver-02

[webservers]
webserver-01

Ansible extra variable – Specify hosts on the command line

The first idea that may come to mind is to use an Ansible variable and pass it as an extra vars.
Here I name the variable my_playbook_hosts and ask to target a group and a host.

# extravars.yml
---
- name: My play targeting hosts specified in the command line 
  hosts: '{{ my_playbook_hosts }}'
  tasks:
    - debug:
        msg: 'Running on hosts specified by my extra vars'

Then you can simply run the ansible-playbook command with the option ‐‐extra-vars:

ansible-playbook -i hosts extravars.yml -e 'my_playbook_hosts=dbservers'

This solution works fine, you just need to come up with a new variable and be sure that its name is not already used by Ansible or in your existing roles/tasks (except if it is expected).

Default value

Using the Jinja2 ‘default’ filter, you can specify a default value.
That way, if you don’t provide the extra variable, the playbook will run on the default value hosts. Here for example with the group ‘dbservers’:

  hosts: '{{ my_playbook_hosts | default("dbservers") }}'

Common pattern

A pattern can be used for targeting hosts and groups.
More information can be read in the Ansible official documentation.

Screenshot of ansible documentation about common patterns
Link to official documentation: https://docs.ansible.com/ansible/latest/inventory_guide/intro_patterns.html#common-patterns
# Target group webservers and host dbserver-01.
ansible-playbook -i hosts extravars.yml -e 'my_playbook_hosts=webservers,dbserver-01'

# Target group dbservers, excluding host dbserver-01.
ansible-playbook -i hosts extravars.yml -e 'my_playbook_hosts=dbservers,!webserver-01'

# Target nothing as all is negate.
ansible-playbook -i hosts blogtest.yml -e 'my_playbook_hosts=!all,webserver-01'

Those patterns can also be used in the playbook file directly:

- name: Targeting multiple groups called dbservers and webservers
  hosts: dbservers,webservers  

- name: Targeting all except webservers
  hosts: 'all,!webservers'  

Negate

About negating hosts and groups, always use single quotes to prevent bash interpolation or syntax error.

Ansible limit – Limit on which host/group it runs

Command ansible-playbook has an option which is ‐‐limit or -l, which further limits selected hosts to an additional pattern.

Let’s say your playbook runs on ‘dbservers’ group, with limit, you can restrict further the hosts or groups on which it should run:

# limit.yml
---
- name: My play targeting all dbservers
  hosts: dbservers
  tasks:
    - debug:
        msg: 'Running all dbservers'
# Will only target host dbserver-02, if its belong to dbservers
ansible-playbook -i hosts limit.yml -l dbserver-02 

# Will target dbservers group, except the hosts dbserver-02
ansible-playbook -i hosts limit.yml -l '!dbserver-02'
# limit2.yml
---
- name: Play running on all hosts
  hosts: all
  tasks:
    - debug:
        msg: 'Running on all hosts'
# Will only target groups webservers and host dbserver-01
ansible-playbook -i hosts limit2.yml -l 'webservers,dbserver-01'

I personnaly prefer this method to the extra vars, as it doesn’t require adding a new additional variable and doesn’t require modifying the existing playbook.

Diving more into ansible-playbook ‐‐limit

You can find a lot of examples of ansible-playbook ‐‐limit with a playbook targeting all hosts (hosts: all).


That is something that bothered me a little depending on the playbook usage.
If the playbook is to be used on all hosts every time, with rare occasions to target only a specific host and/or group, then it should be good.


On the contrary, if the playbook is to always be run to specific hosts and groups every time, then it might be dangerous to set the ‘hosts’ to ‘all’ or any others groups by default.
If by accident, the ‐‐limit option is forgotten, your playbook will be run on the ‘hosts’ specified on the playbook file and might cause serious issues.

Here is a safer solution that I find elegant and good.

Ansible limit – Specify hosts on the command line

With this method, the playbook only runs on the pattern you provide in the limit option.
It is exactly the same way as how ‐‐extra-vars work, but in this situation, you don’t need to create a new variable.

The value specified in ‐‐limit is stored in the variable ansible_limit.

# safer.yml
---
- name: Play running only on the pattern given by the option --limit
  hosts: '{{ ansible_limit | default(omit) }}'
  tasks:
    - debug:
        msg: 'Running on hosts specified by limit'
# Will target all host
ansible-playbook -i hosts safer.yml -l 'all'

# Will target group dbservers
ansible-playbook -i hosts safer.yml -l 'dbservers'

# Will target group webservers and host dbserver-01
ansible-playbook -i hosts safer.yml -l 'webservers,dbserver-01'

# Will target all except dbservers 
ansible-playbook -i hosts safer.yml -l 'all,!dbservers'

The special keyword omit in default is a special variable that allows omitting the parameters, causing the playbook to run without crashing.
The results will be a skip of the play.

ansible-playbook -i hosts safer.yml
[WARNING]: Could not match supplied host pattern, ignoring: None

PLAY [Play running only on the pattern given by the option --limit] **************************************************************************
skipping: no hosts matched

PLAY RECAP ****************************************************************************************************************************************************

Conclusion

In conclusion, the best method will always vary and depend on different factors, so try out the different methods and pick those suitable for your needs.

You are now able to specify via the command line which hosts/groups to apply your playbook!


Learn more on Ansible with our Training course: https://www.dbi-services.com/en/courses/ansible-basics/ 

And check our other blog articles https://www.dbi-services.com/blog/category/devops/ansible/