32

On the play-level, we have serial: 1 to allow us to run the whole play one host at a time. But I haven't found a simple way to do this on a single task. This is especially relevant, if the task in question doesn't perform proper locking (for whatever reason).

One obvious answer is to put the task in its own play. But that doesn't help with roles. (Having to put serial: 1 on the play using the role isn't really intuitive.)

essential
  • 109
Elrond
  • 618

7 Answers7

26

You can run a single task in serial (i.e. host-by-host) by adding throttle: 1 to it.

Example:

---
  • hosts: all tasks: - name: wait in parallel command: sleep 20 - name: wait in serial command: sleep 30 throttle: 1

References:

NB: throttle was introduced in Ansible 2.9

maxschlepzig
  • 1,224
20

If you don't want any parallelism in performing the steps in your playbook, set the fork level to 1:

ansible-playbook --forks=1 ...

You can also put this in your ansible cfg file:

[defaults]
forks=1

but if you want it on an individual basis, use the command line option above.

EDIT:

serial: 1 does something completely different: that is like running the playbook for each host in turn, waiting for completion of the complete playbook before moving on to the next host. forks=1 means run the first task in a play on one host before running the same task on the next host, so the first task will be run for each host before the next task is touched.

So you want forks=1 for just one play; unfortunately that is not currently possible.

wurtel
  • 3,999
7

There's a workaround to this problem - one can pass list of hosts (or a group) to with_items, and then use delegate_to with this list. This way task will be executed host by host.

For example:

- name: start and enable rabbitmq (run task host by host)
  service:
    name: "rabbitmq-server"
    state: "started"
    enabled: true
  delegate_to: "{{ item }}"
  with_items: "{{ groups['rabbitmq-cluster'] }}"
  run_once: true
2

If you are executing it on a single machine , then exclusive locks issue arises for more than one host .So you should execute one by one for all the hosts .For this you need to have --forks=1 being set when calling ansible playbook command. FOr example: ansible-playbook webserver.yml --forks=1 where webserver.yml has app01 and app02 inside your [webserver]

0

I often have a set of etra tasks for some hosts in a play book, not all hosts, just some.

This was my solution...

    - name: Include Host Tasks
      ansible.builtin.include_tasks: >-
        {{ lookup("first_found",
           'host_tasks/'+inventory_hostname+'.yml', '/dev/null' ) }}

This will run the extra tasks for just that specific host, located in the 'host_tasks' sub-directory of the playbook, if present. If there are no task file for that host nothing is done, and no error happens. VERY useful!

anthony
  • 325
0

For commands that can be run locally, use a loop to iterate over all the hosts in the play. This ONLY works if the command can be run locally. You could also run a command with ssh in it to the remote machines one by one in this manner, if keys are setup, but it becomes difficult when talking about escalation.

EG:

- name: Init New Appliances - Remove the known hosts entry for the server in case it has changed
  run_once: yes
  connection: local
  become: no
  command: "ssh-keygen -R {{ item }}"
  with_items:
  - "{{ inventory_hostname }}"
-2

Think what you want is

run_once: true