Task Delegation
What is Task Delegation? Task delegation allows you to execute a task on one host while referencing data from other hosts. This enables complex orchestration scenarios like updating load balancers, performing centralized operations, or coordinating between dependent systems.
Understanding Delegation
Why Delegate Tasks?
Delegation solves orchestration challenges:
- Load Balancer Management: Remove servers from pool during updates
- Centralized Operations: Run API calls from control node
- Cross-Host Dependencies: Update monitoring from monitored hosts
- Local Operations: Perform file operations on Ansible controller
- Database Management: Execute admin commands from primary server
Common Use Cases
- Updating load balancer configurations
- Calling external APIs or webhooks
- Writing logs to centralized server
- Performing DNS updates
- Coordinating database replication
delegate_to Keyword
Basic Delegation
Delegate tasks to specific hosts using delegate_to:
---
- name: Web server rolling update
hosts: webservers
serial: 1
tasks:
- name: Remove server from load balancer
ansible.builtin.command: >
/usr/bin/remove_from_pool {{ inventory_hostname }}
delegate_to: loadbalancer.example.com
- name: Update application
ansible.builtin.yum:
name: myapp
state: latest
- name: Restart application
ansible.builtin.service:
name: myapp
state: restarted
- name: Wait for application to be ready
ansible.builtin.wait_for:
port: 8080
state: started
timeout: 60
- name: Add server back to load balancer
ansible.builtin.command: >
/usr/bin/add_to_pool {{ inventory_hostname }}
delegate_to: loadbalancer.example.com
Delegate to Localhost
Run tasks on the Ansible control node:
---
- name: Manage cloud infrastructure
hosts: webservers
tasks:
- name: Create DNS record for each server
community.general.cloudflare_dns:
zone: example.com
record: "{{ inventory_hostname_short }}"
type: A
value: "{{ ansible_host }}"
account_email: admin@example.com
account_api_key: "{{ cloudflare_api_key }}"
delegate_to: localhost
- name: Log deployment
ansible.builtin.lineinfile:
path: /var/log/ansible/deployments.log
line: "{{ ansible_date_time.iso8601 }} - Deployed to {{ inventory_hostname }}"
create: yes
delegate_to: localhost
Delegate to Another Host in Inventory
---
- name: Database replication setup
hosts: db_replicas
tasks:
- name: Get primary server data directory
ansible.builtin.command: cat /etc/postgresql/data_dir
delegate_to: "{{ groups['db_primary'][0] }}"
register: primary_data_dir
- name: Configure replication from primary
ansible.builtin.template:
src: recovery.conf.j2
dest: /var/lib/postgresql/recovery.conf
vars:
primary_host: "{{ groups['db_primary'][0] }}"
primary_port: 5432
local_action Shorthand
Using local_action
Simplified syntax for delegating to localhost:
---
- name: Call external API
hosts: webservers
tasks:
# Using delegate_to (verbose)
- name: Send notification (delegate_to)
ansible.builtin.uri:
url: https://api.slack.com/webhooks/YOUR_WEBHOOK
method: POST
body_format: json
body:
text: "Deploying to {{ inventory_hostname }}"
delegate_to: localhost
# Using local_action (concise)
- name: Send notification (local_action)
local_action:
module: ansible.builtin.uri
url: https://api.slack.com/webhooks/YOUR_WEBHOOK
method: POST
body_format: json
body:
text: "Deploying to {{ inventory_hostname }}"
Local File Operations
---
- name: Collect logs from servers
hosts: all
tasks:
- name: Fetch application logs
ansible.builtin.fetch:
src: /var/log/app/app.log
dest: /tmp/collected-logs/{{ inventory_hostname }}/app.log
flat: yes
- name: Create summary report locally
local_action:
module: ansible.builtin.copy
content: |
Server: {{ inventory_hostname }}
IP: {{ ansible_host }}
Status: Logs collected
dest: /tmp/collected-logs/{{ inventory_hostname }}/summary.txt
run_once Directive
Execute Task Once for All Hosts
Run a task only once, even when multiple hosts are targeted:
---
- name: Database migration
hosts: app_servers
tasks:
- name: Run database migration (only once)
ansible.builtin.command: /opt/app/migrate.sh
run_once: true
delegate_to: "{{ groups['app_servers'][0] }}"
- name: Send deployment notification (only once)
ansible.builtin.uri:
url: https://hooks.slack.com/services/YOUR/WEBHOOK
method: POST
body_format: json
body:
text: "Deployment started for {{ groups['app_servers'] | length }} servers"
run_once: true
delegate_to: localhost
- name: Deploy application (runs on all hosts)
ansible.builtin.copy:
src: /tmp/app.jar
dest: /opt/app/app.jar
run_once with Loops
Loop over all hosts in a single task execution:
---
- name: Update monitoring for all servers
hosts: webservers
tasks:
- name: Register all servers with monitoring
ansible.builtin.uri:
url: https://monitoring.example.com/api/servers
method: POST
body_format: json
body:
hostname: "{{ item }}"
ip: "{{ hostvars[item]['ansible_host'] }}"
environment: production
loop: "{{ groups['webservers'] }}"
run_once: true
delegate_to: localhost
Delegation Patterns
Rolling Update with Load Balancer
---
- name: Zero-downtime deployment
hosts: webservers
serial: 2 # Update 2 servers at a time
max_fail_percentage: 25
tasks:
- name: Disable server in load balancer
ansible.builtin.command: >
haproxy-mgmt disable {{ inventory_hostname }}
delegate_to: "{{ item }}"
loop: "{{ groups['loadbalancers'] }}"
- name: Wait for connections to drain
ansible.builtin.wait_for:
timeout: 30
delegate_to: localhost
- name: Stop application
ansible.builtin.service:
name: myapp
state: stopped
- name: Deploy new version
ansible.builtin.copy:
src: /releases/myapp-v2.0.jar
dest: /opt/myapp/myapp.jar
- name: Start application
ansible.builtin.service:
name: myapp
state: started
- name: Health check
ansible.builtin.uri:
url: "http://{{ ansible_host }}:8080/health"
status_code: 200
register: health_check
until: health_check.status == 200
retries: 5
delay: 10
delegate_to: localhost
- name: Enable server in load balancer
ansible.builtin.command: >
haproxy-mgmt enable {{ inventory_hostname }}
delegate_to: "{{ item }}"
loop: "{{ groups['loadbalancers'] }}"
Centralized Configuration Management
---
- name: Update configuration server
hosts: application_servers
tasks:
- name: Register service with config server
ansible.builtin.uri:
url: https://config.example.com/api/services
method: POST
body_format: json
body:
service_name: "{{ service_name }}"
host: "{{ inventory_hostname }}"
port: "{{ service_port }}"
metadata:
version: "{{ app_version }}"
environment: "{{ environment }}"
delegate_to: localhost
run_once: false # Runs for each host
- name: Update DNS records
community.general.route53:
state: present
zone: example.com
record: "{{ inventory_hostname }}.example.com"
type: A
value: "{{ ansible_host }}"
overwrite: yes
delegate_to: localhost
Database Coordination
---
- name: Configure database replication
hosts: db_replicas
tasks:
- name: Backup primary database before adding replica
community.postgresql.postgresql_db:
name: production_db
state: dump
target: /tmp/pre-replica-backup.sql
delegate_to: "{{ groups['db_primary'][0] }}"
run_once: true
- name: Get replication credentials from primary
ansible.builtin.command: >
psql -t -c "SELECT * FROM pg_create_physical_replication_slot('replica_{{ inventory_hostname_short }}');"
delegate_to: "{{ groups['db_primary'][0] }}"
register: replication_slot
- name: Configure replica to follow primary
ansible.builtin.template:
src: postgresql.conf.j2
dest: /etc/postgresql/postgresql.conf
notify: restart postgresql
Delegation Variables and Facts
Understanding Variable Context
When delegating, variable context can be confusing:
---
- name: Variable delegation example
hosts: webservers
tasks:
- name: Example showing variable context
ansible.builtin.debug:
msg: |
Current host (inventory_hostname): {{ inventory_hostname }}
Ansible host IP (ansible_host): {{ ansible_host }}
Delegated to: localhost
To access facts from current host after delegation:
Original host: {{ hostvars[inventory_hostname]['ansible_host'] }}
delegate_to: localhost
delegate_facts
Control where gathered facts are assigned:
---
- name: Gather facts with delegation
hosts: webservers
tasks:
- name: Gather facts from load balancer
ansible.builtin.setup:
delegate_to: loadbalancer.example.com
delegate_facts: true
# Facts will be assigned to loadbalancer.example.com
- name: Use load balancer facts
ansible.builtin.debug:
msg: "Load balancer OS: {{ hostvars['loadbalancer.example.com']['ansible_distribution'] }}"
Delegation with Parallelism
Serial Execution with Delegation
---
- name: Controlled parallel deployment
hosts: webservers
serial: 5 # Process 5 hosts at a time
tasks:
- name: Update load balancer config
ansible.builtin.template:
src: haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
delegate_to: loadbalancer.example.com
# This could cause issues if multiple hosts update simultaneously
- name: Better approach - use run_once
ansible.builtin.template:
src: haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
delegate_to: loadbalancer.example.com
run_once: true
# Runs only once per serial batch
Throttle Delegation
---
- name: Rate-limited API calls
hosts: servers
tasks:
- name: Register with API (rate-limited)
ansible.builtin.uri:
url: https://api.example.com/register
method: POST
body_format: json
body:
hostname: "{{ inventory_hostname }}"
delegate_to: localhost
throttle: 1 # Only 1 host at a time makes API call
Delegation Limitations
Tasks That Cannot Be Delegated
Some tasks don't support delegation:
includeandimportstatementsadd_hostandgroup_bydebugmodule- Tasks with
connection: localalready set
---
- name: Delegation limitations
hosts: all
tasks:
# This works
- name: Run command with delegation
ansible.builtin.command: echo "hello"
delegate_to: localhost
# This doesn't work (ignored)
- name: Debug with delegation (ignored)
ansible.builtin.debug:
msg: "This runs on current host, not delegated"
delegate_to: localhost
# This doesn't work
- name: Add host with delegation (error)
ansible.builtin.add_host:
name: newhost
delegate_to: localhost # Will fail
Best Practices
Delegation Best Practices:
- Use run_once: Combine with delegation for single-execution tasks
- Check Idempotency: Ensure delegated tasks are idempotent
- Mind Variables: Use hostvars to access original host data
- Serial + Delegation: Be careful with concurrent delegated tasks
- Error Handling: Add retries for delegated network operations
- Document Intent: Comment why delegation is necessary
- Test Thoroughly: Delegation can create race conditions
- Use throttle: Rate-limit API calls when delegating to localhost
Common Issues
Connection Failures
If delegation fails to connect:
- Verify the delegated host is in inventory
- Check SSH connectivity to delegated host
- Ensure proper credentials for delegated host
- Use
delegate_to: localhostfor local operations
Variable Confusion
If variables show wrong values:
- Remember variables refer to current host, not delegated host
- Use
hostvars[inventory_hostname]to access original host vars - Use
delegate_facts: truewhen gathering facts
Race Conditions
If concurrent updates cause issues:
- Use
run_once: truefor single-execution tasks - Implement
serial: 1for sequential processing - Add
throttleto limit concurrency - Use file locking for shared resource updates
Next Steps
- Async & Parallel Execution - Advanced execution control
- Playbooks - Learn playbook fundamentals
- Tags - Selective task execution
- Advanced Topics - Complex orchestration patterns
- Try the Playground - Practice delegation concepts