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.
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.
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.
- 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.
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')
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.