1. Introduction to Ansible
Ansible is an open-source automation engine that automates software provisioning, configuration management, and application deployment. It uses a simple YAML syntax and connects over SSH — no agents needed on managed nodes. From beginners to senior DevOps engineers, Ansible brings clarity, repeatability, and speed to infrastructure management.
Real DevOps Mindset: Infrastructure as Code (IaC) means treating servers like cattle, not pets. Manual changes are forbidden. Ansible playbooks are the single source of truth for your entire infrastructure.
🔨 Project 1: Verify Ansible Installation
Real-world scenario: As a new DevOps engineer, your very first task is to ensure Ansible is correctly installed and functional on the control node. This is a basic health check before any automation work begins.
ansible --version command shows you the Ansible version, the config file location being used, and the Python interpreter version. This helps you verify the installation and understand which configuration Ansible is reading.ansible --version
-m ping flag tells Ansible to use the ping module. This is not an ICMP ping; it’s an Ansible-specific module that checks if the control node can establish a connection to the target and if Python is available on the target.ansible localhost -m ping
Expected output: localhost | SUCCESS => { "changed": false, "ping": "pong" } — If you see this, Ansible is working perfectly. The "changed": false indicates idempotency: nothing was changed because nothing needed to be.
🔨 Project 2: First Ad-hoc Command (Check Uptime)
Real-world scenario: Production servers need regular health checks. Checking uptime is a common daily task to see how long servers have been running and what the current load average is.
command module with -m command. The -a flag passes the actual shell command as an argument. Here we run the standard Linux uptime command.ansible localhost -m command -a "uptime"
Expected output: localhost | CHANGED | rc=0 >> 14:32:10 up 5 days, 2:15, 1 user, load average: 0.00, 0.01, 0.05
Why command module? It’s the default module if none is specified. However, note that it does not use a shell (no pipes, redirects). For those, you would use the shell module.
🔨 Project 3: Create Your First Inventory File
Real-world scenario: In any production environment, you have multiple servers categorized by their role — web servers, database servers, caching servers, etc. The inventory file organizes them into logical groups.
inventory.ini. The default location is /etc/ansible/hosts, but for project-based work, we use a custom inventory file in the project directory.[webservers] web01 ansible_host=192.168.1.10 web02 ansible_host=192.168.1.11 [dbservers] db01 ansible_host=192.168.1.20
[webservers] is a group name. web01 is an alias (hostname) we give to the server. The ansible_host variable specifies the actual IP address Ansible should connect to. This decoupling of name and IP is useful when hostnames are long or IPs change.ansible -i inventory.ini all -m ping. The -i flag specifies the inventory file, and all targets every host in the file.🔨 Project 4: Gather System Facts
Real-world scenario: Before automating a server, you need detailed information about it — OS distribution, kernel version, available memory, CPU cores, network interfaces, disk space. Ansible’s setup module collects all these “facts” automatically.
ansible localhost -m setup
filter parameter. This is much faster when you only need specific data.ansible localhost -m setup -a "filter=ansible_memory_mb"
Use case: When configuring Nginx, you might want to set worker_connections based on available memory. You can dynamically calculate this using ansible_memory_mb.real.total in your playbook.
🔨 Project 5: Explain Ansible Architecture in Your Own Words
Real-world scenario: Whether you’re preparing for a job interview, training new team members, or writing documentation, being able to clearly explain Ansible’s architecture is an essential skill.
• Control Node vs Managed Node — what runs where
• Inventory — how Ansible knows which servers to manage
• Modules — what they are and the concept of idempotency
• Playbooks — YAML structure, tasks, and handlers
• Push-based architecture — why being agentless is a massive advantage over Puppet/Chef
• Connection flow: Control Node → SSH → Managed Node → Execute Module → Return JSON
This exercise builds deep understanding. You should be able to explain why Ansible is agentless, how it achieves idempotency, and what happens under the hood when you run ansible-playbook.
2. Why Ansible in DevOps (Real Industry Use)
Why has Ansible become the go-to automation tool in DevOps? Three words: Simple, Agentless, Powerful. Unlike Puppet, Chef, or SaltStack, you don’t need to install any agent software on managed nodes. It uses SSH — which is already present on every Linux server. Its YAML syntax is readable by both developers and operations engineers, breaking down silos.
Industry adoption: Netflix uses Ansible for deployment orchestration and chaos engineering. Red Hat integrates Ansible deeply with Satellite and OpenShift. Major cloud providers (AWS, GCP, Azure) have official Ansible collections. NASA uses it for infrastructure automation.
🔨 Project 1: Agentless Comparison Report
Real-world scenario: Your company is considering migrating from Puppet to Ansible. You need to prepare a detailed comparison report for the CTO explaining the benefits and trade-offs.
Key insight to highlight: Ansible’s agentless architecture eliminates the need to maintain agent versions across hundreds of servers, drastically reducing operational overhead.
🔨 Project 2: Simple Nginx Install Playbook
Real-world scenario: Every new web server needs Nginx installed. Doing this manually for 50 servers is error-prone. A playbook makes it automated, repeatable, and auditable.
install_nginx.yml. hosts: webservers limits execution to the webservers group. become: yes elevates privileges (like sudo).---
- name: Install and configure Nginx on web servers
hosts: webservers
become: yes
tasks:
- name: Update apt package cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install Nginx package
apt:
name: nginx
state: present
- name: Ensure Nginx service is running and enabled at boot
service:
name: nginx
state: started
enabled: yesansible-playbook -i inventory.ini install_nginx.yml. Task 1 updates the apt cache (but only if it’s older than 3600 seconds — idempotent). Task 2 installs nginx (skips if already present). Task 3 ensures the service is started and will auto-start on boot.Module explanation: apt module works on Debian-based distributions. The state: present means “ensure it’s installed” (not absent/removed). The service module controls systemd services.
🔨 Project 3: Idempotency Demonstration
Real-world scenario: You need to demonstrate to skeptical stakeholders that Ansible is safe to run repeatedly in production without causing unwanted changes.
changed=3 (or however many tasks actually made changes).changed=0 and ok=3. Ansible recognized that everything is already in the desired state and made zero changes.Why this matters: In production, you can schedule playbooks to run every hour as a compliance check. If nothing has drifted, nothing changes. If someone manually messed with a server, Ansible will fix it back to the defined state.
🔨 Project 4: Multi-Distribution Support with Conditionals
Real-world scenario: Your environment has both Ubuntu (Debian-based) and CentOS (RedHat-based) servers. The same playbook should work on both without manual intervention.
---
- name: Install web server (cross-distribution)
hosts: all
become: yes
tasks:
- name: Install Apache on Debian/Ubuntu systems
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Install Apache on RedHat/CentOS systems
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"Explanation: The when condition checks the Ansible fact ansible_os_family. This fact is automatically collected from the managed node by the setup module. The correct package manager (apt or yum) is chosen based on the OS family.
🔨 Project 5: Group Variables for Environment Segregation
Real-world scenario: Staging and Production environments need different configurations — different database passwords, API endpoints, debug flags. But they should use the same playbook for consistency.
group_vars/staging.yml and group_vars/production.yml.# group_vars/production.yml --- db_password: "prod_secure_pass_2024" app_env: "production" debug_mode: false max_workers: 8
[staging] and [production]. Ansible automatically loads the corresponding group_vars/<group_name>.yml file.Benefit: One playbook serves both environments. Variables are isolated per environment. You can’t accidentally deploy staging configs to production because Ansible picks the right file based on group membership.
3. Installation & Setup (Linux)
Setting up a production-ready Ansible control node on Ubuntu 22.04 LTS. There are two main methods: APT repository (stable, recommended for production) and pip (latest version, useful for development). We’ll cover both and configure ansible.cfg with performance-optimized settings.
🔨 Project 1: Install Ansible on Ubuntu 22.04 via APT
Real-world scenario: You’re setting up a fresh Ubuntu 22.04 server that will serve as the centralized Ansible control node for your entire infrastructure.
sudo apt update && sudo apt upgrade -y sudo apt install software-properties-common -y sudo add-apt-repository --yes --update ppa:ansible/ansible sudo apt install ansible -y
ansible --version. You should see version 2.15 or later. Also note the config file path it’s currently using.Why PPA? The default Ubuntu repos lag behind. For production automation, you want the latest modules and security fixes.
🔨 Project 2: Configure ansible.cfg for Production Performance
Real-world scenario: The default Ansible configuration is conservative and not optimized for production. You need to tune settings for speed, reliability, and convenience.
ansible.cfg in your project root directory.[defaults] inventory = ./inventory host_key_checking = False remote_user = ansible private_key_file = ~/.ssh/id_rsa timeout = 30 gathering = smart fact_caching = jsonfile fact_caching_connection = /tmp/ansible_cache fact_caching_timeout = 3600 pipelining = True forks = 20 [ssh_connection] ssh_args = -o ControlMaster=auto -o ControlPersist=300s control_path = /tmp/ansible-%%h-%%p-%%r
•
host_key_checking = False — Bypasses the SSH host key confirmation prompt for new servers. Essential for automation where you can’t interactively type “yes”.
•
pipelining = True — Reduces SSH connections by executing modules without copying them first. This significantly improves performance.
• forks = 20 — Run tasks on up to 20 servers simultaneously. The default is 5, which is too slow for large fleets.
• gathering = smart — Collects facts, but uses cache if available. fact_caching stores these facts as JSON files to avoid re-collection on every run.
• ControlPersist — Keeps SSH connections alive for 5 minutes, avoiding the overhead of establishing new connections for each task.
🔨 Project 3: SSH Key-Based Authentication Setup
Real-world scenario: Password-based SSH is a security risk and impossible to use in automation scripts. Key-based authentication is the industry standard.
-N "" creates a key without a passphrase — suitable for automation. For extra security, use ssh-agent with a passphrase-protected key.ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
ssh-copy-id command appends your public key to the ~/.ssh/authorized_keys file on the target server.ssh-copy-id ansible@192.168.1.10 ssh-copy-id ansible@192.168.1.11
ssh ansible@192.168.1.10 — you should be logged in without any password prompt.🔨 Project 4: Test Connection to All Managed Nodes
Real-world scenario: After provisioning new servers or updating the inventory, the first step is always to verify Ansible can reach all nodes.
ansible all -m ping -i inventory.ini
Expected result: Every server should respond with SUCCESS. If any server returns UNREACHABLE, you need to troubleshoot: check if the server is running, if the IP is correct, if port 22 is open, and if the SSH key is properly installed.
🔨 Project 5: Configure Python Interpreter for Legacy Systems
Real-world scenario: Some older servers (CentOS 6, ancient Ubuntu) come with Python 2 by default. Ansible requires Python 3 on managed nodes for full functionality.
legacy_server ansible_host=10.0.0.5 ansible_python_interpreter=/usr/bin/python3