Command Palette

Search for a command to run...

Hectal

Mission 0.1 · Stage 0 — Configuration Management with Ansible

Inventory and Your First Ad-Hoc Commands

Goal: An Ansible project that knows about two ShopLite web servers and can run commands on both at once.

30 min Free — local containers stand in for servers 5 steps 2 break-it drills

By the end of this mission

  • Explain what configuration management solves and why Ansible is agentless
  • Write an inventory with groups and connection settings
  • Run ad-hoc commands and modules across many hosts

Part 1

Understand it first

Why configuration management exists

Logging into ten servers and running the same commands by hand works exactly once. The eleventh server is subtly different, someone forgets a step, and nobody can say what's installed where. Configuration management describes the desired state of each machine (packages, files, users, services) in code, and applies it the same way everywhere, which is the same idea as Terraform (Terraform course, Mission 0.1), one layer higher.

How Ansible works

Ansible is AGENTLESS: nothing is installed on the managed machines except Python. The CONTROL NODE (your laptop or a CI runner) connects over SSH (or WinRM for Windows, or Docker for containers), copies small Python modules across, runs them, and collects the JSON results. Puppet, Chef, and Salt, the older generation, mostly use an agent on every machine pulling configuration from a central server.

The INVENTORY lists the machines and groups them (web, db, prod); every command targets a group or pattern. MODULES are the units of work (apt, copy, template, service, user), each designed to be IDEMPOTENT: running it twice changes nothing the second time.

Part 2

Your project after this mission · 2 files change

shoplite-gitops/
  • ansible/
    • ansible.cfgnew
    • inventory.ininew

Part 3

Build it, step by step

  1. 1

    Install Ansible and start two lab 'servers'

    Two Ubuntu containers stand in for web servers, so the lab is free and disposable. For real servers, the only difference is the connection type (SSH instead of Docker). The ansible package includes the community.docker collection used here.

    terminal
    $ pipx install --include-deps ansible
    for h in web1 web2; do docker run -d --name $h ubuntu:24.04 sleep infinity; done
    ansible --version | head -1
    ── expected output ──
    ansible [core 2.19.2]
  2. 2

    Write the inventory

    Groups in square brackets; host variables inline. [web:vars] applies variables to every host in the group. For SSH servers you'd set ansible_host, ansible_user, and a key instead of the Docker connection.

    ansible/inventory.iniwhole fileini
    [web]
    web1
    web2
    
    [web:vars]
    ansible_connection=community.docker.docker
    
    # A real server would look like:
    # web3 ansible_host=10.20.10.15 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/shoplite.pem
  3. 3

    Project defaults in ansible.cfg

    Ansible reads ansible.cfg from the current directory, so every command in ansible/ uses this inventory without extra flags.

    ansible/ansible.cfgwhole fileini
    [defaults]
    inventory = inventory.ini
    host_key_checking = False    # lab only; keep checking on for real servers
    stdout_callback = yaml
  4. 4

    Bootstrap Python, then ping every host

    Modules need Python on the target. The raw module runs a plain command without Python, which is handy for exactly this bootstrap. After that, the ping module confirms Ansible can connect and run modules. It isn't an ICMP ping.

    terminal
    $ cd ansible
    ansible web -m raw -a 'apt-get update -qq && apt-get install -y -qq python3'
    ansible web -m ping
    ── expected output ──
    web1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
    }
    web2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
    }
  5. 5

    Ad-hoc commands across the fleet

    Ad-hoc commands are one-off tasks: great for questions ('which kernel is everyone on?') and emergencies. -m picks the module and -a gives its arguments. Notice changed: true the first time apt installs something and changed: false when you run it again. That's idempotency.

    terminal
    $ ansible web -m command -a 'uname -r'
    ansible web -m apt -a 'name=curl state=present'
    ansible web -m apt -a 'name=curl state=present' | grep changed
    ── expected output ──
    web1 | CHANGED | rc=0 >>
    6.8.0-45-generic
    web2 | CHANGED | rc=0 >>
    6.8.0-45-generic
    ...
    "changed": false,
    "changed": false,

Checkpoint — you should now have

  • ✓ansible web -m ping returns pong from both hosts.
  • ✓You can run a module across a group and see changed flip from true to false on the second run.
  • ✓Your inventory and ansible.cfg are committed in ansible/.

Part 4

Break it on purpose

Make each change, run the command, and read the error before revealing the diagnosis. Recognising these messages on sight is what makes you fast on a real team. Undo the change afterwards.

Break #1

Target a host that isn't reachable

Add web3 to the [web] group without starting a container for it, then run ansible web -m ping.

terminal
$ ansible web -m ping
── what you'll see ──
web3 | UNREACHABLE! => {
"changed": false,
"msg": "Error when connecting to the Docker daemon: container web3 not found",
"unreachable": true
}

Break #2

Use a module without Python on the target

Start a fresh container web4 from ubuntu:24.04, add it to the inventory, and run ansible web4 -m ping without the raw bootstrap.

terminal
$ ansible web4 -m ping
── what you'll see ──
web4 | FAILED! => {
"changed": false,
"module_stderr": "/bin/sh: 1: /usr/bin/python3: not found",
"msg": "The module failed to execute correctly, you probably need to set the interpreter."
}

Part 5

Interview questions from this mission

01

What does 'agentless' mean for Ansible, and what are the trade-offs?

02

What makes an Ansible module idempotent?

0/4 · 0%