Skip to main content

Ansible Delegation

Introduction

When managing multiple servers with Ansible, you'll often encounter situations where you need to run a specific task on a different host than the current target. For example, you might need to add all your web servers to a load balancer, or you might want to interact with an external service from a specific machine. This is where Ansible's delegation features come in handy.

Ansible delegation allows you to execute specific tasks on a host different from the one currently being processed in the playbook. This powerful feature gives you flexibility in your automation workflows and enables complex orchestration scenarios.

Understanding Delegation Basics

The delegate_to Directive

The most common way to delegate tasks in Ansible is using the delegate_to directive. This tells Ansible to run a particular task on a specific host rather than the current host in the play.

yaml
- name: Ensure web server is added to load balancer
ansible.builtin.shell: /usr/local/bin/add_to_lb.sh {{ inventory_hostname }}
delegate_to: load_balancer.example.com

In this example, even though the playbook might be running against your web servers, this specific task will execute on the load_balancer.example.com host.

Delegation vs. Local Actions

Before we dive deeper, let's understand the difference between delegation and local actions:

  • Delegation: Run a task on a specific remote host
  • Local Action: Run a task on the Ansible control node itself

For local actions, you can use either:

yaml
- name: Take a local backup
local_action:
module: ansible.builtin.shell
cmd: /usr/local/bin/backup.sh

Or the equivalent:

yaml
- name: Take a local backup
ansible.builtin.shell: /usr/local/bin/backup.sh
delegate_to: localhost

Practical Examples of Ansible Delegation

Example 1: Adding Servers to a Load Balancer

Let's say you're deploying a set of web servers and need to add each one to a load balancer after it's configured:

yaml
- name: Configure web servers and update load balancer
hosts: webservers
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
become: yes

- name: Start nginx service
ansible.builtin.service:
name: nginx
state: started
become: yes

- name: Add server to load balancer
ansible.builtin.shell: /usr/local/bin/add_to_lb.sh {{ inventory_hostname }} {{ ansible_host }}
delegate_to: loadbalancer.example.com
become: yes

When you run this playbook:

  1. Ansible connects to each web server and installs nginx
  2. Ansible starts the nginx service on each web server
  3. Ansible connects to the loadbalancer and runs the script to add the current web server

Example 2: Database Backup Before Upgrade

Another common scenario is taking a database backup before upgrading:

yaml
- name: Upgrade database servers
hosts: db_servers
tasks:
- name: Backup database before upgrade
ansible.builtin.mysql_db:
name: production_db
state: dump
target: /backups/{{ inventory_hostname }}_pre_upgrade.sql
delegate_to: backup.example.com

- name: Perform database upgrade
ansible.builtin.shell: /usr/local/bin/upgrade_db.sh
become: yes

In this example, the backup task runs on the backup server, not on the database servers themselves.

Advanced Delegation Techniques

Facts and Variables with Delegation

When you delegate a task, Ansible uses the variables from the delegated host, not the current inventory host. This can sometimes lead to confusion.

For example, if you delegate a task to host_b while running a play against host_a, the ansible_* variables will reflect the properties of host_b during that task.

If you need to access facts from both hosts, you can use the setup module with delegate_facts: yes:

yaml
- name: Gather facts from load balancer
ansible.builtin.setup:
delegate_to: loadbalancer.example.com
delegate_facts: yes

Now you can reference facts from both the current host and the load balancer:

yaml
- name: Debug information from both hosts
ansible.builtin.debug:
msg: "Current host {{ ansible_hostname }} and load balancer {{ hostvars['loadbalancer.example.com'].ansible_hostname }}"

Using delegate_facts

The delegate_facts directive instructs Ansible to store the gathered facts under the delegated host instead of the current inventory host:

yaml
- name: Gather information from the database
ansible.builtin.mysql_info:
login_user: root
login_password: "{{ mysql_root_password }}"
delegate_to: db.example.com
delegate_facts: yes
register: db_info

This approach is particularly useful when you need to gather information from multiple systems and then use that data for decision-making later in your playbook.

