Open-Source Wikis

/

Ansible

/

Ansible-core overview

/

Architecture

ansible/ansible

Architecture

Ansible-core is a controller process that reads YAML playbooks, expands them into a sequence of tasks against a host inventory, and ships those tasks to remote machines for execution. The controller is a single Python process that forks worker processes for parallel host execution; the remote side never runs an Ansible daemon. Instead, each task is delivered as a self-contained Python "AnsiBallZ" archive, executed once, and discarded.

This page traces the dispatch path from a bin/ansible-playbook invocation through to a module running on a remote target.

End-to-end dispatch

graph TD
    BIN[bin/ansible-playbook] -->|symlink| CLI[lib/ansible/cli/playbook.py PlaybookCLI]
    CLI -->|parses YAML| PB[Playbook lib/ansible/playbook]
    CLI --> INV[InventoryManager lib/ansible/inventory/manager.py]
    CLI --> VM[VariableManager lib/ansible/vars/manager.py]
    CLI --> EX[PlaybookExecutor lib/ansible/executor/playbook_executor.py]
    EX --> TQM[TaskQueueManager lib/ansible/executor/task_queue_manager.py]
    TQM --> ITER[PlayIterator lib/ansible/executor/play_iterator.py]
    TQM --> STRAT[Strategy plugin lib/ansible/plugins/strategy]
    STRAT --> WP[WorkerProcess lib/ansible/executor/process/worker.py]
    WP --> TE[TaskExecutor lib/ansible/executor/task_executor.py]
    TE --> AP[Action plugin lib/ansible/plugins/action]
    AP --> AZ[AnsiBallZ builder lib/ansible/executor/module_common.py]
    AZ --> CONN[Connection plugin lib/ansible/plugins/connection]
    CONN --> REMOTE[Remote target Python interpreter]

Layer-by-layer

1. CLI layer

Each end-user binary in bin/ is a symlink to a Python module in lib/ansible/cli/ (e.g. bin/ansible-playbook → lib/ansible/cli/playbook.py). Every CLI subclasses CLI from lib/ansible/cli/__init__.py, which provides a common init_parser()/post_process_args()/run() lifecycle, locale setup, vault password handling, and option groups built from lib/ansible/cli/arguments/option_helpers.py.

The CLI parses arguments, builds a DataLoader, an InventoryManager, a VariableManager, and instantiates the right executor for its job. See Apps for per-CLI breakdowns.

2. Parsing and the playbook object model

YAML is loaded by DataLoader (lib/ansible/parsing/dataloader.py), which wraps the project's custom YAML loader (lib/ansible/parsing/yaml/loader.py) and Vault-decrypts files transparently. Parsed YAML becomes a tree of objects in lib/ansible/playbook/:

  • Playbook (lib/ansible/playbook/__init__.py) — a list of plays plus optional import_playbook includes.
  • Play (lib/ansible/playbook/play.py) — hosts, vars, tasks, handlers, roles.
  • Block (lib/ansible/playbook/block.py) — a group of tasks with shared error handling (rescue, always).
  • Task (lib/ansible/playbook/task.py) — the leaf node: an action, args, conditionals, loops, tags.
  • Role (lib/ansible/playbook/role/__init__.py) — packaged set of tasks/handlers/vars/files.
  • Handler (lib/ansible/playbook/handler.py) — a task triggered by notify.

The base class Base (lib/ansible/playbook/base.py) gives every object its attribute system, validation, and templating support. See Primitives for details.

3. Inventory and variables

InventoryManager reads inventory sources by delegating to the configured inventory plugins (lib/ansible/plugins/inventory/). The result is an InventoryData object holding Host and Group instances. VariableManager (lib/ansible/vars/manager.py) merges variables from many sources at the precedence ordering documented in lib/ansible/vars/manager.py:get_vars.

4. Execution engine

PlaybookExecutor walks the list of plays, applies serial/percentage batching, and constructs a TaskQueueManager (TQM) per run. The TQM:

  • Builds a PlayIterator (lib/ansible/executor/play_iterator.py) — a deterministic state machine over tasks, blocks, includes, and rescue/always paths, on a per-host basis.
  • Spawns a pool of WorkerProcess instances (lib/ansible/executor/process/worker.py) that pull tasks from a queue and run them through TaskExecutor.
  • Loads a strategy plugin (linear, free, host_pinned, debug) that decides which (host, task) pairs to dispatch next.
  • Routes results back through callback plugins for display, logs, and external systems.

