Ansible Automation: The Ultimate Practical Cheatsheet

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

  1. Ansible reads playbook/tasks
  2. Determines hosts from inventory
  3. Establishes connections to hosts
  4. Copies modules to hosts
  5. Executes modules on hosts
  6. 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)

  1. ANSIBLE_CONFIG environment variable
  2. ./ansible.cfg (current directory)
  3. ~/.ansible.cfg (home directory)
  4. /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

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
Scroll to Top