Ansible Automation: Complete Configuration Management Guide
· Pavan Y N · ansible, automation, configuration-management, devops
Published: October 30, 2025
Reading Time: 24 minutes
Tags: Ansible, Automation, Configuration Management, DevOps, IaC
# Introduction
Ansible is a powerful open-source automation tool that simplifies configuration management, application deployment, and task automation. With its agentless architecture and simple YAML syntax, Ansible has become the go-to choice for IT automation.
This comprehensive guide covers Ansible fundamentals, playbook development, best practices, and real-world automation scenarios.
# What is Ansible?
Ansible is an agentless automation platform that uses SSH to configure systems, deploy applications, and orchestrate complex workflows.
## Key Features
- →Agentless: No software to install on managed nodes
- →Simple: YAML-based playbooks, easy to read and write
- →Powerful: Manage thousands of nodes simultaneously
- →Idempotent: Safe to run multiple times
- →Extensible: 3000+ modules and growing
## Ansible Architecture
┌──────────────────┐
│ Control Node │ (Where Ansible runs)
│ - Ansible CLI │
│ - Playbooks │
│ - Inventory │
└────────┬─────────┘
│
│ SSH/WinRM
│
┌────┴────┬────────┬────────┐
│ │ │ │
┌───▼───┐ ┌──▼───┐ ┌──▼───┐ ┌──▼───┐
│ Node 1│ │Node 2│ │Node 3│ │Node N│
│(Linux)│ │(Linux)│ │(Win) │ │(...) │
└───────┘ └──────┘ └──────┘ └──────┘
# Installation and Setup
## Installing Ansible
# Ubuntu/Debian
sudo apt update
sudo apt install ansible
# RHEL/CentOS
sudo yum install ansible
# macOS
brew install ansible
# Python pip
pip install ansible
# Verify installation
ansible --version
## Initial Configuration
ansible.cfg:
[defaults]
inventory = ./inventory
remote_user = ansible
private_key_file = ~/.ssh/ansible_key
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
## Inventory Management
Static Inventory (inventory/hosts.ini):
[webservers]
web1.example.com ansible_host=192.168.1.10
web2.example.com ansible_host=192.168.1.11
web3.example.com ansible_host=192.168.1.12
[databases]
db1.example.com ansible_host=192.168.1.20
db2.example.com ansible_host=192.168.1.21
[loadbalancers]
lb1.example.com ansible_host=192.168.1.30
[production:children]
webservers
databases
loadbalancers
[production:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
environment=production
Dynamic Inventory (AWS):
# aws_ec2.yml
plugin: aws_ec2
regions:
- us-east-1
- us-west-2
filters:
tag:Environment: production
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
- key: tags.Environment
prefix: env
- key: placement.availability_zone
prefix: az
hostnames:
- tag:Name
- private-ip-address
compose:
ansible_host: public_ip_address
# Test dynamic inventory
ansible-inventory -i aws_ec2.yml --list
ansible-inventory -i aws_ec2.yml --graph
# Ansible Playbooks
## Basic Playbook Structure
---
- name: Configure web servers
hosts: webservers
become: yes
vars:
app_name: myapp
app_port: 8080
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required packages
apt:
name:
- nginx
- python3-pip
- git
state: present
- name: Create application directory
file:
path: /opt/{{ app_name }}
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Copy application files
copy:
src: files/app/
dest: /opt/{{ app_name }}/
owner: www-data
group: www-data
mode: '0644'
notify: Restart nginx
- name: Configure nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: Restart nginx
- name: Enable nginx site
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: Restart nginx
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
## Advanced Playbook Patterns
1. Roles:
roles/
└── webserver/
├── tasks/
│ ├── main.yml
│ ├── install.yml
│ └── configure.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
├── files/
│ └── index.html
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
├── meta/
│ └── main.yml
└── README.md
roles/webserver/tasks/main.yml:
---
- name: Include OS-specific variables
include_vars: "{{ ansible_os_family }}.yml"
- name: Install packages
include_tasks: install.yml
- name: Configure web server
include_tasks: configure.yml
- name: Ensure web server is running
service:
name: "{{ web_server_service }}"
state: started
enabled: yes
Using Roles:
---
- name: Deploy web application
hosts: webservers
roles:
- role: common
tags: common
- role: webserver
tags: webserver
- role: monitoring
tags: monitoring
2. Conditionals:
---
- name: Install packages based on OS
hosts: all
tasks:
- name: Install Apache on Debian
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Install Apache on RedHat
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: Configure firewall for production
firewalld:
service: http
permanent: yes
state: enabled
when:
- ansible_os_family == "RedHat"
- environment == "production"
- name: Install development tools
apt:
name:
- build-essential
- gdb
state: present
when: environment != "production"
3. Loops:
---
- name: Create multiple users
hosts: all
vars:
users:
- name: alice
uid: 1001
groups: ['developers', 'docker']
- name: bob
uid: 1002
groups: ['developers']
- name: charlie
uid: 1003
groups: ['ops', 'docker']
tasks:
- name: Create user groups
group:
name: "{{ item }}"
state: present
loop:
- developers
- ops
- docker
- name: Create users
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
shell: /bin/bash
create_home: yes
loop: "{{ users }}"
- name: Add SSH keys
authorized_key:
user: "{{ item.name }}"
key: "{{ lookup('file', 'files/ssh_keys/' + item.name + '.pub') }}"
loop: "{{ users }}"
4. Blocks and Error Handling:
---
- name: Deploy application with error handling
hosts: webservers
tasks:
- name: Deployment block
block:
- name: Stop application
service:
name: myapp
state: stopped
- name: Backup current version
archive:
path: /opt/myapp
dest: /backup/myapp-{{ ansible_date_time.iso8601 }}.tar.gz
- name: Deploy new version
unarchive:
src: files/myapp-v2.0.tar.gz
dest: /opt/myapp
- name: Run database migrations
command: /opt/myapp/migrate.sh
- name: Start application
service:
name: myapp
state: started
rescue:
- name: Restore from backup
unarchive:
src: "{{ backup_file }}"
dest: /opt/myapp
- name: Start application
service:
name: myapp
state: started
- name: Send failure notification
mail:
to: ops@example.com
subject: "Deployment failed on {{ inventory_hostname }}"
body: "Deployment failed and was rolled back"
always:
- name: Clean up temporary files
file:
path: /tmp/deployment
state: absent
5. Templates (Jinja2):
templates/nginx.conf.j2:
upstream {{ app_name }}_backend {
{% for host in groups['webservers'] %}
server {{ hostvars[host]['ansible_default_ipv4']['address'] }}:{{ app_port }};
{% endfor %}
}
server {
listen 80;
server_name {{ server_name }};
{% if ssl_enabled %}
listen 443 ssl http2;
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
{% endif %}
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
{% if environment == 'production' %}
# Production-specific configuration
client_max_body_size 100M;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
{% endif %}
}
# Ansible Modules
## Common Modules
1. System Modules:
# User management
- name: Create user
user:
name: deploy
shell: /bin/bash
groups: sudo
append: yes
# Service management
- name: Manage service
service:
name: nginx
state: started
enabled: yes
# Cron jobs
- name: Add cron job
cron:
name: "Backup database"
minute: "0"
hour: "2"
job: "/usr/local/bin/backup.sh"
2. Package Management:
# APT (Debian/Ubuntu)
- name: Install packages
apt:
name:
- nginx
- postgresql
state: present
update_cache: yes
# YUM (RHEL/CentOS)
- name: Install packages
yum:
name:
- httpd
- mariadb-server
state: present
# Pip
- name: Install Python packages
pip:
name:
- django
- celery
virtualenv: /opt/myapp/venv
3. File Operations:
# Copy files
- name: Copy configuration
copy:
src: files/config.yml
dest: /etc/myapp/config.yml
owner: root
group: root
mode: '0644'
backup: yes
# Template files
- name: Generate config from template
template:
src: templates/app.conf.j2
dest: /etc/myapp/app.conf
# Create directories
- name: Create directory structure
file:
path: /opt/myapp/{{ item }}
state: directory
owner: www-data
mode: '0755'
loop:
- logs
- data
- tmp
# Download files
- name: Download file
get_url:
url: https://example.com/file.tar.gz
dest: /tmp/file.tar.gz
checksum: sha256:abc123...
4. Cloud Modules:
# AWS EC2
- name: Launch EC2 instance
ec2_instance:
name: web-server
key_name: my-key
instance_type: t3.micro
image_id: ami-0c55b159cbfafe1f0
region: us-east-1
vpc_subnet_id: subnet-12345678
security_group: web-sg
tags:
Environment: production
Role: webserver
# AWS S3
- name: Upload to S3
aws_s3:
bucket: my-bucket
object: /backups/db-backup.sql
src: /tmp/db-backup.sql
mode: put
encrypt: yes
# Azure VM
- name: Create Azure VM
azure_rm_virtualmachine:
resource_group: myResourceGroup
name: myVM
vm_size: Standard_B2s
admin_username: azureuser
ssh_password_enabled: false
ssh_public_keys:
- path: /home/azureuser/.ssh/authorized_keys
key_data: "{{ ssh_public_key }}"
5. Docker Modules:
# Docker container
- name: Run Docker container
docker_container:
name: myapp
image: myregistry.azurecr.io/myapp:latest
state: started
restart_policy: always
ports:
- "8080:8080"
env:
DATABASE_URL: "postgresql://db:5432/myapp"
REDIS_URL: "redis://redis:6379"
volumes:
- /opt/myapp/data:/data
networks:
- name: app-network
# Docker image
- name: Build Docker image
docker_image:
name: myapp
tag: latest
source: build
build:
path: /opt/myapp
dockerfile: Dockerfile
push: yes
repository: myregistry.azurecr.io/myapp
# Ansible Vault
## Encrypting Sensitive Data
# Create encrypted file
ansible-vault create secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# Encrypt existing file
ansible-vault encrypt vars/production.yml
# Decrypt file
ansible-vault decrypt vars/production.yml
# View encrypted file
ansible-vault view secrets.yml
# Change vault password
ansible-vault rekey secrets.yml
Using Vault in Playbooks:
---
- name: Deploy with secrets
hosts: webservers
vars_files:
- vars/common.yml
- vars/secrets.yml # Encrypted file
tasks:
- name: Configure database connection
template:
src: templates/database.yml.j2
dest: /etc/myapp/database.yml
no_log: true # Don't log sensitive data
Running with Vault:
# Prompt for password
ansible-playbook deploy.yml --ask-vault-pass
# Use password file
ansible-playbook deploy.yml --vault-password-file ~/.vault_pass
# Use multiple vault IDs
ansible-playbook deploy.yml --vault-id prod@~/.vault_pass_prod
# STAR Approach: Ansible Implementation
Situation: Company managing 500+ servers manually, taking 2-3 days for configuration changes and frequent inconsistencies.
Task: Implement Ansible to automate configuration management, reduce deployment time, and ensure consistency.
Action:
- •
Infrastructure Audit:
- →Documented all server configurations
- →Identified common patterns and variations
- →Created inventory of all systems
- •
Ansible Setup:
- →Installed Ansible on control nodes
- →Configured SSH key-based authentication
- →Set up dynamic inventory for AWS and Azure
- →Implemented Ansible Tower for GUI and RBAC
- •
Playbook Development:
- →Created 50+ reusable roles
- →Implemented standardized server baseline
- →Developed application deployment playbooks
- →Added comprehensive error handling
- •
Security Implementation:
- →Encrypted all sensitive data with Ansible Vault
- →Implemented role-based access control
- →Added audit logging for all changes
- →Integrated with HashiCorp Vault
- •
CI/CD Integration:
- →Automated playbook testing with Molecule
- →Integrated with Jenkins for automated deployments
- →Implemented GitOps workflow
- →Added Slack notifications
Result:
- →Configuration deployment time reduced from days to 15 minutes
- →Configuration drift eliminated (100% compliance)
- →Server provisioning automated (30 minutes vs 2 days)
- →Reduced configuration errors by 95%
- →Enabled self-service for developers
- →Improved disaster recovery time by 80%
# Common Issues & Troubleshooting
## Issue 1: SSH Connection Failures
Symptoms:
- →"Permission denied" errors
- →Connection timeouts
Diagnosis:
# Test SSH connection
ansible all -m ping -vvv
# Check SSH configuration
ssh -vvv user@host
Solutions:
# ansible.cfg
[defaults]
host_key_checking = False
timeout = 30
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
## Issue 2: Privilege Escalation Failures
Symptoms:
- →"sudo: a password is required" errors
- →Permission denied on system operations
Solutions:
# In playbook
- name: Task requiring sudo
apt:
name: nginx
state: present
become: yes
become_method: sudo
become_user: root
# Run with password prompt
ansible-playbook deploy.yml --ask-become-pass
## Issue 3: Idempotency Issues
Symptoms:
- →Tasks always report "changed"
- →Unnecessary operations on each run
Solutions:
# Use creates parameter
- name: Download file
get_url:
url: https://example.com/file.tar.gz
dest: /tmp/file.tar.gz
args:
creates: /tmp/file.tar.gz
# Check before action
- name: Check if service exists
stat:
path: /etc/systemd/system/myapp.service
register: service_file
- name: Install service
template:
src: myapp.service.j2
dest: /etc/systemd/system/myapp.service
when: not service_file.stat.exists
# Best Practices
## 1. Playbook Organization
- →Use roles for reusability
- →Keep playbooks simple and focused
- →Use meaningful names
- →Document complex logic
## 2. Variables
- →Use group_vars and host_vars
- →Encrypt sensitive data with Vault
- →Use defaults for optional variables
- →Validate variable types
## 3. Idempotency
- →Design tasks to be idempotent
- →Use appropriate modules
- →Test playbooks multiple times
- →Avoid shell/command when possible
## 4. Error Handling
- →Use blocks for error handling
- →Implement rescue and always
- →Add meaningful error messages
- →Log important operations
## 5. Testing
- →Test in non-production first
- →Use check mode (--check)
- →Implement automated testing
- →Validate with Molecule
# Conclusion
Ansible has transformed configuration management and automation by providing a simple yet powerful platform that doesn't require agents. Its declarative approach and extensive module library make it ideal for managing infrastructure at any scale.
Whether automating server configuration, deploying applications, or orchestrating complex workflows, Ansible provides the tools needed to build reliable, repeatable automation.
# Additional Resources
Ready to automate with Ansible? Start with simple tasks and gradually build more complex playbooks as you learn!