0

I am using ansible 2.9.27 with a dynamic inventory on Hetzner cloud servers.

I have 20 servers that I want to iterate over with my playbook.

$ ansible-inventory -i inventory/production/ --graph | grep srv
  |  |  |--srv01.mydomain.co.uk
  |  |  |--srv02.mydomain.co.uk
  |  |  |--srv03.mydomain.co.uk
  |  |  |--srv04.mydomain.co.uk
  |  |  |--srv05.mydomain.co.uk
  |  |  |--srv06.mydomain.co.uk
  |  |  |--srv07.mydomain.co.uk
  |  |  |--srv08.mydomain.co.uk
  |  |  |--srv09.mydomain.co.uk
  |  |  |--srv10.mydomain.co.uk
  |  |  |--srv11.mydomain.co.uk
  |  |  |--srv12.mydomain.co.uk
  |  |  |--srv13.mydomain.co.uk
  |  |  |--srv14.mydomain.co.uk
  |  |  |--srv15.mydomain.co.uk
  |  |  |--srv16.mydomain.co.uk
  |  |  |--srv17.mydomain.co.uk
  |  |  |--srv18.mydomain.co.uk
  |  |  |--srv19.mydomain.co.uk
  |  |  |--srv20.mydomain.co.uk

Looking at various places, including How do i loop over hosts identified by wildcards in Ansible, my belief is that if I have the following playbook:

- name: test iteration over names
  hosts: "{{ HOSTS }}"
  gather_facts: False

  tasks:

  - ping:
    with_items: "{{ ansible_play_hosts }}"     

It should ping each host once. The following command should ping 2 hosts:

$ ansible-playbook --inventory inventory/production iteration.yaml --extra-vars "HOSTS=srv?5.mydomain.co.uk "

PLAY [test iteration over names] *************************************************************************************************************************************************************************

TASK [ping] **********************************************************************************************************************************************************************************************
ok: [srv05.mydomain.co.uk] => (item=srv05.mydomain.co.uk)
ok: [srv05.mydomain.co.uk] => (item=srv15.mydomain.co.uk)
ok: [srv15.mydomain.co.uk] => (item=srv05.mydomain.co.uk)
ok: [srv15.mydomain.co.uk] => (item=srv15.mydomain.co.uk)

PLAY RECAP ***********************************************************************************************************************************************************************************************
srv05.mydomain.co.uk : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
srv15.mydomain.co.uk : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

the play recap lists the anticipated number of hosts, but it pings each host the same number of times as the number of hosts specified by the wildcard.

HOSTS=srv15.mydomain.co.uk would ping one host once
HOSTS=srv?5.mydomain.co.uk pings 2 hosts (srv05 and srv15) twice
HOSTS=srv1?.mydomain.co.uk pings 10 hosts (srv10 to srv19) 10 times
etc.

Why is this and how do I perform the task once per host?

Kevin Jones
  • 3,837
  • 1
  • 16
  • 18
  • 2
    Because this is the behaviour of targeting hosts via the `hosts` parameter. Just get rid of your `with_items`, you do not need it. – β.εηοιτ.βε Mar 29 '23 at 21:16
  • 2
    Related documentation: https://docs.ansible.com/ansible/latest/inventory_guide/intro_patterns.html#patterns-targeting-hosts-and-groups – β.εηοιτ.βε Mar 29 '23 at 21:17
  • From current case desription I understand that `--limit` would be the appropriate approach to address it. In example [Limit hosts](https://stackoverflow.com/a/73315734/6771046) or [launch playbook only on the first host of each inventory group](https://stackoverflow.com/a/73947076/6771046). – U880D Mar 30 '23 at 05:45

1 Answers1

1

My belief is that if I have the following playbook ...

Actually not. In fact the task is iterating over 2 lists, performing an outer loop on HOSTS as the initiator of a connection test with an inner loop on ansible_play_hosts as the target of the connection test resulting in example into an execution of

HOSTS=srv1?.mydomain.co.uk pings 10 hosts (srv10 to srv19) 10 times

Why am I iterating multiple times over wildcard Ansible hosts?

This is because, in the given example it is not defined from where to initiate a connection check (or ping), but only to which host, so the task gets sent to all host in the ansible_play_hosts list trying to make a connection to all hosts in the list from there.

Why is this

That's why

ok: [srv05.mydomain.co.uk] => (item=srv05.mydomain.co.uk)
ok: [srv05.mydomain.co.uk] => (item=srv15.mydomain.co.uk)
ok: [srv15.mydomain.co.uk] => (item=srv05.mydomain.co.uk)
ok: [srv15.mydomain.co.uk] => (item=srv15.mydomain.co.uk)

How do I perform the task once per host?

There might be different options, one is to define the initiator and the target within the task.

- name: test iteration over names
  hosts: "{{ HOSTS }}"
  gather_facts: False

  tasks:

  - ping:
    delegate_to: controlnode.mydomain.co.uk
    with_items: "{{ ansible_play_hosts }}"

The best practice would be for me to provide only a list of HOSTS to test against.

- name: test iteration over names
  # From
  hosts: controlnode.mydomain.co.uk # the initiator of the connection test
  gather_facts: False

  tasks:

  - ping:
    # To
    with_items: "{{ HOSTS }}" # the tartgets of the connection test
U880D
  • 8,601
  • 6
  • 24
  • 40