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.
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
- ansible/
- ansible.cfgnew
- inventory.ininew
Part 3
Build it, step by step
- 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
ansiblepackage includes thecommunity.dockercollection used here.terminal$ pipx install --include-deps ansiblefor h in web1 web2; do docker run -d --name $h ubuntu:24.04 sleep infinity; doneansible --version | head -1── expected output ──ansible [core 2.19.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 setansible_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
Project defaults in ansible.cfg
Ansible reads
ansible.cfgfrom the current directory, so every command inansible/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
Bootstrap Python, then ping every host
Modules need Python on the target. The
rawmodule runs a plain command without Python, which is handy for exactly this bootstrap. After that, thepingmodule confirms Ansible can connect and run modules. It isn't an ICMP ping.terminal$ cd ansibleansible 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
Ad-hoc commands across the fleet
Ad-hoc commands are one-off tasks: great for questions ('which kernel is everyone on?') and emergencies.
-mpicks the module and-agives its arguments. Noticechanged: truethe first timeaptinstalls something andchanged: falsewhen 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-genericweb2 | CHANGED | rc=0 >>6.8.0-45-generic..."changed": false,"changed": false,
Checkpoint — you should now have
- ✓
ansible web -m pingreturns pong from both hosts. - ✓You can run a module across a group and see
changedflip 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.
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.
Part 5
Interview questions from this mission
What does 'agentless' mean for Ansible, and what are the trade-offs?
What makes an Ansible module idempotent?