Ansible playbook starting with large forked host list, but spawning serial tasks for smaller subsets of hosts

Solution 1:

It's not possible to achieve the goal in a single playbook. A dedicated playbook for each group is needed if you want to process the groups in parallel. Use Ansible to create the code.

"Smaller subsets of hosts"

Given the inventory to simplify the testing

test_01
test_02
test_03
test_04
test_05
test_06

[GUID_01]
test_01
test_02
test_03

[GUID_02]
test_04
test_05
test_06

[foo]
test_01
test_02
test_04
test_05
srvX

[bar]
test_03
test_06
srvY

Create a dictionary of all GUID_* and a list of hosts both in particular GUID_x and foo groups. For example

- hosts: GUID_*:&foo
  gather_facts: false
  tasks:
    - set_fact:
        my_groups: "{{ my_groups|default({})|
                       combine({item: my_hosts|dict2items|
                                      selectattr('value', 'eq', item)|
                                      map(attribute='key')|list}) }}"
      loop: "{{ my_guids|unique }}"
      vars:
        my_guids: "{{ ansible_play_hosts_all|
                      map('extract', hostvars, 'group_names')|
                      map('select', 'match', 'GUID')|
                      map('first')|flatten }}"
        my_hosts: "{{ dict(ansible_play_hosts_all|zip(my_guids)) }}"
      run_once: true
    - debug:
        var: my_groups
      run_once: true

gives

  my_groups:
    GUID_01:
    - test_01
    - test_02
    GUID_02:
    - test_04
    - test_05

The same dictionary can be created without patterns

- hosts: all
  gather_facts: false
  tasks:
    - set_fact:
        my_groups: "{{ my_groups|default({})|
                       combine({item: groups[item]|
                                      intersect(groups['foo'])}) }}"
      loop: "{{ groups|select( 'match', 'GUID')|list }}"
      run_once: true

Q: "Create parallel workers that execute serial: 1 playbooks for a subset of hosts."

A: Use Ansible templates to create the code. For example, given the role

shell> cat roles/reboot/tasks/main.yml
- command: date "+%H:%M:%S"
  register: result
- debug:
    msg: "{{ result.stdout }} {{ inventory_hostname }} Reboot"
- command: sleep 3
- command: date "+%H:%M:%S"
  register: result
- debug:
    msg: "{{ result.stdout }} {{ inventory_hostname }} Ready"

The templates

shell> cat templates/my_hosts.j2
{{ lookup('file', 'hosts') }}

# ---------------------------------------------------------------------
# Groups for my_wrapper. See template my_hosts.j2

{% for group,hosts in my_groups.items() %}
[my_{{ group }}]
{% for host in hosts %}
{{ host }}
{% endfor %}

{% endfor %}
shell> cat templates/my_pb.j2
- hosts: my_{{ item }}
  gather_facts: false
  serial: 1
  roles:
    - reboot
shell> cat templates/my_wrapper.j2
#!/bin/bash
{% for group,hosts in my_groups.items() %}
nohup ansible-playbook -i my_hosts my_pb_{{ group }}.yml &
{% endfor %}

and the playbook

- hosts: all
  gather_facts: false
  tasks:
    - set_fact:
        my_groups: "{{ my_groups|default({})|
                       combine({item: groups[item]|
                                      intersect(groups['foo'])}) }}"
      loop: "{{ groups|select( 'match', 'GUID')|list }}"
      run_once: true
    - block:
        - template:
            src: my_hosts.j2
            dest: my_hosts
        - template:
            src: my_wrapper.j2
            dest: my_wrapper.sh
            mode: "a+x"
        - template:
            src: my_pb.j2
            dest: "my_pb_{{ item }}.yml"
          loop: "{{ my_groups.keys()|list }}"
      run_once: true
      delegate_to: localhost

create the files with the code

shell> cat my_hosts

   ...

# ---------------------------------------------------------------------
# Groups for my_wrapper. See template my_hosts.j2

[my_GUID_01]
test_01
test_02

[my_GUID_02]
test_04
test_05
shell> cat my_wrapper.sh
#!/bin/bash
nohup ansible-playbook -i my_hosts my_pb_GUID_01.yml &
nohup ansible-playbook -i my_hosts my_pb_GUID_02.yml &
shell> cat my_pb_GUID_01.yml 
- hosts: my_GUID_01
  gather_facts: false
  serial: 1
  roles:
    - reboot

shell> cat my_pb_GUID_02.yml 
- hosts: my_GUID_02
  gather_facts: false
  serial: 1
  roles:
    - reboot

Then, running the wrapper gives

shell> grep msg nohup.out 
    "msg": "13:03:51 test_01 Reboot"
    "msg": "13:03:51 test_04 Reboot"
    "msg": "13:03:56 test_01 Ready"
    "msg": "13:03:56 test_04 Ready"
    "msg": "13:03:58 test_02 Reboot"
    "msg": "13:03:58 test_05 Reboot"
    "msg": "13:04:04 test_02 Ready"
    "msg": "13:04:04 test_05 Ready"