Ansible Basics - Infrastructure Automation Fundamentals
Status: Active
Last Updated: 2026-01-30
Category: Infrastructure - Configuration Management
Prerequisites: kb/basics/linux-fundamentals, kb/basics/ssh-basics
Time: 3-4 hours
Tags: ansible, automation, configuration-management, iac, agentless
Summary
Learn infrastructure automation with Ansible, an agentless configuration management tool. Master playbooks, inventory, and ad-hoc commands to automate server provisioning, configuration, and application deployment without writing complex scripts.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Understand what Ansible is and when to use it
- โ Install Ansible on control node
- โ Create and manage inventory files
- โ Write basic playbooks
- โ Use modules for common tasks
- โ Run ad-hoc commands
- โ Understand Ansible architecture
๐ค What is Ansible?
Ansible: Open-source IT automation tool for configuration management, application deployment, and orchestration.
Key Characteristics:
- Agentless: No software on managed nodes (uses SSH)
- Declarative: Describe desired state, Ansible makes it happen
- Idempotent: Safe to run multiple times
- Simple: YAML-based, human-readable
- Powerful: Thousands of modules for everything
๐๏ธ Ansible Architecture
Components:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Control Node (Your Laptop/Server) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Ansible (installed here) โ โ
โ โ - Playbooks (YAML files) โ โ
โ โ - Inventory (list of hosts) โ โ
โ โ - Modules (tasks to execute) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SSH (no agent needed!)
โโโโโโโโโดโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ โ โ โ
โโโโโโโโโผโโโโโโโโ โโโโโโผโโโโโ โโโโโโผโโโโโ โโโโโโผโโโโโ
โ Managed Node โ โ Node 2 โ โ Node 3 โ โ Node N โ
โ (web-01) โ โ (web-02)โ โ (db-01) โ โ ... โ
โ โ โ โ โ โ โ โ
โ Python only! โ โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
No Agents!: Ansible uses SSH to connect and execute tasks. Managed nodes only need Python installed.
๐ Ansible vs Alternatives
| Feature | Ansible | Puppet | Chef | SaltStack |
|---|---|---|---|---|
| Agent | No | Yes | Yes | Optional |
| Language | YAML | Ruby | Ruby | YAML |
| Learning Curve | Low | High | High | Medium |
| Speed | Medium | Medium | Medium | Fast |
| State | Push | Pull | Pull | Push/Pull |
| Complexity | Simple | Complex | Complex | Medium |
When to Use Ansible:
- Quick setup (no agents!)
- Small to medium infrastructure
- Ad-hoc tasks frequently needed
- Team prefers simple YAML
- Agentless requirement
When to Consider Alternatives:
- Very large scale (thousands of nodes) โ SaltStack
- Complex compliance requirements โ Puppet
- Need guaranteed state enforcement โ Puppet/Chef (pull model)
๐ฆ Installation
Control Node (Where Ansible Runs)
Requirements:
- Linux/macOS/WSL (not Windows natively)
- Python 3.8+
Ubuntu/Debian
# Add Ansible PPA
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
# Install Ansible
sudo apt install -y ansible
# Verify
ansible --version
Fedora/RHEL/CentOS
# Install from dnf
sudo dnf install -y ansible
# Or use EPEL on RHEL/CentOS
sudo dnf install -y epel-release
sudo dnf install -y ansible
# Verify
ansible --version
macOS
# Using Homebrew
brew install ansible
# Verify
ansible --version
Python pip (Any Platform)
# Install via pip (in virtual environment recommended)
python3 -m venv ~/ansible-venv
source ~/ansible-venv/bin/activate
pip install ansible
# Verify
ansible --version
Managed Nodes (Target Servers)
Requirements:
- SSH access from control node
- Python 3.5+ installed
- User with sudo (for privilege escalation)
Prepare Managed Node:
# On managed node
# Install Python (usually already installed)
sudo apt install -y python3 # Ubuntu/Debian
sudo dnf install -y python3 # Fedora/RHEL
# Configure SSH key-based auth (from control node)
ssh-copy-id user@managed-node
๐ Inventory Basics
Inventory: List of managed nodes (hosts) Ansible controls.
Simple Inventory
File: inventory.ini
# Single host
web1.example.com
# Multiple hosts
web1.example.com
web2.example.com
db1.example.com
# Hosts with IP addresses
192.168.1.10
192.168.1.11
Inventory with Groups
# Web servers
[webservers]
web1.example.com
web2.example.com
192.168.1.10
# Database servers
[databases]
db1.example.com
db2.example.com
# Monitoring servers
[monitoring]
monitor.example.com
# Group of groups
[production:children]
webservers
databases
monitoring
Inventory with Variables
[webservers]
web1.example.com ansible_host=192.168.1.10 ansible_user=ubuntu
web2.example.com ansible_host=192.168.1.11 ansible_user=ubuntu
[webservers:vars]
ansible_port=22
ansible_python_interpreter=/usr/bin/python3
http_port=80
[databases]
db1.example.com
[databases:vars]
ansible_user=postgres
db_port=5432
YAML Inventory
File: inventory.yaml
all:
children:
webservers:
hosts:
web1.example.com:
ansible_host: 192.168.1.10
web2.example.com:
ansible_host: 192.168.1.11
vars:
ansible_user: ubuntu
http_port: 80
databases:
hosts:
db1.example.com:
ansible_host: 192.168.1.20
vars:
ansible_user: postgres
db_port: 5432
Test Inventory
# List all hosts
ansible all -i inventory.ini --list-hosts
# List specific group
ansible webservers -i inventory.ini --list-hosts
# Ping all hosts
ansible all -i inventory.ini -m ping
๐ฏ Ad-Hoc Commands
Ad-Hoc: One-line commands for quick tasks (no playbook needed).
Syntax:
ansible [hosts] -i [inventory] -m [module] -a "[arguments]"
Ping Hosts
# Ping all hosts
ansible all -i inventory.ini -m ping
# Ping specific group
ansible webservers -i inventory.ini -m ping
# Output (success):
web1.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
Run Commands
# Execute shell command
ansible all -i inventory.ini -m shell -a "uptime"
# Check disk usage
ansible all -i inventory.ini -m shell -a "df -h"
# View memory
ansible all -i inventory.ini -m shell -a "free -m"
# With sudo (become)
ansible all -i inventory.ini -m shell -a "systemctl status nginx" --become
Install Packages
# Install package (Ubuntu/Debian)
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become
# Install multiple packages
ansible webservers -i inventory.ini -m apt -a "name=nginx,git,curl state=present" --become
# Update all packages
ansible all -i inventory.ini -m apt -a "upgrade=dist" --become
# On RHEL/Fedora (use dnf module)
ansible webservers -i inventory.ini -m dnf -a "name=nginx state=present" --become
Manage Services
# Start service
ansible webservers -i inventory.ini -m service -a "name=nginx state=started" --become
# Stop service
ansible webservers -i inventory.ini -m service -a "name=nginx state=stopped" --become
# Restart service
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become
# Enable service on boot
ansible webservers -i inventory.ini -m service -a "name=nginx enabled=yes" --become
Copy Files
# Copy file to remote host
ansible webservers -i inventory.ini -m copy -a "src=/local/file.txt dest=/remote/file.txt" --become
# Copy with permissions
ansible webservers -i inventory.ini -m copy -a "src=app.conf dest=/etc/app/app.conf owner=root mode=0644" --become
Manage Users
# Create user
ansible all -i inventory.ini -m user -a "name=john state=present" --become
# Create user with sudo access
ansible all -i inventory.ini -m user -a "name=john groups=sudo append=yes" --become
# Remove user
ansible all -i inventory.ini -m user -a "name=john state=absent remove=yes" --become
๐ Playbooks Basics
Playbook: YAML file defining automation tasks.
First Playbook
File: webserver.yaml
---
- name: Configure web servers
hosts: webservers
become: yes # Use sudo
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Start Nginx service
service:
name: nginx
state: started
enabled: yes
- name: Copy index.html
copy:
content: "<h1>Hello from Ansible!</h1>"
dest: /var/www/html/index.html
mode: '0644'
Run Playbook:
ansible-playbook -i inventory.ini webserver.yaml
Playbook Structure
---
# Play 1
- name: Play description
hosts: target_hosts
become: yes # Optional: use sudo
vars: # Optional: variables
var1: value1
tasks:
- name: Task description
module_name:
parameter1: value1
parameter2: value2
# Play 2 (multiple plays in one file)
- name: Another play
hosts: other_hosts
tasks:
- name: Another task
module_name:
param: value
Multiple Plays Example
---
- name: Configure web servers
hosts: webservers
become: yes
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
- name: Configure database servers
hosts: databases
become: yes
tasks:
- name: Install PostgreSQL
apt:
name: postgresql
state: present
๐งฉ Common Modules
apt/dnf - Package Management
- name: Install packages (Ubuntu/Debian)
apt:
name:
- nginx
- git
- curl
state: present
update_cache: yes
- name: Install packages (RHEL/Fedora)
dnf:
name: nginx
state: present
service/systemd - Service Management
- name: Manage service
service:
name: nginx
state: started # started, stopped, restarted, reloaded
enabled: yes # Start on boot
copy - Copy Files
- name: Copy file
copy:
src: /local/path/file.txt # File on control node
dest: /remote/path/file.txt # Destination on managed node
owner: www-data
group: www-data
mode: '0644'
- name: Copy with inline content
copy:
content: "Hello World"
dest: /tmp/hello.txt
template - Jinja2 Templates
- name: Deploy config from template
template:
src: nginx.conf.j2 # Template file
dest: /etc/nginx/nginx.conf
owner: root
mode: '0644'
notify: Restart Nginx # Trigger handler
Template Example (nginx.conf.j2):
server {
listen {{ http_port }};
server_name {{ server_name }};
location / {
root {{ web_root }};
}
}
file - Manage Files/Directories
- name: Create directory
file:
path: /app/data
state: directory
owner: appuser
mode: '0755'
- name: Create empty file
file:
path: /tmp/myfile
state: touch
- name: Remove file
file:
path: /tmp/oldfile
state: absent
- name: Create symbolic link
file:
src: /app/current
dest: /app/releases/v1.0
state: link
user - User Management
- name: Create user
user:
name: appuser
groups: sudo,docker
append: yes # Add to groups (don't replace)
shell: /bin/bash
create_home: yes
lineinfile - Edit Files
- name: Add line to file
lineinfile:
path: /etc/hosts
line: "192.168.1.10 myserver.local"
state: present
- name: Replace line with regex
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
git - Git Operations
- name: Clone repository
git:
repo: https://github.com/user/repo.git
dest: /app/repo
version: main # Branch/tag/commit
- name: Pull latest changes
git:
repo: https://github.com/user/repo.git
dest: /app/repo
version: main
update: yes
๐ Playbook Features
Variables
Define in playbook:
---
- name: Deploy app
hosts: webservers
vars:
app_name: myapp
app_port: 8080
app_user: appuser
tasks:
- name: Create app directory
file:
path: "/opt/{{ app_name }}"
state: directory
owner: "{{ app_user }}"
Define in separate file (vars.yaml):
app_name: myapp
app_port: 8080
app_user: appuser
Use in playbook:
---
- name: Deploy app
hosts: webservers
vars_files:
- vars.yaml
tasks:
- name: Create directory
file:
path: "/opt/{{ app_name }}"
state: directory
Handlers
Handlers: Tasks that run only when notified (e.g., restart service only if config changed).
---
- name: Configure Nginx
hosts: webservers
become: yes
tasks:
- name: Copy Nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart Nginx
- name: Copy site config
copy:
src: mysite.conf
dest: /etc/nginx/sites-available/mysite
notify: Restart Nginx
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
Key Points:
- Handlers run at end of play
- Only run if notified
- Run once even if notified multiple times
Conditionals
- name: Install package on Debian
apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Install package on RedHat
dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Run only on production
command: /app/deploy.sh
when: environment == "production"
Loops
- name: Install multiple packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl
- vim
- name: Create multiple users
user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- charlie
- name: Loop with dict
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: 'alice', groups: 'sudo' }
- { name: 'bob', groups: 'docker' }
Tags
Run specific tasks:
---
- name: Full deployment
hosts: webservers
tasks:
- name: Install packages
apt:
name: nginx
state: present
tags: install
- name: Copy config
copy:
src: nginx.conf
dest: /etc/nginx/
tags: config
- name: Start service
service:
name: nginx
state: started
tags: service
Run only tagged tasks:
# Run only install tasks
ansible-playbook playbook.yaml --tags install
# Skip specific tags
ansible-playbook playbook.yaml --skip-tags config
๐ฌ Complete Example: LAMP Stack
Playbook: lamp.yaml
---
- name: Install LAMP stack
hosts: webservers
become: yes
vars:
mysql_root_password: "secret123"
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install Apache
apt:
name: apache2
state: present
- name: Install MySQL
apt:
name:
- mysql-server
- python3-pymysql
state: present
- name: Install PHP
apt:
name:
- php
- php-mysql
- libapache2-mod-php
state: present
- name: Start and enable Apache
service:
name: apache2
state: started
enabled: yes
- name: Start and enable MySQL
service:
name: mysql
state: started
enabled: yes
- name: Deploy test PHP page
copy:
content: |
<?php
phpinfo();
?>
dest: /var/www/html/info.php
mode: '0644'
- name: Configure firewall
ufw:
rule: allow
port: "{{ item }}"
loop:
- "80"
- "443"
Run:
ansible-playbook -i inventory.ini lamp.yaml
๐ Debugging
Check Syntax
# Validate playbook syntax
ansible-playbook playbook.yaml --syntax-check
Dry Run (Check Mode)
# See what would change (don't actually change)
ansible-playbook playbook.yaml --check
# With diff output
ansible-playbook playbook.yaml --check --diff
Verbose Output
# Normal
ansible-playbook playbook.yaml
# Verbose
ansible-playbook playbook.yaml -v
# More verbose
ansible-playbook playbook.yaml -vv
# Very verbose
ansible-playbook playbook.yaml -vvv
Debug Module
- name: Show variable
debug:
var: ansible_hostname
- name: Show message
debug:
msg: "The server is {{ ansible_hostname }}"
- name: Show all facts
debug:
var: ansible_facts
โ๏ธ Configuration
File: /etc/ansible/ansible.cfg or ~/.ansible.cfg or ./ansible.cfg
Example Configuration:
[defaults]
# Inventory location
inventory = ./inventory.ini
# Don't check host keys (lab only!)
host_key_checking = False
# Number of parallel processes
forks = 10
# Timeout for SSH connections
timeout = 30
# Gathering facts (slow, can disable if not needed)
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
# Roles path
roles_path = ./roles
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
๐ What's Next?
You've learned Ansible basics! Continue with:
Inventory Management:
- ansible-inventory - Advanced inventory patterns
Playbook Mastery:
- ansible-playbooks - Advanced playbook techniques
Reusable Content:
- ansible-roles - Organize code with roles
Secrets:
- ansible-vault - Encrypt sensitive data
๐ Resources
Official Docs:
Learning:
Community:
- Ansible Galaxy - Shared roles
- Ansible GitHub
๐ Change Log
2026-01-30
- Created Ansible basics article
- Covered installation and setup
- Explained inventory management
- Introduced ad-hoc commands
- Demonstrated playbook creation
- Covered common modules
- Included LAMP stack complete example
- Added debugging and configuration sections
Next Article: ansible-inventory - Master inventory management!