Ansible Delegation

In order to complete some configuration tasks, it may be necessary for actions to be taken on a different server than the one being configured. Some examples of this might include an action that requires waiting for the server to be restarted, adding a server to a load balancer or a monitoring server, or making changes to the DHCP or DNS database needed for the server being configured.

Delegating tasks to the local machine
When any action needs to be performed on the node running Ansible, it can be delegated to localhost by using delegate_to: localhost.
Here is a sample playbook which runs the command ps on the localhost using the delegate_to keyword, and displays the output using the debug module:

The local_action keyword is a shorthand syntax replacing delegate_to: localhost, and can be used on a per-task basis.

The previous playbook can be re-written using this shorthand syntax to delegate the task to the node running Ansible (localhost):

 

Delegating task to a host outside the play
Ansible can be configured to run a task on a host other than the one that is part of the play with delegate_to. The delegated module will still run once for every machine, but instead of running on the target machine, it will run on the host specified by delegate_to. The facts available will be the ones applicable to the original host and not the host the task is delegated to. The task has the context of the original target host, but it gets executed on the host the task is
delegated to.
The following example shows Ansible code that will delegate a task to an outside machine (in this case, loadbalancer-host). This example runs a command on the local balancer host to remove the managed hosts from the load balancer before deploying the latest version of the web stack. After that task is finished, a script is run to add the managed hosts back into the load balancer pool.

 

Delegating a task to a host that exists in the inventory
When delegating to a host listed in the inventory, the inventory data will be used when creating the connection to the delegation target. This would include settings for ansible_connection, ansible_host, ansible_port, ansible_user and so on. Only the connection-related variables are used; the rest are read from the managed host originally targeted.

 

Delegating a task to a host that does not exist in the inventory
When delegating a task to a host that does not exist in the inventory, Ansible will use the same connection type and details used for the managed host to connect to the delegating host. To adjust the connection details, use the add_host module to create an ephemeral host in your inventory with connection data defined.

When the preceding playbook is executed, Ansible will use the connection details for the ephemeral host demo while executing the task echo Hello on the delegate_to host. The inventory_hostname will be read from the targeted managed host which in this case is localhost. The following example shows the playbook execution. Note that the output contains the add_host line and the variable expansion to localhost.

 

Task execution concurrency with delegation
Delegated tasks run for each managed host targeted. But Ansible tasks can run on multiple managed hosts in parallel. This can create issues with race conditions on the delegated host. This is particularly likely when using conditionals in the task, or when multiple concurrent tasks are run in parallel. This can also create a “thundering herd” problem where too many connections are being opened at once on the delegated host. SSH servers have a MaxStartups configuration option that can limit the number of concurrent connections allowed.

 

Delegated Facts
Any facts gathered by a delegated task are assigned by default to the delegate_to host, instead of the host which actually produced the facts. The following example shows a task file that will loop through a list of inventory servers to gather facts.

When the previous playbook is run, the output shows the gathered facts of
managedhost2.example.com as the task delegated to the host from the servers2 inventory group instead of managedhost1.example.com.

The delegate_facts directive can be set to True to assign the gathered facts from the task to the delegated host instead of the current host.

On running the above playbook, the output now shows the facts gathered from managedhost1.example.com instead of the facts from the current managed host.

 

Example.

In this exercise, you will configure the delegation of tasks in an Ansible playbook. The playbook will configure workstation as a proxy server and servera as an Apache web server. During the deployment of the website on servera, you will delegate the task of stopping the traffic coming to servera to workstation proxy server and later after deployment you will start the
traffic coming to servera by delegating task to workstation.

Create an inventory file named hosts under ~/ansible/delegation.
The inventory file should have two groups defined: webservers and proxyservers. The managedhost1.example.com host should be part of the webservers group and managedhost2.example.com should be part of the proxyservers group.

Create managedhost1.example.com-httpd.conf.j2, template file that configures the virtual host in the ~/delegation/templates directory. Later you will use an Ansible variable (inventory_hostname) to list the source of this file.

Create the ~/delegation/managedhost2.example.comhttpd.conf.j2 template file for configuring reverse proxy in the ~/delegation/templates directory.

Create a template file, named index.html.j2, for the website to be hosted on managedhost1.example.com under the templates directory. The file should contain the following content:

Create a site.yml playbook under the lab project directory, ~/ansible/delegation/. Define a play inside the playbook that will execute the tasks on all hosts. Use miro for remote connection and use privilege escalation to root using sudo. Define a task to install the httpd package and start and enable the httpd service on all hosts.  Define a task to enable the firewall to allow web traffic on managedhost1.example.com. Define a task in the site.yml playbook to copy the managedhost2.example.com-httpd.conf.j2 and managedhost1.example.com-httpd.conf.j2 template files to the /etc/httpd/conf.d/myconfig.conf directory on their respective hosts. After copying the configuration file, use the notify: keyword to invoke the restart httpd handler defined in the next step. Define a handler to restart the httpd service when it is invoked.

Define another play that will have tasks to deploy a website that needs to be run on managedhost1.example.com of the webservers inventory group.
In the site.yml playbook, define a task to stop the incoming web traffic
to managedhost1.example.com by stopping the proxy server running on
managedhost2.example.com. Since the hosts keyword of the play points to webservers host group, use delegate_to keyword to delegate this task to managedhost2.example.com of the proxyservers host group. Define a task to copy the index.html.j2 template present under templates directory to /var/www/html/index.html on managedhost1.example.com, part of the webservers group. Change the owner and group to apache and file permission to 0644. Add another task to the site.yml playbook that starts the proxy server by delegating the task to managedhost2.example.com.

Check the syntax of the site.yml playbook. Resolve any syntax errors you find.