CloudWithHorla | Cloud & DevOps Engineer (Learning in Progress 🚀)
Automation looks simple from the outside — until you try to build it yourself.
Recently, I worked on an Ansible hands-on project as part of my continuous learning in Cloud and DevOps engineering. The goal was simple on paper:
set up a controller node and manage multiple target nodes using Ansible.
In reality, the journey taught me far more than just commands.
🏗️ Project Overview
I built a small but realistic infrastructure on AWS consisting of:
- * 1 EC2 Controller (Ansible Master)
- * 3 EC2 Target Nodes
- * Ubuntu-based servers
- * SSH key-based authentication
- * Ansible ad-hoc commands and playbooks
This setup mirrors what actually happens in real-world environments — not just labs.
🔧 What I Implemented (Step-by-Step)
- Provisioned EC2 instances
- 1 controller
- 3 managed nodes
- Installed core dependencies
- Python3
- Pip
- Ansible on the controller
- Created Ansible configuration
- Custom
ansible.cfg - Inventory file defining all target nodes
- SSH Key-Based Authentication
- Generated SSH key pair on the controller
- Injected the controller’s public key into each node’s
authorized_keys -
Learned the difference between:
>> # append (safe) > # overwrite (dangerous)
You are adding a new line of Public Key which is safe.
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCppACFB47TRDuT8YXSSxL+T9jxirz0yC04avxRB8nAOnVcz5xXpyT0mhYbhcLCbI2D4pcWkSb1NqGyUwF3OeHGtur+0MkDq7/9jhPG0amrFbk+RBQM+WZ7UaEzopiK7ZB8MUKwtMGVhR9wgtIiSK6+fgmzNaCpfj13B9aH7iiMRn4sHCwr81zCAskb79DnhCmB8Y7NU2VAqlMnW5NIFEujv3aixmPSZbIKomNotGT2ljtOV5EoMuFFqq3DqA/kZVM1WJjBMEAlWtdQTn2UM5dTAi5BNvPMYJcdzEfc4fXvkp11rFYLiEc9ZUFkYi6APg1Ju5bPgh0Yp+9YOhlCMEuEPlAdxUIHE4Ih3HoNhLOB622c+a6lNK5SF7Y76gB5cdK8Rar/tRQqF6kxqy852SLfZbLnXkfIOuktF4XGemMMt13F7yw1k980vwcwCgfAK4zOZ2hmaDypmG0Rd/lhsIpC+4i6s4T8a+AB3BSKt9KH/Dn9cxUM0BVgxgM1hWyk7Hs= ubuntu@ip-10-0-8-231" >> .ssh/authorized_keys
Caveat: Best practise
You are removing an existing “Public Key” which is not safe.
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCppACFB47TRDuT8YXSSxL+T9jxirz0yC04avxRB8nAOnVcz5xXpyT0mhYbhcLCbI2D4pcWkSb1NqGyUwF3OeHGtur+0MkDq7/9jhPG0amrFbk+RBQM+WZ7UaEzopiK7ZB8MUKwtMGVhR9wgtIiSK6+fgmzNaCpfj13B9aH7iiMRn4sHCwr81zCAskb79DnhCmB8Y7NU2VAqlMnW5NIFEujv3aixmPSZbIKomNotGT2ljtOV5EoMuFFqq3DqA/kZVM1WJjBMEAlWtdQTn2UM5dTAi5BNvPMYJcdzEfc4fXvkp11rFYLiEc9ZUFkYi6APg1Ju5bPgh0Yp+9YOhlCMEuEPlAdxUIHE4Ih3HoNhLOB622c+a6lNK5SF7Y76gB5cdK8Rar/tRQqF6kxqy852SLfZbLnXkfIOuktF4XGemMMt13F7yw1k980vwcwCgfAK4zOZ2hmaDypmG0Rd/lhsIpC+4i6s4T8a+AB3BSKt9KH/Dn9cxUM0BVgxgM1hWyk7Hs= ubuntu@ip-10-0-8-231" > .ssh/authorized_keys
Caveat: Not a best practise
Sample
- Privilege Escalation
- Used
become: truefor system-level tasks - Ensured sudo access without breaking security
- Ansible Ad-Hoc Commands
ansible -m apt -a "name=git state=present" all -b
- Ansible Playbook Execution
- Installed and removed packages
- Copied files to remote nodes
- Managed services across multiple machines in parallel
📄 Sample Playbook used in my project:
---
- name: Installing nginx playbook
hosts: all
become: true
tasks:
- name: First Task
apt:
name: nginx
state: absent
- name: Second Task
apt:
name: nginx
state: present
- name: Copy file with owner and permissions
ansible.builtin.copy:
src: ./index.html
dest: /var/www/html/index.html
owner: root
group: root
mode: '0777'
- name: Third Task
apt:
name: apache2
state: present
⚠️ Challenges I Faced (and What I Learned)
1️⃣ “Permission denied (publickey)”
I initially thought Ansible could manage nodes without SSH access.
Through research and troubleshooting, I learned a critical truth:
Ansible always uses SSH. There is no automation without an initial trust path.
Key-based authentication must be established at least once — either at instance creation or manually.
2️⃣ PasswordAuthentication Confusion
I tried disabling password authentication before confirming key-based access.
That was a big lesson:
❌ Disabling
PasswordAuthenticationdoes NOT give access
✅ It only removes password login after SSH keys already work
Correct order matters:
- Establish SSH key access
- Test connectivity
- Then disable password authentication
3️⃣ APT Lock Errors (unattended-upgrades)
One node failed while others succeeded.
Cause:
- Ubuntu auto-updates were running in the background
Lesson:
Infrastructure is not deterministic — nodes behave independently.
Solution:
- Wait and retry
- Or design playbooks with retries and checks
Screenshot Output
This is when I ping of the node
This is when I ping all the node
This is the first page output of my Playbook
This is the second page output of my Playbook
Node 02
4️⃣ Wrong Package Names
I attempted to install apache instead of apache2.
Lesson:
Automation does exactly what you tell it — not what you mean.
Small naming errors can break entire workflows.
🧠 Key Takeaways I Want Others to Learn
- 🔑 SSH is foundational — Ansible cannot bypass it
- 🔁 Automation is about idempotency and retries
- 🧪 Always test with
--syntax-checkandpingyourself (localhost) for test.
- 🔐 Never overwrite
authorized_keys - 🛠️ Real learning happens when things break
🌱 Growth Mindset
This project reinforced my core belief:
Never stop learning.
Every error I faced forced me to:
- Research deeper
- Read documentation
- Understand why things work — not just how
I’m still learning.
I’m still breaking things.
And I’m still getting better — one tool at a time.
📌 Final Thought
Automation isn’t about avoiding work —
it’s about doing the work once, correctly and at scale.
CloudWithHorla ☁️
Cloud & DevOps Engineer | Always Learning









