Loading Now

Using Loops in Ansible Playbooks

Using Loops in Ansible Playbooks

Loops are among the most valuable functions in Ansible, enabling you to iterate through lists, dictionaries, and other data formats to execute tasks repeatedly with varying parameters. Rather than crafting repetitive tasks for similar operations, loops assist in creating cleaner and more maintainable playbooks, thereby minimising code redundancy. This guide will explore the different types of loops available in Ansible, provide practical examples, discuss common troubleshooting issues, and outline best practices to enhance your automation processes.

Understanding Ansible Loops

Loops in Ansible function by accepting a list of items and performing a task for each item in that list. You can access the current item using the item variable within the task. Recent versions of Ansible (2.5+) utilise the loop keyword, although older with_* methods are still available for compatibility.

The general format is as follows:

- name: Example of a loop task
  module_name:
    parameter: "{{ item }}"
  loop:
    - item1
    - item2
    - item3

During execution, Ansible processes each iteration in sequence, substituting the item variable with the current element from the list. This functionality is compatible with nearly any Ansible module, rendering loops highly versatile for configuration management tasks.

Step-by-Step Guide to Implementation

Let’s begin with straightforward loop examples and progress to more intricately structured scenarios.

Basic List Loops

The simplest loop iterates through a basic list of values:

- name: Install several packages
  package:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - mysql-server
    - php-fpm
    - git

Using Dictionary Loops

Dictionary loops are crucial when dealing with key-value pairs:

- name: Create user accounts with designated groups
  user:
    name: "{{ item.key }}"
    groups: "{{ item.value.groups }}"
    shell: "{{ item.value.shell }}"
    state: present
  loop: "{{ users | dict2items }}"
  vars:
    users:
      john:
        groups: ['sudo', 'developers']
        shell: /bin/bash
      jane:
        groups: ['admin', 'operators']
        shell: /bin/zsh

Nested Loops

For scenarios requiring iteration within iterations, use loop paired with the subelements filter:

- name: Set up firewall rules for various Servers
  firewalld:
    port: "{{ item.1 }}"
    permanent: yes
    state: enabled
    zone: "{{ item.0.zone }}"
  loop: "{{ Servers | subelements('ports') }}"
  vars:
    Servers:
      - name: web-server
        zone: public
        ports: ['80/tcp', '443/tcp']
      - name: db-server
        zone: internal
        ports: ['3306/tcp', '5432/tcp']

Conditional Loops

Incorporate loops with conditionals for enhanced control over flow:

- name: Install packages only on designated distributions
  package:
    name: "{{ item.name }}"
    state: present
  loop:
    - { name: 'apache2', distro: 'Ubuntu' }
    - { name: 'httpd', distro: 'CentOS' }
    - { name: 'nginx', distro: 'all' }
  when: item.distro == ansible_distribution or item.distro == 'all'

Real-World Applications and Examples

Setting Up Databases with Multiple Schemas

An example for establishing multiple database schemas:

- name: Create multiple databases and users
  mysql_db:
    name: "{{ item.db_name }}"
    state: present
  loop:
    - { db_name: 'app_production', user: 'app_prod', password: 'secure_prod_pass' }
    - { db_name: 'app_staging', user: 'app_stage', password: 'secure_stage_pass' }
    - { db_name: 'app_development', user: 'app_dev', password: 'secure_dev_pass' }

- name: Create database users with necessary privileges
  mysql_user:
    name: "{{ item.user }}"
    password: "{{ item.password }}"
    priv: "{{ item.db_name }}.*:ALL"
    state: present
  loop:
    - { db_name: 'app_production', user: 'app_prod', password: 'secure_prod_pass' }
    - { db_name: 'app_staging', user: 'app_stage', password: 'secure_stage_pass' }
    - { db_name: 'app_development', user: 'app_dev', password: 'secure_dev_pass' }

Deploying SSL Certificates

Implementation of SSL certificates across various virtual hosts:

- name: Transfer SSL certificates for multiple domains
  copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    mode: 0600
    owner: root
    group: root
  loop:
    - { src: 'certs/example.com.crt', dest: '/etc/ssl/certs/example.com.crt' }
    - { src: 'certs/api.example.com.crt', dest: '/etc/ssl/certs/api.example.com.crt' }
    - { src: 'certs/admin.example.com.crt', dest: '/etc/ssl/certs/admin.example.com.crt' }
  notify: restart nginx

Creating Configuration File Templates

Generation of configuration files for multiple services:

- name: Generate service configuration files
  template:
    src: "{{ item.template }}"
    dest: "{{ item.dest }}"
    backup: yes
  loop:
    - { template: 'nginx.conf.j2', dest: '/etc/nginx/nginx.conf' }
    - { template: 'php-fpm.conf.j2', dest: '/etc/php/7.4/fpm/php-fpm.conf' }
    - { template: 'mysql.cnf.j2', dest: '/etc/mysql/mysql.conf.d/mysqld.cnf' }
  notify:
    - restart nginx
    - restart php-fpm
    - restart mysql

Comparing Loop Types

Loop Type Use Case Performance Complexity Best For
Basic Loop Simple list iterations Fast Low Installing packages, file handling
Dictionary Loop Processing key-value pairs Medium Medium User setups, service configurations
Nested Loop Data requiring multi-dimensional iterations Slower High Complex setups, rule configurations
Conditional Loop Selective iterations Variable Medium Tasks based on specific environments
Range Loop Working with numerical sequences Fast Low Managing port ranges, numbered assets

Advanced Loop Techniques

Employing loop_control

