Chapter 1: Foundations
Topic 02

What Ansible Is

ConceptTooling

Ansible is Red Hat's configuration-management and automation tool. It configures servers by connecting to them over plain SSH and pushing changes — with no agent to install, no daemon polling in the background, and crucially no central state file. You install Ansible on one control node, list your servers in an inventory, and describe the desired state in YAML; Ansible logs into each machine, checks what it looks like right now, and makes only the changes needed to match.

Three design choices define it, and they are best understood together: agentless, push-based, and stateless. Each one is a deliberate departure from the configuration-management tools that came before, and each one shapes how you work with Ansible every day. The rest of this topic takes them in turn.

Three design choices
Agentless
No agent on the node — just SSH and the Python it already has.
Push
You trigger the run from the control node, so you decide when change happens.
Stateless
No state file — the managed node is the record, re-read on every run.

Agentless over SSH

There is nothing to install on a managed node beyond two things a Linux server already has: an SSH daemon and Python. Ansible connects over SSH, copies small module programs to the machine, runs them, collects the results, and removes them. Onboarding a new server is therefore adding a line to an inventory file — not bootstrapping an agent, opening a new port, or managing yet another piece of software that itself needs patching.

That single property removes a whole category of problems. There is no agent fleet to upgrade, no agent-to-server version skew, and no chicken-and-egg of "how do I configure the thing that configures the machine?" If a host answers SSH and has Python, it is already manageable, which is why Ansible spread to teams that never wanted to run a configuration-management server at all.

Push, Not Pull

You trigger a run from the control node and Ansible pushes the changes out to the fleet. This is the opposite of the agent model, where each machine pulls its configuration from a central server on a timer. Push means you decide exactly when change happens — now, in this pipeline step, in this order — which is precisely what an orchestrated deploy needs, because step two must be able to wait for step one. The trade is that nothing enforces state between your runs; if you want continuous enforcement, you schedule the runs yourself.

No State File

This is the deepest difference from a provisioning tool like Terraform, and the idea the whole course leans on: Ansible stores no record of what it has done, because the managed node is the record. Every run gathers the machine's current facts and reconciles them against your declared state, live. There is no state file to lock, corrupt, drift from reality, or accidentally check into git.

The consequence is freeing and limiting at once. Freeing, because there is nothing to manage besides your playbooks and inventory, and two people can run the same playbook without coordinating over a shared state lock. Limiting, because Ansible cannot cleanly destroy what it no longer manages — remove a task and Ansible simply stops touching that thing; it has no memory that it once created it. Stateful cleanup is the provisioning layer's job, and the next two topics draw that line.

Idempotency Lives in the Modules

Ansible's idempotency is not a global engine feature — it is enforced module by module. The apt module checks whether a package is already installed before installing it; the service module checks whether a service is already running; the copy module compares checksums before overwriting. Each reports changed only when it actually changed something, which is what lets you run a playbook repeatedly and watch it settle to all-green after the first pass. Choosing idempotent modules over raw commands is the single most important habit in writing good Ansible, and a later chapter is devoted to it.

YAML All the Way Down

Inventory, variables, and playbooks are YAML — a format readable by people who do not write Python. The playbook below installs and starts nginx on every host in the web group; it reads almost like English, and that low floor is why Ansible reached network engineers and operators who never adopted a Ruby or Puppet DSL. The two tasks are idempotent: run this twice and the second run reports no change, because nginx is already present and running.

A minimal playbook — install and start nginx on the web group
- hosts: web
  become: true            # escalate to root for package + service management
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present

    - name: Ensure nginx is running and enabled at boot
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

Nothing in that file says how to install a package or how to start a service — it declares the state it wants, and the modules figure out the rest against whatever the machine currently looks like. That is configuration management in miniature, and every chapter after this one is an expansion of it.

Ansible vs Terraform

Ansible — configures existing machines with no state file, re-checking reality on every run. It answers "how should this server be set up?" Reach for it to install, template, and manage what runs on a host you already have.

Terraform — provisions infrastructure and keeps a state file that maps config to real resources, so it knows what to create and destroy. It answers "what infrastructure should exist?" Run Terraform to build the server, then Ansible to configure it — the two are partners across a layer boundary, covered in depth two topics on.

Common Mistakes
  • Hunting for the Ansible "state file" to diff against before a run — there isn't one; the live machine is the state, and the equivalent of a diff is running in check mode against gathered facts.
  • Assuming you must install an Ansible agent on every server, as Puppet and Chef require — there is no agent, and treating SSH-plus-Python hosts as if they need one wastes effort and adds attack surface.
  • Trying to manage cloud-infrastructure lifecycle with Ansible and expecting Terraform-style cleanup — with no state, Ansible cannot know what to delete when you remove a task; it just stops managing it.
  • Reading "no state file" as "no idempotency" — idempotency is enforced per module against live state, which is stronger than a stored snapshot that can silently disagree with reality.
  • Running playbooks ad-hoc from random laptops because "push is convenient," so changes happen unlogged and out of order — push is a feature only when you control where it is pushed from.
Best Practices
  • Keep your inventory and playbooks in git — together they are the entire system of record, since there is no state file to also manage and protect.
  • Prefer idempotent modules over command and shell, so every run is safe to repeat and the changed count tells the truth.
  • Use Ansible for configuration and orchestration, and pair it with a provisioning tool for the infrastructure lifecycle, rather than stretching one tool across both layers.
  • Run playbooks from a controlled place — a CI runner or a controller — so "push" happens deliberately, in order, and is logged and auditable.
  • Lean on the agentless model when onboarding hosts: confirm SSH and Python reachability first, and you have done nearly all the setup Ansible needs.
Comparable tools Puppet · Chef agent-based pull configuration management SaltStack push or pull over a message bus Terraform · Pulumi provisioning with state, a different layer Fabric · SSH loops the unstructured push Ansible formalizes

Knowledge Check

What does "agentless" let you skip when bringing a new Linux server under Ansible management?

  • Installing any node-side daemon beyond the SSH and Python the box already has
  • Writing the inventory line that names the new host and records its connection address for Ansible
  • Having a working route so the control node can reach the host on port 22
  • Authenticating over SSH with a key or password before tasks run

Ansible has no state file. What plays the role that a state file plays in a provisioning tool?

  • The managed node itself — each run gathers its facts and reconciles them against the declared state
  • A hidden local cache sitting on the control node that lists every resource Ansible has ever created
  • A central database on the control node that continuously tracks the last-known state of every managed host
  • A lock file kept in the inventory directory that serializes concurrent runs across the team

Where does Ansible's idempotency actually come from?

  • Each module checks current state before acting and reports changed only when it changed something
  • A single global engine setting that skips any task whose name it has already seen run before
  • A central state file that diffs the previous run against this one to decide which tasks to skip
  • The SSH transport layer quietly deduplicating any commands it has already sent to the host

Why is push-based execution a good fit for an orchestrated rolling deploy, where a pull model is awkward?

  • You control exactly when and in what order change reaches each host, so one step can wait for the previous one
  • Push runs are always faster than pull runs no matter how large the target fleet grows or how busy each host is
  • Pull-based agents cannot open an SSH connection of any kind, so they can never reach a host to deploy at all
  • Push enforces the declared state continuously between runs on its own without any scheduling on your part

You got correct