Course outline

Ansible / Advanced: orchestration, platforms, tests, and diagnosis / Lesson 14

Trace delegation and host-context ownership

Separate the play host, execution destination, variable context, fact ownership, shared-resource concurrency, and run_once scope.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain which actions can use delegation and where variables come from.
  • Distinguish delegated execution from delegated fact ownership.
  • Plan one-time and shared-target work under batching and strategy rules.

The play host and execution host can differ

delegate_to changes where a delegatable action executes, while the original inventory host remains an important part of task identity and reporting. Connection, interpreter, privilege, and variable evaluation can follow the documented delegated context, so do not assume every reference means the same host. Use explicit hostvars access when the design requires data from a named original or delegated host, and validate availability. Some controller-side actions such as debug do not use a target connection and cannot be meaningfully delegated. Check the action’s connection capability instead of adding delegate_to mechanically to every task.

Execution location does not automatically decide fact ownership

A delegated fact-gathering task and the host receiving those facts are related but distinct. delegate_facts controls the documented assignment behavior when appropriate. Without understanding that distinction, one host can appear to contain another host’s observations, leading to wrong templates or conditions. Do not use fact delegation as a hidden communication bus between roles. Define ownership, freshness, and consumers explicitly, especially when a later play accesses hostvars. Cached facts and failed or skipped gathering need separate handling. No facts are gathered in this course; all host data in examples is synthetic input.

Delegating many hosts does not serialize them

Several play hosts can execute delegated work against the same helper concurrently. A shared file, API object, or load-balancer configuration can therefore suffer races even if every original host is distinct. Delegation changes location, not the global concurrency policy. Use a deliberate aggregation or synchronization design, such as a separate orchestration play or a justified throttle, and preserve per-host intent. Do not assume a module’s individual idempotence makes concurrent writers safe. The correct design must define ownership of the shared resource and the order in which updates and verification occur.

run_once follows the execution context, including batches

run_once can run a task once for the relevant host batch under supported strategy behavior, so serial batches can cause more than one execution across a play. It is not a universal once-per-project lock. A task may also use the selected host’s variables in ways that differ from an aggregate of all hosts. Free strategy and strategy-plugin details require their own documentation review. Define whether the operation is once per batch, once per play population, or once for an external durable resource, then select a design that actually satisfies that contract.

yaml Non-runnable delegated batch illustration
- name: Illustrate explicit helper execution
  hosts: lab_nodes
  gather_facts: false
  serial: 2
  tasks:
    - name: Describe one batch through a fictional helper
      ansible.builtin.command:
        argv:
          - /usr/bin/printf
          - '%s\n'
          - Synthetic helper message
      delegate_to: lab_helper
      run_once: true
      changed_when: false

This fictional helper and command are never contacted or executed. The example shows why run_once with serial needs a batch-aware interpretation; YAML parsing cannot prove its runtime count.

Aggregate host intent without mutating shared state

An offline mapping can show how distinct host inputs become one reviewed aggregate. Sorting stabilizes presentation and exposes missing or duplicate identities without writing a shared file. This is not Ansible delegation, hostvars evaluation, or a concurrency lock. In an authorized disposable test, verify the real strategy, batch count, execution destination, variable provenance, and shared-resource postcondition. Keep output and credentials scoped to the actual execution host, and test failure of the delegate separately from failure of an original host. A successful original host status alone cannot establish that a shared action was applied exactly once.

python Build a deterministic synthetic aggregate offline
hosts = {'lab_b': {'port': 8202}, 'lab_a': {'port': 8201}}
aggregate = [f"{name}:{hosts[name]['port']}" for name in sorted(hosts)]
print(','.join(aggregate))
print('shared_write=not-performed')
Expected output
lab_a:8201,lab_b:8202
shared_write=not-performed

The model formats declared dictionaries only. It writes no configuration, accesses no hostvars runtime, and provides no mutual-exclusion guarantee.

Common errors and fixes

delegate_to is assumed to serialize all callers

Why it happens
Execution location was confused with synchronization.
How to fix it
Define shared-resource ownership and concurrency explicitly.
Prevent it next time
Test multiple original hosts targeting one delegate.

run_once is called once for the entire deployment

Why it happens
Batch and strategy context were omitted.
How to fix it
State and test the required once-scope.
Prevent it next time
Include serial and strategy cases in the plan.

Delegated facts are attributed to the wrong host

Why it happens
Execution and assignment ownership were conflated.
How to fix it
Review delegate_facts and explicit data consumers.
Prevent it next time
Record fact provenance with host identity.

Check your understanding

1. Why can a delegated task race when the original hosts are different?

Show worked solution

They may all act on the same delegate resource concurrently. Distinct original identities do not create separate files or API objects at the destination. Use explicit shared-resource ownership and a tested orchestration policy.

2. What must once mean before selecting run_once?

Show worked solution

Define whether it means once per batch, per play population, or per durable external action. Verify the selected strategy and serial behavior against that meaning; use another explicit coordination boundary when the task keyword does not provide the required guarantee.

Key takeaways

  • Track play identity, execution location, and data ownership separately.
  • Treat shared-target and once-only behavior as explicit protocols.

Official references