Introduction to Ansible
Ansible is an open-source automation tool that simplifies configuration management, application deployment, task automation, and orchestration. It uses an agentless architecture, connecting to managed nodes via SSH (for Linux/Unix) or WinRM (for Windows), making it lightweight and secure. Ansible is built on Python, uses YAML for defining automation tasks, and follows a declarative approach that focuses on the desired end state rather than the steps to achieve it. Ansible’s simplicity, flexibility, and powerful automation capabilities make it a popular choice for IT teams looking to streamline operations and enforce consistency across infrastructure.
Core Concepts & Components
Ansible Architecture
- Control Node: The machine where Ansible is installed and automation runs from
- Managed Nodes: Target machines that Ansible manages (no agent required)
- Inventory: Defines managed nodes and their groupings
- Modules: Self-contained units of code that perform specific tasks
- Tasks: Individual units of work that use modules
- Playbooks: YAML files that organize tasks in ordered sequences
- Roles: Reusable, shareable collections of playbooks, tasks, and related files
- Collections: Distribution format for Ansible content (roles, playbooks, modules)
- Plugins: Extend Ansible’s core functionality
Ansible Execution Flow
- Ansible reads playbook/tasks
- Determines hosts from inventory
- Establishes connections to hosts
- Copies modules to hosts
- Executes modules on hosts
- Returns results to control node
Installation & Configuration
Installation Commands
# Ubuntu/Debian
$ sudo apt update
$ sudo apt install ansible
# RHEL/CentOS
$ sudo yum install epel-release
$ sudo yum install ansible
# Fedora
$ sudo dnf install ansible
# macOS (with Homebrew)
$ brew install ansible
# Python pip (recommended for version control)
$ pip install ansible
Configuration Files (in order of precedence)
ANSIBLE_CONFIG
environment variable./ansible.cfg
(current directory)~/.ansible.cfg
(home directory)/etc/ansible/ansible.cfg
(system-wide)
Key Configuration Settings
[defaults]
inventory = ./inventory
remote_user = ansible
host_key_checking = False
forks = 20
timeout = 30
log_path = /var/log/ansible.log
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
Inventory Management
Inventory File Formats
- INI format (traditional)
- YAML format (more features)
- Dynamic inventory (scripts or plugins)
Static Inventory Example (INI)
# Simple hosts
web1.example.com
web2.example.com
# Groups
[webservers]
web1.example.com
web2.example.com ansible_host=192.168.1.101
[dbservers]
db1.example.com ansible_connection=ssh ansible_user=admin
db2.example.com
# Group of groups
[datacenter:children]
webservers
dbservers
# Group variables
[webservers:vars]
http_port=80
proxy_timeout=5
# Range patterns
web[01:50].example.com
db-[a:f].example.com
Static Inventory Example (YAML)
all:
hosts:
mail.example.com:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
ansible_host: 192.168.1.101
vars:
http_port: 80
dbservers:
hosts:
db1.example.com:
db2.example.com:
datacenter:
children:
webservers:
dbservers:
Inventory Variables
- Host variables: Specific to individual hosts
- Group variables: Applied to all hosts in a group
- Group vars files:
group_vars/group_name.yml
- Host vars files:
host_vars/hostname.yml
Dynamic Inventory
- Scripts or plugins that generate inventory
- Common sources: cloud providers, CMDB, custom scripts
- Usage:
ansible-playbook -i inventory_script.py playbook.yml
Ad-hoc Commands
Basic Syntax
ansible [pattern] -m [module] -a "[module arguments]" [options]
Common Ad-hoc Examples
# Ping all hosts
$ ansible all -m ping
# Run command on hosts
$ ansible webservers -m command -a "uptime"
# Use shell module for complex commands
$ ansible dbservers -m shell -a "ps aux | grep mysql"
# Gather facts
$ ansible web1.example.com -m setup
# File operations
$ ansible all -m file -a "path=/tmp/test state=touch mode=0644"
# Package management
$ ansible webservers -m apt -a "name=nginx state=latest"
$ ansible webservers -m yum -a "name=httpd state=present"
# User management
$ ansible all -b -m user -a "name=deploy shell=/bin/bash groups=sudo"
# Service management
$ ansible all -b -m service -a "name=nginx state=started enabled=yes"
# Copy files
$ ansible webservers -m copy -a "src=/local/path dest=/remote/path mode=0644"
Playbooks
Basic Playbook Structure
---
- name: Webserver configuration playbook
hosts: webservers
become: yes
vars:
http_port: 80
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Start and enable nginx
service:
name: nginx
state: started
enabled: yes
Playbook Execution
# Basic execution
$ ansible-playbook playbook.yml
# Check mode (dry run)
$ ansible-playbook --check playbook.yml
# Limit to specific hosts
$ ansible-playbook playbook.yml --limit web1.example.com
# Pass variables
$ ansible-playbook playbook.yml -e "http_port=8080"
# Start at specific task
$ ansible-playbook playbook.yml --start-at-task="Start and enable nginx"
# Step through execution
$ ansible-playbook playbook.yml --step
Handlers
tasks:
- name: Configure nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
Variables and Facts
# Variable definition in playbooks
vars:
user_name: john
max_connections: 100
# Include variables from files
vars_files:
- vars/users.yml
- vars/common.yml
# Register output as variable
tasks:
- command: whoami
register: current_user
- debug:
msg: "Current user is {{ current_user.stdout }}"
# Using ansible facts
- debug:
msg: "OS is {{ ansible_distribution }} {{ ansible_distribution_version }}"
Conditionals
# When condition
- name: Install Apache on RedHat systems
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
# Multiple conditions
- name: Install memcached if enough memory
apt:
name: memcached
state: present
when: ansible_memtotal_mb > 1024 and ansible_distribution == "Ubuntu"
# Checking registered variables
- name: Check if file exists
stat:
path: /etc/myapp.conf
register: config_file
- name: Create config file if missing
template:
src: myapp.conf.j2
dest: /etc/myapp.conf
when: not config_file.stat.exists
Loops
# Simple loop
- name: Create multiple users
user:
name: "{{ item }}"
state: present
loop:
- john
- alice
- bob
# Loop with dictionary
- name: Create users with specific properties
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell | default('/bin/bash') }}"
loop:
- { name: john, groups: admins }
- { name: alice, groups: developers, shell: /bin/zsh }
- { name: bob, groups: developers }
# Using with_* (legacy but still common)
- name: Mount volumes
mount:
path: "{{ item.path }}"
src: "{{ item.src }}"
fstype: "{{ item.fstype }}"
state: mounted
with_items:
- { path: /mnt/data, src: /dev/sdb1, fstype: ext4 }
- { path: /mnt/backup, src: /dev/sdc1, fstype: xfs }
Error Handling
# Ignore errors
- name: Run potentially failing command
command: /usr/bin/potentially_failing_command
ignore_errors: yes
# Block with error handling
- block:
- name: Update web server configuration
template:
src: web.conf.j2
dest: /etc/httpd/conf.d/web.conf
- name: Restart web server
service:
name: httpd
state: restarted
rescue:
- name: Log failure
shell: "echo 'Web server update failed' > /var/log/ansible_failures.log"
- name: Revert to old configuration
copy:
src: /etc/httpd/conf.d/web.conf.bak
dest: /etc/httpd/conf.d/web.conf
always:
- name: Always send notification
debug:
msg: "Web server configuration task completed"
Tags
# Adding tags to tasks
- name: Install nginx
apt:
name: nginx
state: present
tags:
- packages
- webserver
# Adding tags to includes or roles
- import_tasks: webserver_setup.yml
tags:
- configuration
# Running playbook with specific tags
$ ansible-playbook playbook.yml --tags "configuration,packages"
$ ansible-playbook playbook.yml --skip-tags "notification"
Roles
Role Directory Structure
roles/
rolename/
defaults/ # Default variables
main.yml
files/ # Static files
handlers/ # Handlers
main.yml
meta/ # Role metadata
main.yml
tasks/ # Tasks
main.yml
templates/ # Jinja2 templates
tests/ # Tests
vars/ # Role variables
main.yml
Creating a Role
# Using ansible-galaxy
$ ansible-galaxy init rolename
# Manually
$ mkdir -p roles/rolename/{defaults,files,handlers,meta,tasks,templates,vars}
$ touch roles/rolename/{defaults,handlers,meta,tasks,vars}/main.yml
Using Roles in Playbooks
# Simple role inclusion
- hosts: webservers
roles:
- common
- nginx
- mysql
# Role with parameters
- hosts: webservers
roles:
- role: nginx
vars:
http_port: 8080
max_clients: 200
# Using include_role within tasks
- hosts: all
tasks:
- name: Include database role conditionally
include_role:
name: mysql
when: deploy_database | bool
# Using import_role (static)
- hosts: all
tasks:
- name: Import security role
import_role:
name: security
Role Dependencies
# meta/main.yml
dependencies:
- role: common
vars:
some_parameter: value
- role: apache
vars:
apache_port: 8080
Shared Roles
# Installing roles from Ansible Galaxy
$ ansible-galaxy install geerlingguy.nginx
# Installing roles from GitHub
$ ansible-galaxy install git+https://github.com/username/rolename.git
# Installing multiple roles from a requirements file
$ ansible-galaxy install -r requirements.yml
Templates with Jinja2
Basic Template Example
# nginx.conf.j2
server {
listen {{ http_port | default(80) }};
server_name {{ server_name }};
location / {
root {{ document_root }};
index index.html;
}
}
Jinja2 Conditionals
{% if ansible_distribution == 'Ubuntu' %}
apt_cmd = apt-get
{% elif ansible_distribution == 'CentOS' %}
apt_cmd = yum
{% else %}
apt_cmd = unknown
{% endif %}
Jinja2 Loops
# vhosts.conf.j2
{% for vhost in virtual_hosts %}
server {
listen {{ vhost.port | default(80) }};
server_name {{ vhost.name }};
root {{ vhost.root }};
{% for alias in vhost.aliases | default([]) %}
server_name {{ alias }};
{% endfor %}
access_log {{ vhost.access_log | default('/var/log/nginx/access.log') }};
}
{% endfor %}
Jinja2 Filters
{{ variable | default('default_value') }}
{{ path | basename }}
{{ user_input | regex_replace('^', 'PREFIX_') }}
{{ command_output | from_json }}
{{ list_variable | join(', ') }}
{{ some_text | to_yaml }}
{{ integer_value | string }}
{{ version | version_compare('2.0.0', '>=') }}
Ansible Vault
Managing Sensitive Data
# Create encrypted file
$ ansible-vault create secret.yml
# Edit encrypted file
$ ansible-vault edit secret.yml
# Encrypt existing file
$ ansible-vault encrypt existing.yml
# Decrypt file
$ ansible-vault decrypt secret.yml
# View encrypted file
$ ansible-vault view secret.yml
# Change vault password
$ ansible-vault rekey secret.yml
Using Vault in Playbooks
# Run playbook with vault password prompt
$ ansible-playbook site.yml --ask-vault-pass
# Run playbook with vault password file
$ ansible-playbook site.yml --vault-password-file vault_pass.txt
# Multiple vault passwords
$ ansible-playbook site.yml --vault-id dev@dev_vault_pass.txt --vault-id prod@prod_vault_pass.txt
Encrypting Specific Variables
# In a playbook or vars file
my_secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
31613262633139663834363562656134396661326134356237386434623766653861636566633534
3334666565376564353630313166303830393832326566660a303633636439623539616438653438
65396334623861653431623935383863663962393437373734646234366631666634613731396261
3838366264643230340a323361653737623565633662386564313436613136373261306564376230
3261
Advanced Ansible Features
Task Delegation
- name: Add server to load balancer
shell: /usr/bin/add_to_lb.sh {{ inventory_hostname }}
delegate_to: loadbalancer.example.com
# Running locally
- name: Update local inventory file
template:
src: inventory.j2
dest: /etc/ansible/hosts
delegate_to: localhost
# Run once per batch
- name: Notify that deployment started
mail:
subject: "Deployment started"
to: admin@example.com
body: "Deployment to {{ inventory_hostname }} started"
delegate_to: localhost
run_once: yes
Async Tasks & Polling
# Fire and forget (no polling)
- name: Long running operation
command: /opt/long_script.sh
async: 3600 # 1 hour
poll: 0
# Run with polling
- name: Update packages
apt:
update_cache: yes
upgrade: dist
async: 600 # 10 minutes
poll: 15 # Check every 15 seconds
# Check on async task later
- name: Start backup
command: /usr/local/bin/backup.sh
async: 3600
poll: 0
register: backup_job
- name: Check backup status
async_status:
jid: "{{ backup_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 60
Task Control
# Serial execution (batches)
- hosts: webservers
serial: 2 # Process 2 hosts at a time
tasks:
- name: Update application
command: /opt/app/update.sh
# Percentage-based batches
- hosts: webservers
serial:
- "20%" # First 20%
- "50%" # Next 50%
- "100%" # Remaining hosts
# With throttling
- name: Restart application servers
command: /opt/app/restart.sh
throttle: 3 # Max 3 hosts in parallel
Dynamic Includes
# Conditional includes
- include_tasks: "{{ ansible_distribution }}.yml"
# Include with variables
- include_vars: "{{ ansible_distribution }}_vars.yml"
# Include tasks based on variable
- include_tasks: "tasks/deployment/{{ deployment_type }}.yml"
Custom Modules & Plugins
# Simple custom module (my_module.py)
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=True),
state=dict(type='str', choices=['present', 'absent'], default='present'),
)
)
name = module.params['name']
state = module.params['state']
# Module logic here
module.exit_json(changed=True, name=name, state=state)
if __name__ == '__main__':
main()
Best Practices & Tips
Playbook Organization
- Use roles for code reuse
- Group related tasks
- Use include_/import_ for modularity
- Keep playbooks focused on specific purposes
- Use meaningful names for tasks, plays, and variables
Variable Naming
- Use snake_case for variable names
- Prefix role variables with role name (e.g.,
nginx_port
) - Avoid variable name conflicts across roles
- Document variables in defaults/main.yml
Performance Optimization
- Increase
forks
in ansible.cfg (default: 5) - Use
gather_facts: no
when facts aren’t needed - Use
--limit
to target specific hosts - Use
async
for long-running tasks - Enable SSH pipelining in ansible.cfg
Security Best Practices
- Use Ansible Vault for sensitive data
- Limit access to inventory and playbooks
- Use host_key_checking (disable only in dev/test)
- Use become only when necessary
- Audit playbook runs with –check
Testing & Validation
- Use
--syntax-check
to validate playbooks - Use
--check
mode for dry runs - Create test playbooks for roles
- Use Molecule framework for testing roles
- Validate templates with
--syntax-check
CI/CD Integration
- Store playbooks and roles in version control
- Use CI/CD pipelines to validate and test
- Implement environment-specific inventories
- Use tags for partial deployments
Ansible Environment Variables
ANSIBLE_CONFIG # Configuration file path
ANSIBLE_INVENTORY # Inventory file path
ANSIBLE_ROLES_PATH # Path to roles directory
ANSIBLE_LIBRARY # Path to custom modules
ANSIBLE_VAULT_PASSWORD_FILE # Path to vault password file
ANSIBLE_STDOUT_CALLBACK # Output format (json, yaml, etc.)
ANSIBLE_CALLBACK_PLUGINS # Path to callback plugins
ANSIBLE_STRATEGY # Execution strategy (linear, free, etc.)
ANSIBLE_FORKS # Number of parallel processes
ANSIBLE_HOST_KEY_CHECKING # Enable/disable host key checking
ANSIBLE_SSH_ARGS # Custom SSH arguments
ANSIBLE_BECOME # Enable/disable privilege escalation
ANSIBLE_BECOME_METHOD # Method for privilege escalation
ANSIBLE_BECOME_USER # User for privilege escalation
Common Modules Reference
System Modules
- command: Execute commands (no shell)
- shell: Execute commands in a shell
- raw: Execute low-level commands
- script: Run a local script on remote nodes
- reboot: Reboot a machine
- service: Manage services
- systemd: Manage systemd services
- user: Manage user accounts
- group: Manage groups
- cron: Manage cron jobs
- mount: Control mounts and filesystem mounts
- timezone: Set system timezone
Package Management
- apt: Manage apt packages (Debian/Ubuntu)
- yum: Manage yum packages (RHEL/CentOS)
- dnf: Manage dnf packages (Fedora)
- package: Meta-package manager
- apt_repository: Add/remove apt repositories
- yum_repository: Add/remove yum repositories
- pip: Manage Python packages
Files & Templates
- copy: Copy files to remote locations
- file: Manage files and directories
- fetch: Fetch files from remote nodes
- template: Process Jinja2 templates
- lineinfile: Modify specific lines in files
- replace: Replace text in files
- blockinfile: Insert/update/remove blocks of text
- synchronize: Wrapper around rsync
- unarchive: Extract archives
Cloud & Infrastructure
- docker_container: Manage Docker containers
- docker_image: Manage Docker images
- ec2: Manage EC2 instances
- ec2_group: Manage EC2 security groups
- azure_rm_virtualmachine: Manage Azure VMs
- gcp_compute_instance: Manage GCP instances
- vmware_guest: Manage VMware virtual machines
- k8s: Manage Kubernetes resources
Database
- mysql_db: Manage MySQL databases
- mysql_user: Manage MySQL users
- postgresql_db: Manage PostgreSQL databases
- postgresql_user: Manage PostgreSQL users
- mongodb_user: Manage MongoDB users
Network
- uri: Interact with web services
- get_url: Download files from HTTP/HTTPS/FTP
- nmcli: Manage networking with NetworkManager
- iptables: Manage iptables rules
- firewalld: Manage firewalld
Utility
- debug: Print statements during execution
- assert: Assert given expressions are true
- fail: Force a failure
- pause: Pause playbook execution
- wait_for: Wait for a condition
- set_fact: Set variables (“facts”)
- include_vars: Load variables from files
Resources for Further Learning
Official Documentation
Books
- “Ansible for DevOps” by Jeff Geerling
- “Ansible: Up and Running” by Lorin Hochstein
- “Mastering Ansible” by Jesse Keating
Online Courses & Training
- Red Hat Ansible Automation training
- Udemy: “Ansible for the Absolute Beginner”
- Linux Academy/A Cloud Guru Ansible courses
- PluralSight Ansible path
Community Resources
- Ansible Galaxy – Shared roles and collections
- Ansible Blog
- Ansible GitHub
- Ansible Mailing List
- /r/ansible subreddit
Tools & Extensions
- Ansible Tower/AWX: Web UI and REST API for Ansible
- VSCode Ansible Extension: Syntax highlighting and intellisense
- Molecule: Testing framework for Ansible roles
- ansible-lint: Linting tool for playbooks
- ansible-cmdb: Generate HTML inventory overviews
- ara: Ansible Run Analysis