3

I would like to run a task only on one host with Ansible. To do that, I use the parameter run_once.

But what would really rock is to be able to specify a strategy to select that one host to run that task on. For instance, the host with the highest available memory.

I am planning to use this feature if it exists on a playbook which creates new virtual machines on a libvirt cluster.

Raspbeguy
  • 555

1 Answers1

3

There is no such integrated mechanism that I know of. The task will start with the first host in the inventory depending on what is matched in your play (i.e. the hosts parameter) and eventually the limits you used on your command line (i.e. the -l option to ansible playbook) and will stop after this one if run_once was used.

Meanwhile, I think there is a way to meet your target.

Background info

  • Ansible maintains some magic variable among which:
    • ansible_play_hosts - the list of active hosts in the current play run limited by the serial, aka ‘batch’
    • hostvars - a hashmap with all the hosts in inventory and variables assigned to them
  • The available memory on each host is available in ansible_memfree_mb. This var only exist if you gather facts on your hosts (i.e. you did not turn this off with gather_facts: no on your play)
  • The solution uses two filters for which you may want to have a deeper look
    • map allows us to extract only the relevant host info from hostvars
    • json_query (implementing jmespath) allows us to filter the list of vars looking for max memory and getting only the hostname of the relevant node at final

Proposed solution

The below playbook demonstrate how to combine the above elements to hit the target. The task uses a combination of run_once, delegate_to and delegate_facts so that it runs on a specific host as if it as been chosen in first place.

---
- name: Delegate single running task to dynamically chosen host
  hosts: all
  gather_facts: true

  vars:
    max_mem_query: max_by(@, &ansible_memfree_mb).inventory_hostname
    selected_host: >-
      {{
        ansible_play_hosts |
        map('extract', hostvars) |
        list |
        json_query(max_mem_query)
      }}

  tasks:
    - name: Run a command on host with most memory
      debug:
        msg: "I would run on host {{ inventory_hostname }}"
      run_once: true
      delegate_to: "{{ selected_host }}"
      delegate_facts: true

Note: I only tested this against an inventory with two local docker instances to validate my playbook so the output is not really relevant (all instances have the same available memory, the first one is chosen which happens to delegate to itself). But I'm quite confident it will work and I'm ready to edit this to accommodate with some eventual quirks if not.