Delegation with Loops

You can combine delegation with loops to perform actions on multiple hosts:

yaml
- name: Add all web servers to monitoring
ansible.builtin.shell: /usr/local/bin/add_to_monitoring.sh {{ item }}
delegate_to: monitor.example.com
loop: "{{ groups['webservers'] }}"

This task runs once for each webserver, but executes on the monitoring server each time.

Understanding Delegation Flow

To better understand how delegation works, let's look at a diagram:

Run Once with Delegation

Sometimes you want to run a task just once but delegate it to a specific host. The run_once directive is perfect for this scenario:

yaml
- name: Notify Slack that deployment has started
ansible.builtin.uri:
url: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
method: POST
body_format: json
body:
text: "Deployment to {{ inventory_hostname }} has started"
delegate_to: localhost
run_once: true

This task will run only once for the entire play, regardless of how many hosts are in the play, and it will execute on localhost.

Common Delegation Patterns

Load Balancer Management

yaml
- name: Remove server from load balancer before maintenance
ansible.builtin.shell: /usr/local/bin/remove_from_lb.sh {{ inventory_hostname }}
delegate_to: loadbalancer.example.com

- name: Perform maintenance tasks
ansible.builtin.shell: /usr/local/bin/maintenance.sh

- name: Add server back to load balancer
ansible.builtin.shell: /usr/local/bin/add_to_lb.sh {{ inventory_hostname }}
delegate_to: loadbalancer.example.com

Centralized Backup

yaml
- name: Backup configuration before changes
ansible.builtin.fetch:
src: /etc/myapp/config.ini
dest: /backups/{{ inventory_hostname }}/
flat: yes
delegate_to: backup.example.com

External Service Integration

yaml
- name: Update DNS records
community.aws.route53:
state: present
zone: example.com
record: "{{ inventory_hostname }}.example.com"
type: A
value: "{{ ansible_host }}"
delegate_to: localhost

Best Practices for Delegation

  1. Be explicit with variables: When using delegation, be clear about which host's variables you're referencing by using hostvars.

  2. Consider connection settings: Remember that the delegated task will use the connection settings for the delegated host from your inventory.

  3. Use run_once appropriately: For tasks that should only happen once per playbook run, combine with delegation for efficiency.

  4. Document your delegations: Clearly comment your playbooks to explain why delegation is being used.

  5. Test thoroughly: Delegation can introduce complexity, so test your playbooks in a safe environment first.

Common Issues and Troubleshooting

Variable Confusion

If you're getting unexpected variable values when using delegation, explicitly reference variables using hostvars:

yaml
- name: Debug information
ansible.builtin.debug:
msg: "Current host IP: {{ hostvars[inventory_hostname]['ansible_host'] }} vs Load balancer IP: {{ hostvars['loadbalancer.example.com']['ansible_host'] }}"
delegate_to: loadbalancer.example.com

Connection Issues

If you're having connection problems with delegated tasks, check:

  1. That the delegated host is in your inventory
  2. That the connection settings (SSH keys, etc.) are correct
  3. That any necessary SSH agent forwarding is configured

Summary

Ansible delegation is a powerful feature that allows you to create sophisticated automation workflows by executing tasks on specific hosts. It's particularly useful for scenarios involving load balancers, backups, monitoring systems, and other cross-server orchestration needs.

Key concepts to remember:

  • Use delegate_to to run a task on a specific host
  • delegate_facts: yes stores facts under the delegated host
  • run_once: true combined with delegation prevents repetitive tasks
  • Be explicit about variables when using delegation

By mastering delegation, you can create more efficient and sophisticated Ansible playbooks that handle complex infrastructure automation scenarios.

Additional Resources

Exercises

  1. Create a playbook that deploys a web application to multiple servers and then updates a central load balancer.

  2. Write a playbook that backs up database servers to a central backup server before performing an upgrade.

  3. Develop a playbook that deploys a new application and then registers each server with a monitoring system.

  4. Create a playbook that updates DNS records via a central DNS server whenever new web servers are provisioned.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)