The loop_control directive grants additional control over loop execution:

- name: Process extensive dataset with custom loop variable
  debug:
    msg: "Handling user {{ user_item.name }} with ID {{ user_item.id }}"
  loop: "{{ user_database }}"
  loop_control:
    loop_var: user_item
    pause: 2
    label: "{{ user_item.name }}"

Optimising Performance with Until Loops

For tasks that require waiting for certain conditions, apply until loops:

- name: Wait until the service is operational
  uri:
    url: "http://{{ item }}/health"
    method: GET
  register: health_check
  until: health_check.status == 200
  retries: 30
  delay: 10
  loop:
    - web-server-1.example.com
    - web-server-2.example.com
    - web-server-3.example.com

Simplifying Complex Data Structures

Utilise the flatten filter for nesting lists:

- name: Install packages from several packages
  package:
    name: "{{ item }}"
    state: present
  loop: "{{ [base_packages, web_packages, db_packages] | flatten }}"
  vars:
    base_packages: ['curl', 'wget', 'vim']
    web_packages: ['nginx', 'apache2']
    db_packages: ['mysql-server', 'postgresql']

Common Challenges and Troubleshooting

Variable Scope Conflicts

A common issue arises when loop variables interfere with existing variables:

# Issue: 'item' variable conflicts
- name: Complicated nested loop
  debug:
    msg: "Outer: {{ item }}, Inner: {{ item }}"  # Inner loop overrides outer
  loop: "{{ outer_list }}"
  # Inner task would disrupt variable access

# Solution: Define loop_control with distinct variable names
- name: Resolved nested loop
  debug:
    msg: "server: {{ server_item }}, Port: {{ port_item }}"
  loop: "{{ Servers }}"
  loop_control:
    loop_var: server_item

Performance Problems with Large Datasets

Loops can slow down when dealing with extensive datasets. Here are some strategies for optimisation:

  • Incorporate async and poll for tasks that can be processed in parallel
  • Use batching with the batch filter
  • Consider using block statements to consolidate related loop tasks
  • Implement changed_when and failed_when to lessen unnecessary iterations
- name: Process extensive file list in groups
  file:
    path: "{{ item }}"
    state: absent
  loop: "{{ large_file_list | batch(50) | list }}"
  async: 300
  poll: 0

Handling Errors in Loops

Ensure robust error handling to avoid a single failure halting the entire loop:

- name: Install packages with defined error handling
  package:
    name: "{{ item }}"
    state: present
  loop: "{{ package_list }}"
  register: package_results
  failed_when: false
  changed_when: package_results.rc == 0

- name: Report failed package installations
  debug:
    msg: "Installation failed for: {{ item.item }}"
  loop: "{{ package_results.results }}"
  when: item.rc != 0

Best Practices and Security Considerations

Security Guidelines

  • Avoid disclosing sensitive information in loop labels – use _log: true for sensitive tasks
  • Validate incoming data prior to processing in loops
  • Utilise loop_control.label to obscure sensitive data in logs
  • Implement error handling to avoid disclosing sensitive information
- name: Create users with passwords (secured)
  user:
    name: "{{ item.name }}"
    password: "{{ item.password | password_hash('sha512') }}"
    state: present
  loop: "{{ users }}"
  _log: true
  loop_control:
    label: "{{ item.name }}"

Performance Guidelines

  • Choose the most specific loop types whenever possible
  • Avoid using nested loops – flatten data structures when it is practical to do so
  • Early conditional checks can reduce unnecessary iterations
  • Use run_once for tasks that only need to be executed on one machine
  • Consider using delegate_to for tasks that should be performed on specified hosts

Maintaining Code Quality

  • Employ descriptive variable names in loop_control.loop_var
  • Keep loop logic streamlined – complex operations should be separated into individual tasks
  • Add comments to explain complex loop constructions
  • Use variables to manage loop data rather than inline definitions

Integrating with Other Ansible Features

Loops work seamlessly with other Ansible functionalities such as handlers, blocks, and roles. Here’s an example of how to use these combinations:

- name: Complex loop with block and rescue
  block:
    - name: Set up services
      template:
        src: "{{ item.template }}"
        dest: "{{ item.dest }}"
      loop: "{{ service_configs }}"
      notify: "restart {{ item.service }}"
  rescue:
    - name: Handle configuration failures
      debug:
        msg: "Configuration failure; reverting to defaults"
    - name: Restore default configurations
      copy:
        src: "defaults/{{ item.service }}.conf"
        dest: "{{ item.dest }}"
      loop: "{{ service_configs }}"

For thorough documentation on Ansible loops and their varied implementations, refer to the official Ansible documentation. The Ansible examples repository also provides a wealth of real-life implementations that you can adapt for your own requirements.

Mastering loops in Ansible playbooks significantly enhances both your automation proficiency and code maintainability. Commence with straightforward implementations and gradually introduce more advanced approaches as your automation demands evolve. Well-crafted loops not only reduce code redundancy but also improve the readability of your playbooks, making them simpler to troubleshoot.



This article includes information and content from a variety of online sources. We acknowledge and appreciate the contributions of all original authors, publishers, and websites. Although we have made significant efforts to accurately credit the source material, any unintentional omissions do not constitute copyright infringement. All trademarks, logos, and images mentioned herein are the property of their respective owners. Should you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and timely action.

This article serves informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted materials have been used without proper credit or in violation of copyright laws, this is unintentional, and we will promptly rectify it upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the content in any format is prohibited without the explicit written permission of the author and website owner. For permission or further inquiries, please contact us.