Ansible Architecture, Configuration & Automated Inventory
Have Ansible discover your EC2 instances by tag instead of maintaining a host list nobody remembers to update.
- Time
- 39 min
- Level
- Intermediate
- Objectives
- 4 objectives
- Cost
- Low cost
Where this fits in the platform
This lab adds
- Hosts discovered by tag rather than typed into a file
Before you start
Cost — Low cost
— Ansible itself is free; the cost is whichever EC2 instances it configures. A `t3.micro` control node is inside the free tier.
The scenario#
The inventory is a text file with IP addresses in it. Instances are replaced weekly, so it is wrong most of the time, and the way you find out is a playbook that reports success against a host that no longer exists.
Hands-on environment
Run it on your own machine
Run this lab on your own machine. One command starts the environment, with everything the lab needs already installed:
The architecture, inventory and playbook sections run against the node1 container using the static inventory the controller ships with. The dynamic-inventory section is the exception — the aws_ec2 plugin queries the EC2 API, so that part needs an AWS account.
You will need:
- ansible
git clone https://github.com/EgyKode/EgyKode-lab.git
cd EgyKode-lab
./egykode start
./egykode shellYou need Docker and Git installed. Everything else runs inside the environment. The first start downloads it and takes a few minutes; later starts are seconds.
Not sure what you already have? Run: npm run doctor — it checks and changes nothing.
Run it on AWS
This lab builds real cloud infrastructure, so it needs your own AWS account. Follow the cost and cleanup notes above — the resources are yours, and so is the bill.
Anything you tick here is your own record. EgyKode cannot see inside that terminal, so the success criteria stay self-assessed even when the environment checks your work for you.
ansible.cfg — the settings you will otherwise repeat forever
Step 1 of 3
What you are building#
control node (your laptop)
| SSH :22 -- no agent on the far end
v
+---------------------------------------------+
| inventory: generated from AWS tags |
| tag_Role_jenkins -> i-0abc (10.0.1.12) |
| tag_Role_worker -> i-0def, i-0ghi |
+---------------------------------------------+Ansible is agentless, and that is the design decision everything else follows from. Puppet, Chef and Salt run a daemon on every target that you have to install, upgrade and monitor — a second fleet to operate. Ansible connects over SSH, copies a Python module to a temporary directory, runs it, collects JSON and deletes it.
The cost of that choice is real: execution is serial-ish and slower at thousands of hosts, and the target needs Python and SSH. The benefit is that there is nothing to keep running.
Build it#
What you are proving: You can set the connection defaults once, instead of repeating them on every command
This step settles no success criterion on its own.
[defaults]
inventory = inventory/aws_ec2.yml
remote_user = ec2-user
private_key_file = ~/.ssh/platform.pem
host_key_checking = False
interpreter_python = auto_silent
stdout_callback = yaml
callbacks_enabled = timer, profile_tasks
retry_files_enabled = False
forks = 20
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=300sThree of these are worth understanding rather than copying:
pipelining = Trueremoves a file transfer per task. On a playbook with forty tasks it is a large speed-up, and it requiresrequirettyto be off in/etc/sudoers— which it is on modern distributions.ControlPersistreuses one SSH connection instead of reconnecting per task.host_key_checking = Falseis right for ephemeral cloud instances whose keys change on every rebuild, and wrong for long-lived servers, where it removes your protection against a man-in-the-middle.
What you are proving: You can generate an inventory from AWS tags, so a new instance is managed without editing a file
Marking this settles success criteria 2 and 3.
# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
filters:
instance-state-name: running
tag:Project: platform
keyed_groups:
- key: tags.Role
prefix: tag_Role
- key: placement.availability_zone
prefix: az
hostnames:
- private-ip-address # private: you reach them through the VPC
compose:
ansible_host: private_ip_addressansible-galaxy collection install amazon.aws
ansible-inventory --graph@all:
|--@tag_Role_jenkins:
| |--10.0.1.12
|--@tag_Role_worker:
| |--10.0.10.4
| |--10.0.11.9This is the whole point of the lab. A new instance carrying
Role=worker is in the tag_Role_worker group the moment it boots. Nobody
edits anything, and the inventory cannot drift from reality because it is
reality, queried on each run.
What you are proving: You can prove SSH, the user, become and Python all work before writing a playbook, and explain why no agent is needed on the target
Marking this settles success criteria 1 and 4.
ansible -m ping all
ansible -m ping tag_Role_jenkins
ansible -a "uptime" tag_Role_worker
ansible -m setup --tree /tmp/facts all # everything Ansible knowsping here is not ICMP. It connects over SSH, runs a Python module and expects
pong back — so a success proves SSH, the user, become and Python all work
before any real task depends on them.
Verify it worked#
# Hosts appear with no file listing them
ansible-inventory --graph
ansible-inventory --host 10.0.1.12 | jq '.tags'
# Connectivity, privilege escalation and Python all work
ansible -m ping all
ansible -m command -a "id" --become all # uid=0(root)
# The proof: launch another tagged instance and re-run with no edits
aws ec2 run-instances ... --tag-specifications \
'ResourceType=instance,Tags=[{Key=Project,Value=platform},{Key=Role,Value=worker}]'
ansible -m ping tag_Role_worker # the new host is simply thereFailed to connect to the host via ssh: Permission denied (publickey)
Wrong remote_user for the AMI — ec2-user on Amazon Linux, ubuntu on
Ubuntu — or the wrong key. ansible -vvv prints the exact SSH command; run it
by hand.
The inventory is empty
The plugin is not enabled, the filters match nothing, or the credentials have
no ec2:DescribeInstances. ansible-inventory --list -vvv shows the API call
and its result.
Missing sudo password
become_ask_pass is false and the user needs a password. Use a NOPASSWD rule
scoped to what automation actually runs, not a blanket one.
/usr/bin/python: not found
The target has no Python at the expected path. interpreter_python = auto_silent handles almost every case; raw is the escape hatch for
bootstrapping a host that has none at all.
Ansible reports success against a host that is gone
That is the static-inventory failure this lab removes. A generated inventory cannot list an instance that is not running.
Clean up#
# Nothing to destroy — this lab only reads AWS.
aws ec2 describe-instances --filters "Name=tag:Project,Values=platform" \
"Name=instance-state-name,Values=running" \
--query 'Reservations[].Instances[].InstanceId'Cost of this lab: Low. Ansible itself is free; you are billed only for whatever instances you are managing.
Success criteria
0 of 4
The concept behind it
Next up
Lab 29 of 59 on the project path