🤖

Level 18 of 24

Ansible

Inventories, playbooks, and automating many servers instead of SSHing into each one.

Manually SSHing into ten servers to run the same command ten times doesn't scale, and is a real source of "I forgot to also do it on server 7" bugs. Ansible automates exactly this: describing desired state once, and applying it consistently across many machines.

Prerequisites

  • • Level 9: SSH & Remote Administration
  • • Level 13: Bash Scripting

By the end of this level, you can

  • ✓ Explain inventories, modules, and playbooks
  • ✓ Run ad-hoc Ansible commands against multiple hosts
  • ✓ Write a basic playbook and explain idempotency

Inventory, ad-hoc commands, and playbooks

How Ansible manages many servers without installing any agent on them. · 11 min

Ansible is agentless — it connects to managed hosts over plain SSH (using the key-based auth from Level 9) and runs Python-based modules remotely, with nothing persistent to install or maintain on the target machines themselves. An inventory file lists the hosts (and groups of hosts) Ansible manages, typically at `/etc/ansible/hosts` or a project-specific file, grouping servers logically (`[webservers]`, `[databases]`) so commands and playbooks can target a specific group instead of every managed host.

An ad-hoc command runs a single module against a target group immediately, without writing a file — genuinely useful for a quick one-off task: `ansible webservers -m ping` checks connectivity to every host in the webservers group; `ansible webservers -a "systemctl status nginx" -b` runs a raw command (`-b` for "become", i.e. sudo) across every matching host and reports back the results from each.

A playbook is where Ansible's real value shows up: a YAML file describing a desired end state — "nginx should be installed and running", "this configuration file should have this exact content" — applied consistently across every targeted host. Critically, Ansible modules are idempotent by design: running the same playbook against a host that already matches the desired state does nothing and reports "ok" rather than an error or a duplicate action, which is what makes it safe to re-run a playbook repeatedly, including as a scheduled, unattended job, without worrying about cumulative side effects.

CommandPurposeExample
ansible all -m pingCheck connectivity to every host in the inventory—
ansible webservers -a "uptime"Run a raw ad-hoc command against a specific group—
ansible-playbook site.ymlRun a playbook against its targeted hosts—
ansible-playbook site.yml --checkDry-run a playbook, showing what would change without actually changing anything—

A minimal, genuinely useful playbook

# site.yml
- name: Ensure Nginx is installed and running
  hosts: webservers
  become: true
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: present

    - name: Ensure nginx is running and enabled
      systemd:
        name: nginx
        state: started
        enabled: true

Common Mistakes

  • ⚠ Forgetting `become: true` (or `-b` on ad-hoc commands) and having tasks silently fail or behave unexpectedly due to insufficient privileges
  • ⚠ Writing a playbook task as an imperative shell command instead of using the appropriate idempotent module — this loses the safe-to-rerun property that's the whole point of Ansible
  • ⚠ Running an untested playbook against production hosts without first using `--check` (dry-run) or testing against a single host

Quick Check

You run the same Ansible playbook twice in a row against a server that already matches the desired state. What should happen the second time?

Takeaway: Ansible modules are idempotent by design — that's what separates "safe to re-run automatically, anytime" from a raw shell script that might duplicate an action or error out the second time it runs.

Sponsor / Advertisement