If you are writing roles with Ansible, you must already have thought about implementing a loop, a loop of loops with Ansible, and wonder how. The ability to execute tasks in loops is primordial. This guide will provide multiple loop examples in Ansible, starting with a basic loop and progressing to more advanced scenarios.
Basic loop
Let’s start with the most basic loop as an introduction.
In the following playbook, called playbook.yml, a list of numbers is created, from 1 to 5. Then a loop on the debug task displays each number.
# playbook.yml
- name: Loop examples
hosts: localhost
connection: local
gather_facts: False
tasks:
- set_fact:
numbers: [1,2,3,4,5]
- name: Most basic loop
debug:
msg: '{{ item }}'
loop: '{{ numbers }}'
You can test it by running the command “ansible-playbook playbook.yml”.
$ ansible-playbook playbook.yml
PLAY [Use vars from dbservers] ****************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [Most basic loop] ************************************************************************************************************************************************************************
ok: [localhost] => (item=1) => {
"msg": 1
}
ok: [localhost] => (item=2) => {
"msg": 2
}
ok: [localhost] => (item=3) => {
"msg": 3
}
ok: [localhost] => (item=4) => {
"msg": 4
}
ok: [localhost] => (item=5) => {
"msg": 5
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Loop of loops
In most use cases, you want to loop multiple tasks sequentially. The first thought is to use a block statement, but a block doesn’t accept a loop. The solution is to use “ansible.builtin.include_tasks” and loop on the task file.
The usage of loop_control is recommended to rename the loop_var name and not use “item”. See below the example with the “playbook.yml” and “loop.yml” files.
# playbook.yml
- name: Loop examples
hosts: localhost
connection: local
gather_facts: False
tasks:
- set_fact:
numbers: [1,2,3,4,5]
- name: loop multiple tasks with include_task
ansible.builtin.include_tasks:
file: loop.yml
loop: '{{ numbers }}'
loop_control:
loop_var: number
# loop.yml
- debug:
msg: 'First task of loop.yml'
- debug:
var: number
The results of running the playbook should be as below.
$ ansible-playbook playbook.yml
PLAY [Loop examples] **************************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [loop multiple tasks with include_task] **************************************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/loop.yml for localhost => (item=1)
included: /Users/kke/dbi/blog/ansible_loop/loop.yml for localhost => (item=2)
included: /Users/kke/dbi/blog/ansible_loop/loop.yml for localhost => (item=3)
included: /Users/kke/dbi/blog/ansible_loop/loop.yml for localhost => (item=4)
included: /Users/kke/dbi/blog/ansible_loop/loop.yml for localhost => (item=5)
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "First task of loop.yml"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 1
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "First task of loop.yml"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 2
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "First task of loop.yml"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 3
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "First task of loop.yml"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 4
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "First task of loop.yml"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 5
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=16 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Conditional on include_tasks
Adding a condition on the include_tasks is only evaluated once. It means that if the condition is True, it will iterate until the end even though the condition might become False, which is the intended result of the condition of include_tasks. See the following example.
# playbook.yml
- name: Loop examples
hosts: localhost
connection: local
gather_facts: False
tasks:
- set_fact:
numbers: [1,2,3,4,5]
- set_fact:
continue_task: true
- when: continue_task == true
name: When on include task only (evaluted at the beginning)
ansible.builtin.include_tasks:
file: condition.yml
loop: '{{ numbers }}'
loop_control:
loop_var: number
# condition.yml
- debug:
var: number
- when: number >= 3
set_fact:
continue_task: false
- debug:
msg: 'current number: {{ number }}. Condition.yml running on number < 3'
Results of the running playbook.
$ ansible-playbook playbook.yml
PLAY [Loop examples] **************************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [When on include task only (evaluted at the beginning)] **********************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=1)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=2)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=3)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=4)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=5)
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 1
}
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 1. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 2
}
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 2. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 3
}
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 3. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 4
}
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 4. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 5
}
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 5. Condition.yml running on number < 3"
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=20 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
If you want the condition to apply on every task, you can either add the statement “when” on every task in “condition.yml”, or you can use the statement “apply” on “ansible.builtin.include_tasks”. Choose the solution that suits the best for the role or tasks you are writing.
The below example will use the statement “apply”.
# playbook.yml
- name: Loop examples
hosts: localhost
connection: local
gather_facts: False
tasks:
- set_fact:
numbers: [1,2,3,4,5]
- set_fact:
continue_task: true
- name: When applied on all tasks included (evaluated each time)
ansible.builtin.include_tasks:
file: condition.yml
apply:
when: continue_task == true
loop: '{{ numbers }}'
loop_control:
loop_var: number
Output of the playbook. The results should print the number until “3” but not the second debug message “current number 3. Condition.yml running on number < 3”.
$ ansible-playbook playbook.yml
PLAY [Loop examples] **************************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [When applied on all tasks included (evalued each time)] *********************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=1)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=2)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=3)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=4)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=5)
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 1
}
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 1. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 2
}
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 2. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 3
}
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=13 changed=0 unreachable=0 failed=0 skipped=9 rescued=0 ignored=0
Advanced loop: do tasks until succeed, or fail at Xth attempt
In this last example, I want to make a loop of tasks. The problem is that multiple tasks may fail for multiple reasons (network, unavailability of external services, awaiting process from external services, etc.). Therefore I want to retry the task at least 5th times, before exiting the playbook run.
The example will include a commented task, to provide a more concrete real use case example. The real example is the following. Fetch the ID of an item from an external service, use this ID to fetch its status to the external service, and only proceed if the item status is completed (successful, completed) or fail the task if it returns a failure state. The task may fail on the fetch of ID (due to network issues or unavailability of the external service) and on the status fetching if it is still ongoing, which will result in retrying the whole task. Note that it is not completely optimized to make it simpler to understand and read (for example we could ignore the fetching of ID if it was already gotten in a previous iteration).
For the simplicity of the setup, a simple condition on “number” is used instead, to showcase the retry of tasks and failure of the play. It causes the playbook’s execution to fail if the number is above 3.
# playbook.yml
- name: Loop examples
hosts: localhost
connection: local
gather_facts: False
tasks:
- set_fact:
numbers: [1,2,3,4,5]
- set_fact:
continue_task: true
- name: Example of a complex loop. Loop on number, do function(number) until suceed, or fail at the fifth attempt
ansible.builtin.include_tasks:
file: function.yml
loop: '{{ numbers }}'
loop_control:
loop_var: number
# function.yml
- block:
- when: init_count | default(true)
ansible.builtin.set_fact:
retry_count: 0
- debug:
msg: 'Current number is {{ number }}, and current retry count is {{ retry_count }}'
# Do an action, use the result to do another action or checks (for example a wget, curl, or another request to get an ID)
# For the simplicity of the example, I simply do an echo
- name: API - get ID
ansible.builtin.shell: 'echo {{ number }}'
register: _api_result
# Use the result from the precedent task
- name: Use the ID to check another API if process is succesful
## An exemple of a use case, using the id
# ansible.builtin.uri:
# url: 'https://example.com/status?id={{ _api_result.stdout }}'
# method: GET
# status_code: 200
# register: _check_status
# until:
# - _check_status.json is defined
# - _check_status.json.status in ["SUCCESSFUL", "COMPLETED", "FAILURE"]
# failed_when: _check_status.json is not defined or _check_status.json.status in ["FAILURE"]
# delay: '5'
# retries: '3'
## For the simplicity, I just used a failed_when on debug
debug:
msg: 'Testing that api result is a > 3'
failed_when: _api_result.stdout|int > 3
rescue:
- when: _check_status.json is defined and _check_status.json.status in ["FAILURE"]
name: Fail if process return Failure
ansible.builtin.fail:
msg: status failure
- name: Fail Task in case of total failure after a certain amount of retry
ansible.builtin.fail:
msg: "5 retries attempted, failed perform desired result"
when: retry_count | int >= 5
# Pause the playbook if necessary.
# - ansible.builtin.pause:
# seconds: '5'
- name: Increment Retry Count
ansible.builtin.set_fact:
retry_count: "{{ retry_count | int + 1 }}"
# Retry the function.yml and indicate to increment the counter.
- name: Retry function
ansible.builtin.include_tasks: function.yml
vars:
init_count: false
Result of the execution.
$ ansible-playbook playbook.yml
PLAY [Loop examples] **************************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [When applied on all tasks included (evalued each time)] *********************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=1)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=2)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=3)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=4)
included: /Users/kke/dbi/blog/ansible_loop/condition.yml for localhost => (item=5)
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 1
}
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 1. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 2
}
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "current number: 2. Condition.yml running on number < 3"
}
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"number": 3
}
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=13 changed=0 unreachable=0 failed=0 skipped=9 rescued=0 ignored=0
kke@DBI-LT-KKE ansible_loop % ansible-playbook playbook.yml
PLAY [Loop examples] **************************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [Example of a complex loop. Loop on number, do function(number) until suceed, or fail at the fifth attempt] ******************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost => (item=1)
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost => (item=2)
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost => (item=3)
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost => (item=4)
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost => (item=5)
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 1, and current retry count is 0"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
ok: [localhost] => {
"msg": "Testing that api result is a > 3"
}
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 2, and current retry count is 0"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
ok: [localhost] => {
"msg": "Testing that api result is a > 3"
}
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 3, and current retry count is 0"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
ok: [localhost] => {
"msg": "Testing that api result is a > 3"
}
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 4, and current retry count is 0"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"msg": "Testing that api result is a > 3"
}
TASK [Fail if process return Failure] *********************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail Task in case of total failure after a certain amount of retry] *********************************************************************************************************************
skipping: [localhost]
TASK [Increment Retry Count] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [Retry function] *************************************************************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 4, and current retry count is 1"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"msg": "Testing that api result is a > 3"
}
TASK [Fail if process return Failure] *********************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail Task in case of total failure after a certain amount of retry] *********************************************************************************************************************
skipping: [localhost]
TASK [Increment Retry Count] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [Retry function] *************************************************************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 4, and current retry count is 2"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"msg": "Testing that api result is a > 3"
}
TASK [Fail if process return Failure] *********************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail Task in case of total failure after a certain amount of retry] *********************************************************************************************************************
skipping: [localhost]
TASK [Increment Retry Count] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [Retry function] *************************************************************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 4, and current retry count is 3"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"msg": "Testing that api result is a > 3"
}
TASK [Fail if process return Failure] *********************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail Task in case of total failure after a certain amount of retry] *********************************************************************************************************************
skipping: [localhost]
TASK [Increment Retry Count] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [Retry function] *************************************************************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 4, and current retry count is 4"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"msg": "Testing that api result is a > 3"
}
TASK [Fail if process return Failure] *********************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail Task in case of total failure after a certain amount of retry] *********************************************************************************************************************
skipping: [localhost]
TASK [Increment Retry Count] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [Retry function] *************************************************************************************************************************************************************************
included: /Users/kke/dbi/blog/ansible_loop/function.yml for localhost
TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Current number is 4, and current retry count is 5"
}
TASK [API - get ID] ***************************************************************************************************************************************************************************
changed: [localhost]
TASK [Use the ID to check another API if process is succesful] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"msg": "Testing that api result is a > 3"
}
TASK [Fail if process return Failure] *********************************************************************************************************************************************************
skipping: [localhost]
TASK [Fail Task in case of total failure after a certain amount of retry] *********************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "5 retries attempted, failed perform desired result"}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=42 changed=9 unreachable=0 failed=1 skipped=16 rescued=6 ignored=0
Conclusion
Loop is a necessity in IT, the base for automation. At first, it might be disorientating with Ansible, but it becomes quite easy to understand and use with a little practice. The official documentation provided by Ansible also covers a lot and explains well the concept of loop, it will be your best friend in your journey to master Ansible.
Links
Ansible – Official documentation
Blog – Faster Ansible
Blog – Ansible Automates – Event Driven & Lightspeed
Blog – Specify hosts in ansible-playbook command line
Blog – Ansible Event Driven Automation
Blog – Create and manage Ansible Execution Environments