See Systems → Executor for a deeper view of the TQM/strategy/worker handshake.

5. Action and connection plugins

TaskExecutor.run() resolves the task's action, loads the matching action plugin from lib/ansible/plugins/action/, and calls its run(). The action plugin is the controller-side half of a module: it may template arguments, copy supporting files, transform the request, or short-circuit altogether (e.g., debug, set_fact).

Most action plugins delegate to the default ActionBase._execute_module() flow, which:

  1. Builds an AnsiBallZ-wrapped Python script via lib/ansible/executor/module_common.py.
  2. Picks a connection plugin (ssh, local, winrm, psrp) via lib/ansible/plugins/loader.py:connection_loader.
  3. Optionally wraps the command with a become plugin (sudo, su, runas).
  4. Transfers the wrapper to the remote host, executes it, and parses the JSON result.

See Systems → Module execution and AnsiBallZ.

6. Plugin loader and collection loader

lib/ansible/plugins/loader.py is the glue. PluginLoader instances (one per plugin type) discover plugin classes by name across the search paths: built-in (lib/ansible/plugins/<type>/), installed collections (~/.ansible/collections/, /usr/share/ansible/collections/), and user-configured directories. Resolution honors fully-qualified collection names (namespace.collection.plugin_name) and the legacy short-name aliases listed in lib/ansible/config/ansible_builtin_runtime.yml.

The collection loader (lib/ansible/utils/collection_loader/) is a Python MetaPathFinder that lets ordinary import statements resolve ansible_collections.<ns>.<col>.plugins.<type>.<name> paths from disk-installed collections.

7. Templating

Variable interpolation, conditionals, and {{ ... }} expansion go through the project's hardened Jinja2 fork in lib/ansible/_internal/_templating/. TemplateEngine (_engine.py) is the entry point; it binds Ansible's filter, test, and lookup plugins into a Jinja environment and tracks "trusted" templates with the TrustedAsTemplate datatag from lib/ansible/_internal/_datatag/_tags.py. Untrusted strings are not evaluated — a defense against template injection from facts and arbitrary user input.

Trust, datatags, and "the rewrite"

In the 2.18–2.19 line, ansible-core moved from string-based templating to a tag-based model: every YAML scalar is wrapped in an AnsibleTaggedObject (lib/ansible/module_utils/_internal/_datatag.py) that carries metadata such as Origin (where it came from in source) and TrustedAsTemplate (whether templating is allowed). The result is the same string at runtime, but with provenance and policy attached. This is why so much of the templating and parsing code now goes through _internal/_datatag/.

Process model

graph LR
    Controller[Controller process - PlaybookExecutor + TQM] -->|fork| W1[Worker 1 - TaskExecutor]
    Controller -->|fork| W2[Worker 2 - TaskExecutor]
    Controller -->|fork| W3[Worker N]
    W1 -->|SSH/WinRM| H1[(Host 1)]
    W2 -->|SSH/WinRM| H2[(Host 2)]
    W3 -->|SSH/WinRM| H3[(Host N)]
    W1 -->|JSON results queue| Controller
    W2 -->|JSON results queue| Controller
    W3 -->|JSON results queue| Controller

The controller process never executes module code itself; that work happens in the worker children. The default fork count (5) is set in lib/ansible/constants.py and overridden by --forks or ansible.cfg. Workers communicate results back through a multiprocessing.Queue that the TQM drains in its main loop.

Languages in the repository

ansible-core is overwhelmingly Python, but ships small amounts of:

  • PowerShell (.ps1, .psm1) under lib/ansible/executor/powershell/, lib/ansible/module_utils/powershell/, and the Windows modules — the equivalent of AnsiBallZ for Windows targets.
  • C# (.cs) under lib/ansible/module_utils/csharp/Ansible.Basic.cs and friends compiled at runtime on Windows hosts to provide the AnsibleModule facility for C# modules.

Total counts: roughly 583 Python source files in lib/, 1213 Python test files, 24 PowerShell, 6 C#, and 2042 YAML files (including changelog fragments and integration-test data). See By the numbers for current statistics.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – Ansible wiki | Factory