Ansible - Play with hosts in order I desire - ansible

When running a playbook Ansible randomly sets a node as first, second and third.
ok: [node-p02]
ok: [node-p03]
ok: [node-p01]
Q: How can I configure Ansible to let it execute with the hosts in sorted order? Example:
ok: [node-p01]
ok: [node-p02]
ok: [node-p03]
Serial: 1 is not an option, since it slows down the play, and my playbook is meant for 3 nodes in a single play.

Applicable for Ansible 2.4 and higher:
This is now the default behaviour, ansible will play the hosts in the order they were mentioned in the inventory file. Ansible also provides a few built in ways you can control it with order:
- hosts: all
order: sorted
gather_facts: False
tasks:
- debug:
var: inventory_hostname
Possible values of order are:
inventory: The default. The order is ‘as provided’ by the inventory
reverse_inventory: As the name implies, this reverses the order ‘as provided’ by the inventory
sorted: Hosts are alphabetically sorted by name
reverse_sorted: Hosts are sorted by name in reverse alphabetical order
shuffle: Hosts are randomly ordered each run
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#hosts-and-users

Edit: The best solution is in dubes' answer but this one gives you more freedom in case specific operations have to be applied to the host list, or you can't use Ansible 2.4.
Since Ansible 2.2 you can use ansible_play_hosts or ansible_play_batch and sort it:
---
- hosts: "{{ ansible_play_hosts | sort() }}"
From ansible doc:
ansible_play_hosts is the full list of all hosts still active in the current play.
ansible_play_batch is available as a list of hostnames that are in scope for the current ‘batch’ of the play. The batch size is defined by serial, when not set it is equivalent to the whole play (making it the same as ansible_play_hosts).

I figured that another possibility is to use the exact hostnames in the hosts as a list, instead of a group. However, the other answers are more compliant to Ansible methods.
---
- hosts:
- node-p01
- node-p02
- node-p03

Related

Ansible Inventory Not Ordering Alphabetically

I'm having the following Ansible hosts file:
# Contains the host mappings
[master]
m1.my-host.com ansible_host=192.168.0.100
[node]
n1.my-host.com ansible_host=192.168.0.102
n2.my-host.com ansible_host=192.168.0.103
[k3s_cluster:children]
master
node
[all:vars]
ansible_python_interpreter=/usr/bin/python3
In my playbook, I'm saying that I need an ordered execution like this:
---
- name: Setup hostname, users and groups
hosts: all
order: sorted
gather_facts: true
remote_user: myuser
become: yes
roles:
- {role: common, tags: ['host', 'system']}
But when I ran it, I get to see the following:
TASK [Gathering Facts] **********************************************************************************************************************************************************************************************
ok: [n1.my-host.com]
ok: [n2.my-host.com]
ok: [m1.my-host.com]
I would have rather expected it to be m1, n1 and then n2. Is there any idea as to why the sort order is not respected?
The sort order is being respected, but because Ansible executes tasks against multiple hosts in parallel, you see the results in the order in which the task completes.
If you were to serialize your task execution like this:
- hosts: all
order: sorted
serial: 1
gather_facts: false
tasks:
- ping:
You would see that the order of execution is consistently:
m1.my-host.com
n1.my-host.com
n2.my-host.com
Generally you don't want to serialize execution like that because it dramatically increases the runtime of your playbook, but if you have a very large number of hosts it sometimes makes sense to use a number > 1 to execute tasks in batches.

Ansible Inventory: how to build a group from hosts not in certain other groups

I am looking for a way to build an inventory group that includes all those hosts that haven't been put into another group.
In my case, the groups identify when a particular server should be updated - either on Mondays, Wednesdays, Fridays, or "any day, it doesn't matter". I do not want to explicitly enumerate the hosts, as that is manual work and error-prone.
all:
hosts:
host1[1-100].example.com:
children:
updateMonday:
hosts:
host1.example.com:
host4.example.com:
updateWednesday:
hosts:
host2.example.com:
host5.example.com:
updateFriday:
hosts:
host3.example.com:
host6.example.com:
updateAnyday:
children:
# This expresses what I want but does not work
!updateMonday:
!updateWednesday:
!updateFriday:
I am using additional groups not shown in this example, so I can't simply use the "ungrouped" group to do what I want.
Edit: I do not want to modify the playbooks I use, because I have quite a few of them, and I also need ad-hoc commands to honor the new group.
Create a group with all hosts, e.g. updateAll
all:
hosts:
host[1-100].example.com:
children:
updateAll:
hosts:
host[1-100].example.com:
updateMonday:
hosts:
host1.example.com:
host4.example.com:
...
In the first play create dynamically the group updateAnyday and use it in the second play
- hosts: all
tasks:
- add_host:
name: "{{ item }}"
groups: updateAnyday
loop: "{{ groups.updateAll|
difference(groups.updateMonday)|
difference(groups.updateWednesday)|
difference(groups.updateFriday) }}"
run_once: true
- hosts: updateAnyday
tasks:
...
(Not tested)
Q: "I don't want to modify the playbook."
A: Create a file with the list of the hosts updateAnyday.txt, e.g. use the playbook above. Then run your playbook updateAnyday.yml with limited inventory, e.g.
shell> ansible-playbook updateAnyday.yml --limit #updateAnyday.txt
See Patterns and ansible-playbook flags.
You can create a play and change the host with the group combinations. For instance, all the hosts except the updateMonday, updateWednesday, and updateFriday would be:
hosts: all:!updateMonday:!updateWednesday:!updateFriday
You can also dynamically create the group using add_host or group_by commands.

