Ansible Fundamentals
Why configuration management exists, how Ansible's agentless architecture works over SSH, and how inventories and ad-hoc commands let you operate on fleets of servers without writing a playbook first.
Learning objectives
- Explain the problem configuration management solves compared to manual server setup
- Describe Ansible's agentless, SSH-based architecture
- Write a static inventory file grouping hosts
- Run ad-hoc commands against inventory groups
Concept Overview Before configuration management tools existed, teams configured servers by hand -- SSH in, install packages, edit a config file, restart a service, repeat on the next box. This works for one server. It falls apart at ten, and it is actively dangerous at a hundred, because every manual session is a chance to do something slightly differently on one machine than another. That inconsistency is called configuration drift: server A has a patch that server B doesn't, a config file edited at 2am during an incident never gets backported to the other nodes, and six months later nobody can explain why one node in the cluster behaves differently.
Configuration management tools solve this by letting you describe the desired state of a server in a file, then apply that file to as many servers as you want. Run it once, run it a hundred times, run it on one server or on a thousand -- the result converges to the same described state. This property is called idempotency: running the same configuration twice produces the same result as running it once, with no side effects from repetition. If a package is already installed, Ansible does not reinstall it. If a config file already matches what you specified, Ansible does not touch it and reports no change. This is fundamentally different from a shell script, which blindly reruns every command regardless of current state and can fail or behave unexpectedly on a second run.
Internal Working
- Ansible evaluates the current state of a target before acting, and only performs work when the current state differs from the desired state.
- Every module ships a 'changed' or 'ok' result per task per host, which is how Ansible reports idempotency in its output.
- Because configuration is declared in version-controlled text files, the entire fleet's configuration history lives in git -- you can diff it, review it, and roll it back.
- This declarative approach also serves as living documentation: reading the playbook tells you exactly what a server looks like, without needing to SSH in and inspect it.
Why It Matters Configuration management turns server setup from a one-off manual ritual into a repeatable, reviewable, testable process -- the same discipline that version control and code review brought to application code, applied to infrastructure.
Concept Overview Many configuration management tools -- Puppet, Chef, SaltStack in its traditional mode -- require you to install a persistent agent daemon on every managed server. That agent has to be installed, kept patched, and kept running, and it typically phones home to a central master server on a schedule. Ansible takes a different approach: it is agentless. There is no daemon running on the servers it manages. Instead, Ansible connects to each target host over standard SSH, copies a small Python script (the 'module' that implements whatever task you asked for, such as installing a package) to a temporary location on the target, executes it, captures the result, and then removes it.
This has real operational consequences. You don't need to pre-provision every new server with an agent before Ansible can manage it -- if a server has SSH access and Python installed, Ansible can manage it today. There's no extra attack surface from a long-running daemon, and no agent version-compatibility matrix to track across your fleet. The control node -- the machine you run Ansible from -- is the only place Ansible itself needs to be installed.
Internal Working
- Ansible uses SSH (or WinRM for Windows targets) as its transport; it reuses your existing SSH keys and configuration, including ProxyJump/bastion setups.
- For each task, Ansible generates a small Python program embedding the module logic and the parameters you gave it, pushes it over SFTP/SCP, executes it remotely, parses the JSON result it prints, then cleans up.
- Connections can be pipelined and parallelized -- Ansible runs tasks across many hosts concurrently (controlled by the
forkssetting), rather than one host at a time. - Because everything travels over SSH, the same authentication, logging, and bastion-host practices you already use for manual access apply directly to Ansible's access.
Why It Matters Agentless design lowers the barrier to adopting Ansible -- there's no fleet-wide agent rollout project before you can start -- and it keeps the attack surface and operational overhead on managed servers close to zero.
Concept Overview
An inventory file tells Ansible which hosts exist and how they're grouped. In its simplest form it's an INI-style text file with hostnames or IP addresses under group headers in square brackets. A host can belong to multiple groups -- for example, a server can be in both [webservers] and [production] -- and you can target either group independently when you run a playbook or command. Groups can also be nested using [group:children], so [production:children] could pull in both webservers and databases.
Inventories aren't limited to static text files. For cloud environments where servers come and go, Ansible supports dynamic inventory -- a script or plugin that queries a cloud provider's API (AWS, Azure, GCP) at run time and builds the host list automatically, so you never hand-maintain IP addresses that change on every deploy.
Internal Working
- The default inventory location is
/etc/ansible/hosts, but in practice almost every team keeps aninventory.ini(orinventory/directory) inside their project repo and points Ansible at it with-i inventory.inior anansible.cfgsetting. - Variables can be attached directly in the inventory (
web1 ansible_host=10.0.0.5 ansible_user=deploy) or, more commonly, in separategroup_vars/andhost_vars/directories that Ansible loads automatically based on group and host names. - The special group
allalways exists and matches every host in the inventory. ansible-inventory --list -i inventory.iniprints the fully resolved inventory, including group membership and variables, which is the fastest way to debug 'why didn't my playbook target that host'.
Why It Matters The inventory is the single source of truth for 'what servers do we have and how are they grouped' -- every playbook and ad-hoc command targets a pattern against this file, so getting its structure right is the foundation everything else builds on.
💻 Code example
[webservers] web1.example.com web2.example.com ansible_host=10.0.1.12 [dbservers] db1.example.com [production:children] webservers dbservers [production:vars] env=production
Concept Overview
Not every task needs a full playbook. Ansible's ad-hoc command mode lets you run a single module against a group of hosts directly from the command line, which is ideal for quick operational questions: 'is this service running everywhere?', 'what's the disk usage on all web servers?', 'reboot every host in the staging group'. The basic form is ansible <pattern> -m <module> -a '<arguments>', where the pattern selects hosts from your inventory (a group name, all, a wildcard, or a comma-separated list) and the module is the specific unit of work to perform.
Ad-hoc commands use exactly the same modules that playbooks use -- there's no separate 'ad-hoc' module set -- so what you learn running ansible webservers -m ping transfers directly to writing playbook tasks later. The most common starting point is the ping module, which doesn't send an ICMP ping at all; it verifies that Ansible can connect over SSH and that Python is available on the target, which is the standard first health check after setting up a new inventory.
Internal Working
ansible webservers -m pingchecks connectivity;ansible all -m setupgathers and prints full facts (OS, memory, network interfaces) about every host.ansible webservers -a "uptime"with no-mflag defaults to thecommandmodule, running a raw shell command -- useful for diagnostics, but not idempotent, so it's a poor substitute for playbook tasks in real automation.ansible webservers -b -m service -a "name=nginx state=restarted"demonstrates-b(become, i.e. use sudo) combined with a real module for a privileged one-off action.- Ad-hoc commands don't get saved or version-controlled by default, which is exactly why they're for quick exploration and incident response, not for anything you want to be repeatable or reviewable -- that's what playbooks are for.
Why It Matters Ad-hoc commands are how most people's first successful Ansible run happens, and they remain the fastest tool for one-off fleet-wide questions and actions throughout day-to-day operations.
💻 Code example
# Check connectivity and Python availability across a group ansible webservers -m ping # Gather facts (OS, memory, interfaces) from every host ansible all -m setup # Run a raw shell command (quick diagnostics only, not idempotent) ansible webservers -a "uptime" # Use a real module with privilege escalation ansible webservers -b -m service -a "name=nginx state=restarted"
Want a visual for this concept?
Generate a diagram tailored to “Ansible Fundamentals” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →