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







Top comments (0)