How to execute task on all hosts from group when playbook is executed with limited hosts?

Scenario
I have a group A in my inventory, where A contains a1,a2,a3 hosts. It does mean that I can write in my playbook X.yml:
- hosts: A
roles:
- role:
name: r
The problem is about playbook X is started with limited number of hosts, namely launch of ansible-playbook X is limited to host a1. This playbook X invoke role r (which is executed on host a1). I wouldn't like to change this behaviour (in other words I would like to preserve this limitation, don't ask why please).
Question
Is it possible to write task in role r in such way that it will be executed on all hosts from group A even if playbook is limited to host a1? Please keep in mind that my inventory contains group A.
If not, could you suggest me another approach?
The one that I can do is:
- hosts: A
tasks:
- name: "This task"
I do not know for certain, but this might work:
- name: Run task on hosts in group A
some_random_module:
var1: value1
var2: value2
delegate_to: "{{ item }}"
with_items: "{{ groups['A'] }}"
No promises.

Ansible how to store output of register in list when iterated over inventory

I am running a shell command, this command runs for all hosts listed in my inventory file. I am then using register to define the variable, when i retrieve these values for debug messages i see register variable for all hosts printed for all IP in my inventory but i want to store them in a list so that i can use them in templates. How can we achieve it?
- name: Command
shell: hostname -f
register: fqdn_name
For your specific question, you are doing more work than you need to. Each time Ansible runs against a host, it collects a series of 'facts' about the host and stores them in a dictionary available during your plays. Therefore, replace your existing Command task with the following, to see what I mean:
- name: Display the Ansible FQDN fact
debug:
var: ansible_fqdn
Running ansible -m setup <hostname taken from inventory file> will show you all the variables that get collected.
The variables for all your hosts are made available through a special dictionary called 'hostvars', therefore in your template you can do something like this:
{% for host in groups.all %}
{{ hostvars[host]['ansible_fqdn'] }}
{% endfor %}
You could replace groups.all with groups.<some inventory groupname> to limit the matched hosts to a particular group.
One possible gotcha here, is that these facts will only have been collected if Ansible has already targeted a host, therefore one strategy for more complex playbooks is:
# This play simply connects to all your hosts and gathers facts
- hosts: all
gather_facts: yes
# Now all subsequent plays have access to facts for all hosts
- hosts: <all or some group>
tasks: ...

Ansible: How to declare global variable within playbook?

How can I declare global variable within Ansible playbook. I have searched in google and found the below solution, but its not working as expected.
- hosts: all
vars:
prod-servers:
- x.x.x.x
- x.x.x.x
- hosts: "{{prod-servers}}"
tasks:
- name: ping
action: ping
When I'm trying the above code, it says variable prod-servers is undefined.
You cannot define a variable accessible on a playbook level (global variable) from within a play.
Variable Scopes
Ansible has 3 main scopes:
Global: this is set by config, environment variables and the command line
Play: each play and contained structures, vars entries (vars; vars_files; vars_prompt), role defaults and vars.
Host: variables directly associated to a host, like inventory, include_vars, facts or registered task outputs
Anything you declare inside a play can thus only be either a play variable, or a (host) fact.
To define a variable, which you can use in the hosts declaration:
run ansible-playbook with --extra-vars option and pass the value in the argument;
or to achieve the same functionality (decide which hosts to run a play on, from within a preceding play):
define an in-memory inventory and run the subsequent play against that inventory.
what you seem to want is an inventory (http://docs.ansible.com/ansible/latest/intro_inventory.html), it looks like you have an static list of IP's that may be prod servers (or dev, or whatever), therefore you can create an static inventory.
In your second play you want to use the list of IP's as hosts to run the tasks, that's not what Ansible expects. After the "hosts" keyword in a play declaration, Ansible expects a group name from the inventory.
If, on the opossite, your prod servers change from time to time, you may need to create a dynamic inventory. You can have a look at examples in https://github.com/ansible/ansible/tree/devel/contrib/inventory (for instance, there are examples of dynamic inventory based on EC2 from Amazon or vsphere)
regards
well, this can be done using
set_fact.
I don't know the best practice for this but this works for me
Here's my playbook example
- hosts: all
gather_facts: false
tasks:
- set_fact: host='hostname'
- hosts: host-name1
gather_facts: false
tasks:
- name: CheckHostName
shell: "{{ host }}"
register: output
- debug: msg="{{ output }}"
- hosts: host-name2
gather_facts: false
tasks:
- name: CheckHostName
shell: "{{ host }}"
register: output
- debug: msg="{{ output }}"

Resources