Part 16 — How Ansible Actually Executes¶
Chapter status: outline
This chapter is scoped but not yet written in full prose. The sections below define what each part will cover.
This chapter traces one command, ansible all -m ping, through every internal step, down to the temporary files created and deleted on the managed node.
Why This Exists¶
- "It just works" is unsatisfying for engineers who need to debug it when it doesn't work — this chapter removes the black box.
Problem Statement¶
- What actually has to happen, mechanically, for a Python module's code to run on a remote machine you only have SSH access to, and for its result to come back as structured data your playbook can act on.
The Execution Sequence¶
sequenceDiagram
participant U as User
participant CLI as ansible CLI
participant INV as Inventory Parser
participant VAR as Variable Loader
participant CONN as SSH Connection Plugin
participant MN as Managed Node
U->>CLI: ansible all -m ping
CLI->>INV: Parse inventory, resolve "all"
CLI->>VAR: Load variables for target hosts
CLI->>CONN: Open SSH connection (ControlPersist if enabled)
CONN->>MN: Copy ping module (or pipe via pipelining)
MN->>MN: Detect/verify Python interpreter
MN->>MN: Execute module as temporary Python script
MN->>CONN: Return JSON result on stdout
CONN->>MN: Remove temporary files
CONN->>CLI: Return JSON result
CLI->>U: Display formatted output (via callback plugin)
Step-by-Step Explanation¶
- CLI:
ansibleparses arguments, loadsansible.cfg/env/CLI-flag configuration. - Inventory parsing: resolves the
allpattern against the active inventory source(s). - Variable loading: Variable Manager resolves the full precedence stack per host (Volume 2, Part 9).
- SSH connection: the SSH connection plugin opens a connection, reusing a multiplexed
ControlPersistsocket if one exists. - Copy module: without pipelining, the module's Python source is copied to a temporary directory on the managed node (
remote_tmp, default under~/.ansible/tmp/). - Execute temporary Python: the managed node's Python interpreter executes the module as a standalone script.
- Return JSON: the module prints a single JSON object to stdout as its result contract.
- Delete temp files: the connection plugin cleans up the temporary directory after retrieving the result (unless
ANSIBLE_KEEP_REMOTE_FILESis set for debugging). - Display output: the result flows back to the control node and is rendered by the active callback plugin (default
defaultcallback, or others likeyaml,json,minimal).
Internal Architecture — Supporting Mechanisms¶
- Temporary files: why Ansible stages module code as real files instead of purely piping bytecode — historical simplicity and debuggability, with pipelining as the optimized alternative.
- JSON communication: the module contract is "print exactly one JSON object to stdout" — this is what makes modules language-agnostic in principle, even though nearly all official modules are Python.
- Python interpreter detection:
interpreter_python = autoprobes the managed node for an available Python 3 binary (full mechanics in Part 17). - Connection plugins: SSH is the default;
local,winrm,docker,paramikoare alternatives with the same result contract. - ControlPersist: SSH multiplexing that keeps a connection open across multiple tasks in the same run, avoiding repeated TCP/SSH handshake cost.
- Pipelining: skips the "copy module file, then execute it" step by piping the module directly into a remote Python process's stdin, reducing the number of SSH operations per task.
- Become: how
become: trueinserts a privilege-escalation wrapper (sudo, etc.) around the module execution step. - Fact gathering: the implicit
setupmodule run that happens automatically at the start of most plays unlessgather_facts: false. - Module transfer: the mechanics differ meaningfully between pipelining-enabled and disabled connections — worth a direct side-by-side.
- Cleanup: temp file removal, and how
-vvvv/ANSIBLE_KEEP_REMOTE_FILES=1let you inspect what was actually sent and run.
Production Best Practices¶
- Enabling pipelining (with its
requirettysudoers caveat) as one of the highest-value performance changes available (previewed here, quantified in Volume 6). - Using
-vvvvbefore assuming a "no such module" or connection error is mysterious — it shows the exact SSH commands and temp paths involved.
Common Mistakes¶
- Debugging a failing task without ever running
-vvvv, missing the actual SSH/Python error underneath Ansible's summarized failure message. - Not realizing fact gathering itself is a real remote execution step with real cost, run before your first visible task.
Performance Considerations¶
- Every task, by default, is a full connect-transfer-execute-cleanup cycle unless ControlPersist and pipelining reduce that overhead — the cost compounds across hosts and tasks (fully quantified in Volume 6).
Security Considerations¶
- Temporary files on the managed node briefly contain module code and, depending on the module, potentially sensitive arguments —
remote_tmppermissions andno_logon sensitive tasks matter here.
Interview Questions¶
- "Walk through what happens internally when you run
ansible all -m ping." - "What is pipelining and how does it change the execution flow?"
- "Where are temporary module files stored on the managed node, and how would you inspect them for debugging?"
Hands-On Lab¶
- Run
ANSIBLE_KEEP_REMOTE_FILES=1 ansible all -m ping -vvvvagainst a test host, then SSH in manually and inspect the leftover files under~/.ansible/tmp/before cleaning them up.
Summary¶
ansible all -m pingis not one atomic action — it's inventory resolution, variable loading, a connection, a file transfer (or pipe), a remote Python execution, a JSON return, and cleanup, all visible with enough verbosity.
Next¶
Continue to Part 17 — Why Python?.