| name | ansible-expert |
| version | 1.0.0 |
| description | Expert-level Ansible for configuration management, automation, and infrastructure as code |
| category | devops |
| tags | ["ansible","automation","configuration-management","iac","playbooks","roles"] |
| allowed-tools | ["Read","Write","Edit","Bash(ansible:*, ansible-playbook:*, ansible-galaxy:*)"] |
Ansible Expert
Expert guidance for Ansible - configuration management, application deployment, and IT automation using declarative YAML playbooks.
Core Concepts
Ansible Architecture
- Control node (runs Ansible)
- Managed nodes (target systems)
- Inventory (hosts and groups)
- Playbooks (YAML automation scripts)
- Modules (units of work)
- Roles (reusable automation units)
- Plugins (extend functionality)
Key Features
- Agentless (SSH-based)
- Idempotent operations
- Declarative syntax
- Human-readable YAML
- Extensible with modules
- Push-based configuration
- Parallel execution
Use Cases
- Configuration management
- Application deployment
- Provisioning
- Continuous delivery
- Security automation
- Orchestration
Installation
pip install ansible
sudo apt update
sudo apt install ansible
sudo yum install ansible
ansible --version
Inventory
Basic Inventory (INI format)
[webservers]
web1.example.com
web2.example.com ansible_host=192.168.1.10
[databases]
db1.example.com ansible_user=dbadmin
db2.example.com
[production:children]
webservers
databases
[production:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_connection=ssh
YAML Inventory
all:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
ansible_host: 192.168.1.10
databases:
hosts:
db1.example.com:
ansible_user: dbadmin
db2.example.com:
production:
children:
webservers:
databases:
vars:
ansible_python_interpreter: /usr/bin/python3
ansible_connection: ssh
Dynamic Inventory
import json
import boto3
def get_inventory():
ec2 = boto3.client('ec2', region_name='us-east-1')
response = ec2.describe_instances(Filters=[
{'Name': 'instance-state-name', 'Values': ['running']}
])
inventory = {
'_meta': {'hostvars': {}},
'all': {'hosts': []},
'webservers': {'hosts': []},
'databases': {'hosts': []},
}
for reservation in response['Reservations']:
for instance in reservation['Instances']:
ip = instance['PrivateIpAddress']
tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
inventory['all']['hosts'].append(ip)
inventory['_meta']['hostvars'][ip] = {
'ansible_host': ip,
'instance_id': instance['InstanceId'],
'instance_type': instance['InstanceType'],
}
role = tags.get('Role', '')
if role inventory:
inventory[role][].append(ip)
inventory
__name__ == :
(json.dumps(get_inventory(), indent=))
Playbooks
Basic Playbook
---
- name: Configure web servers
hosts: webservers
become: yes
vars:
app_port: 8080
app_user: webapp
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Start and enable nginx
systemd:
name: nginx
state: started
enabled: yes
- name: Copy nginx configuration
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/default
mode: '0644'
notify: Reload nginx
Advanced Playbook
---
- name: Deploy application
hosts: webservers
become: yes
vars:
app_name: myapp
app_version: "{{ version | default('latest') }}"
app_port: 8080
deploy_user: deployer
pre_tasks:
- name: Check if required variables are defined
assert:
that:
- app_name is defined
- app_version is defined
fail_msg: "Required variables are not defined"
tasks:
- name: Create deployment directory
file:
path: "/opt/{{ app_name }}"
state: directory
owner: "{{ deploy_user }}"
Conditionals and Loops
---
- name: Conditional and loop examples
hosts: all
tasks:
- name: Install package (Debian)
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
- redis
when: ansible_os_family == "Debian"
- name: Install package (RedHat)
yum:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
- redis
when: ansible_os_family == "RedHat"
- name: Create
{ , }
{ , }
{ , }
{ , , }
{ , , }
{ , , }
Roles
Role Structure
roles/
└── webserver/
├── defaults/
│ └── main.yml # Default variables
├── files/
│ └── app.conf # Static files
├── handlers/
│ └── main.yml # Handlers
├── meta/
│ └── main.yml # Role metadata and dependencies
├── tasks/
│ └── main.yml # Main task list
├── templates/
│ └── nginx.conf.j2 # Jinja2 templates
├── tests/
│ └── test.yml # Role tests
└── vars/
└── main.yml # Role variables
Example Role
---
nginx_port: 80
nginx_user: www-data
document_root: /var/www/html
---
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Copy nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
notify: Restart nginx
- name: Create document root
file:
path: "{{ document_root }}"
state: directory
owner: "{{ nginx_user }}"
mode: '0755'
- name: Start nginx
systemd:
{{ }}
{
}
{
{
{{ }}
{{ }}
{{ }}
{
}
}
}
Role Dependencies
---
dependencies:
- role: common
- role: nginx
vars:
nginx_port: 8080
- role: postgresql
when: database_enabled | default(false)
Templates (Jinja2)
{# templates/app.conf.j2 #}
# Application configuration for {{ app_name }}
# Generated by Ansible on {{ ansible_date_time.iso8601 }}
[server]
host = {{ ansible_default_ipv4.address }}
port = {{ app_port }}
workers = {{ ansible_processor_vcpus }}
[database]
host = {{ db_host }}
port = {{ db_port }}
name = {{ db_name }}
user = {{ db_user }}
password = {{ db_password }}
[cache]
enabled = {{ cache_enabled | default(true) | lower }}
{% if cache_enabled | default(true) %}
backend = redis
redis_host = {{ redis_host }}
redis_port = {{ redis_port }}
{% endif %}
[features]
{% for feature, enabled in features.items() %}
{{ feature }} = {{ enabled | lower }}
{% endfor %}
{% if environment == 'production' %}
[production]
debug = false
log_level = warning
{% else %}
[development]
debug = true
log_level = debug
{% endif %}
Variables and Facts
Variable Precedence (low to high)
- Role defaults
- Inventory file/script group vars
- Inventory group_vars/all
- Playbook group_vars/all
- Inventory group_vars/*
- Playbook group_vars/*
- Inventory file/script host vars
- Inventory host_vars/*
- Playbook host_vars/*
- Host facts
- Play vars
- Play vars_prompt
- Play vars_files
- Role vars
- Block vars
- Task vars
- Extra vars (-e flag)
Using Variables
---
- name: Variable examples
hosts: all
vars:
app_name: myapp
app_version: 1.0.0
vars_files:
- vars/common.yml
- "vars/{{ environment }}.yml"
tasks:
- name: Load variables from file
include_vars:
file: "vars/{{ ansible_distribution }}.yml"
- name: Set fact
set_fact:
full_app_name: "{{ app_name }}-{{ app_version }}"
- name: Register output
command: hostname
register: hostname_output
- name: Use registered variable
debug:
msg: "Hostname is {{ hostname_output.stdout }}"
Error Handling
---
- name: Error handling examples
hosts: all
tasks:
- name: Task that might fail
command: /bin/false
ignore_errors: yes
- name: Task with custom error handling
block:
- name: Try to start service
systemd:
name: myapp
state: started
rescue:
- name: Log error
debug:
msg: "Failed to start myapp"
- name: Try alternative
systemd:
name: myapp-fallback
state: started
always:
-
[, ]
Ansible Vault
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-vault encrypt vars/production.yml
ansible-vault decrypt vars/production.yml
ansible-vault view secrets.yml
ansible-vault rekey secrets.yml
---
db_password: supersecret
api_key: abc123xyz
ssl_key: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
---
- name: Deploy with secrets
hosts: production
vars_files:
- secrets.yml
tasks:
- name: Configure database
template:
src: db.conf.j2
dest: /etc/db.conf
no_log: yes
ansible-playbook playbook.yml --ask-vault-pass
ansible-playbook playbook.yml --vault-password-file ~/.vault_pass
ansible-playbook playbook.yml --vault-id prod@prompt --vault-id dev@~/.vault_dev
Best Practices
Playbook Organization
ansible-project/
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ └── staging/
│ ├── hosts.yml
│ └── group_vars/
├── playbooks/
│ ├── site.yml
│ ├── webservers.yml
│ └── databases.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ └── postgresql/
├── group_vars/
│ ├── all.yml
│ └── webservers.yml
├── host_vars/
└── files/
Idempotency
- name: Add line to file
shell: echo "server {{ ansible_hostname }}" >> /etc/hosts
- name: Add line to file
lineinfile:
path: /etc/hosts
line: "server {{ ansible_hostname }}"
state: present
Performance
- Use
strategy: free for faster execution
- Enable pipelining in ansible.cfg
- Use
async for long-running tasks
- Disable fact gathering when not needed
- Use
serial for rolling updates
[defaults]
forks = 20
host_key_checking = False
pipelining = True
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
---
- name: Fast deployment
hosts: webservers
strategy: free
gather_facts: no
serial: 5
tasks:
- name: Long running task
command: /usr/local/bin/build.sh
async: 3600
poll: 0
register: build_job
- name: Check build
Security
- Use Ansible Vault for secrets
- Use
no_log: yes for sensitive tasks
- Set proper file permissions
- Use
become sparingly
- Validate SSL certificates
- Use SSH keys, not passwords
Testing
Molecule (Role Testing)
pip install molecule molecule-docker
cd roles/myapp
molecule init scenario
molecule test
molecule create
molecule converge
molecule verify
molecule destroy
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-ubuntu2004-ansible
pre_build_image: yes
provisioner:
name: ansible
verifier:
name: ansible
Anti-Patterns to Avoid
❌ Not using roles: Organize code in reusable roles
❌ Shell commands everywhere: Use modules when available
❌ Hardcoded values: Use variables
❌ No error handling: Use blocks, rescue, always
❌ Storing secrets in plaintext: Use Ansible Vault
❌ Not testing: Use molecule for role testing
❌ Ignoring idempotency: Tasks should be safe to run multiple times
❌ Complex playbooks: Break into smaller, focused playbooks
Resources