Skip to content

Lesson 1 of 9 · 20m

The Agentless Model: SSH, Python, and No Daemons

Why Ansible pushes Python over SSH instead of running agents, what that buys you operationally, and the failure modes it quietly avoids — plus the ones it doesn't.

In 2013 I inherited a fleet managed by an agent-based config tool. The agents crashed, the agent versions drifted from the server version, and half my "fleet is converged" reports were stale because nodes hadn't checked in. Then someone showed me Ansible: ssh in, run a thing, leave nothing behind. It felt like cheating. It mostly isn't — but you need to understand the mechanism to know where it bends.

What's actually on the wire

When you run a task, Ansible does this on the control node:

  1. Reads the inventory to resolve hostnames, groups, and variables.
  2. Opens an SSH connection (control node → managed node) as your user.
  3. Ships a small self-contained Python program (the module) across the wire — usually via sftp/scp into ~/.ansible/tmp/, or piped through stdin when pipelining = True.
  4. Executes it (often wrapped in sudo/become), collects JSON output, deletes the temp file.

Ansible control node pushing modules over SSH

Nothing persists on the managed node. No daemon to patch, no certificate infrastructure beyond the SSH keys you already have, no "agent didn't check in" ambiguity — if SSH works, Ansible works.

The trade you're making

Push over SSH has real costs: every task is a connection (mitigated by persistent connections and pipelining), and orchestrating 5,000 hosts is bounded by your control node's forks, not by agents running in parallel. Pull-mode exists (ansible-pull from a git checkout) for hosts that can't be reached inbound — autoscaled instances, laptops.

Your first contact

# ping isn't ICMP — it exercises SSH + Python on the target
ansible all -i inventory.ini -m ping
ansible webservers -i inventory.ini -a "uptime"      # ad-hoc command
ansible db01 -i inventory.ini -m setup | head -40    # dump gathered facts

Requirements on the managed node: Python 3 (Ansible ships pure-Python modules) and SSH. Windows targets use WinRM (ansible_connection=winrm), network gear uses network_cli/netconf — same mental model, different transport.

Become: the privilege story

# ansible.cfg
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False

become on a task, play, or config is Ansible's sudo. It isn't optional plumbing — declaring privilege per-task is what lets you run audits as an unprivileged user and escalate only where needed.

Field notes

  • -m ping returning pong means SSH and a usable Python interpreter — it's the first thing to check on a new host image.
  • ansible_ssh_private_key_file, ansible_user, ansible_port are inventory vars, not CLI flags — put connection metadata where the host lives.
  • Agentless doesn't mean stateless for you: Ansible knows nothing about what ran last week. Drift detection is your git log plus check-mode runs, not a central server.