Ansible Basics

What is Ansible?

Ansible is an open-source automation tool that simplifies configuration management, application deployment, and task automation. It uses a simple, human-readable language (YAML) and doesn't require agents on managed nodes.

Key Benefits:
  • Agentless: No software needed on managed nodes (SSH for Linux, WinRM for Windows)
  • Simple: Uses YAML syntax that's easy to read and write
  • Powerful: Manages complex multi-tier deployments
  • Idempotent: Running the same playbook multiple times produces the same result

Core Concepts

1. Control Node

The machine where Ansible is installed and from where you run your commands and playbooks.

2. Managed Nodes

The target systems that Ansible manages. Also called "hosts".

3. Inventory

A list of managed nodes. Can be static (INI or YAML files) or dynamic (scripts that query cloud providers).

4. Modules

Units of code that Ansible executes. Each module has a specific purpose (e.g., managing packages, files, services).

5. Tasks

Units of action in Ansible. Each task calls a module.

6. Playbooks

YAML files containing a series of tasks to execute on managed nodes.

Installation

Linux (Ubuntu/Debian)

sudo apt update
sudo apt install ansible -y

Linux (RHEL/CentOS)

sudo yum install epel-release -y
sudo yum install ansible -y

macOS

brew install ansible

Python pip

pip install ansible

Your First Command

The simplest Ansible command is the "ping" module to test connectivity:

ansible localhost -m ping

Expected output:

localhost | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Ad-Hoc Commands

Ad-hoc commands are one-liners for quick tasks:

# Check disk space
ansible localhost -m shell -a "df -h"

# Get system information
ansible localhost -m setup

# Create a directory
ansible localhost -m file -a "path=/tmp/test state=directory"
Pro Tip: While ad-hoc commands are useful for quick tasks, playbooks are preferred for complex operations and ensure reproducibility.

Ansible Configuration

Ansible reads configuration from ansible.cfg in this order:

  1. ANSIBLE_CONFIG environment variable
  2. ansible.cfg in current directory
  3. ~/.ansible.cfg in home directory
  4. /etc/ansible/ansible.cfg system-wide

Example ansible.cfg:

[defaults]
inventory = ./inventory
host_key_checking = False
retry_files_enabled = False

Ansible Architecture Deep Dive

How Ansible Works

Ansible operates in a push-based model:

  1. Read: Ansible reads the playbook and inventory
  2. Connect: Establishes SSH/WinRM connections to target hosts
  3. Execute: Copies Python modules to targets and executes them
  4. Cleanup: Removes temporary files from targets
  5. Report: Returns results to the control node
Connection Methods:
  • SSH: Default for Linux/Unix systems (uses paramiko or OpenSSH)
  • WinRM: For Windows systems (requires pywinrm)
  • Local: For the control node itself
  • Docker: For Docker containers
  • Network: For network devices (uses CLI or API)

Variables and Facts

Variables

Variables store values that can be reused throughout playbooks:

# In playbook
vars:
  app_name: myapp
  app_port: 8080

tasks:
  - name: Install {{ app_name }}
    apt:
      name: "{{ app_name }}"
      state: present

  - name: Configure port
    lineinfile:
      path: /etc/myapp/config
      line: "port={{ app_port }}"

Variable Precedence (Lowest to Highest)

  1. Role defaults
  2. Inventory file/script group vars
  3. Inventory group_vars/all
  4. Playbook group_vars/all
  5. Inventory group_vars/*
  6. Playbook group_vars/*
  7. Inventory file/script host vars
  8. Inventory host_vars/*
  9. Playbook host_vars/*
  10. Host facts / cached set_facts
  11. Play vars
  12. Play vars_prompt
  13. Play vars_files
  14. Role vars (defined in role/vars/main.yml)
  15. Block vars (only for tasks in block)
  16. Task vars (only for the task)
  17. Extra vars (-e in command line) - Always wins!

Facts

Facts are system information gathered automatically by Ansible:

# Gather facts (automatic by default)
- name: Display system facts
  debug:
    msg: |
      OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
      Hostname: {{ ansible_hostname }}
      IP: {{ ansible_default_ipv4.address }}
      CPU Cores: {{ ansible_processor_vcpus }}
      Memory: {{ ansible_memtotal_mb }}MB
      Python: {{ ansible_python_version }}

Disable fact gathering for faster execution:

- hosts: all
  gather_facts: no
  tasks:
    - name: Only gather specific facts
      setup:
        filter: ansible_distribution*

Debugging Techniques

1. Debug Module

- name: Print variable
  debug:
    var: my_variable

- name: Print message
  debug:
    msg: "Value is {{ my_variable }}"

- name: Conditional debugging
  debug:
    msg: "This only shows in verbose mode"
    verbosity: 1

2. Verbosity Levels

# No verbosity
ansible-playbook playbook.yml

# Basic verbosity (-v)
ansible-playbook playbook.yml -v

# More details (-vv)
ansible-playbook playbook.yml -vv

# Connections debugging (-vvv)
ansible-playbook playbook.yml -vvv

# Full SSH debugging (-vvvv)
ansible-playbook playbook.yml -vvvv

3. Check Mode (Dry Run)

# Test without making changes
ansible-playbook playbook.yml --check

# See differences
ansible-playbook playbook.yml --check --diff

4. Step-by-Step Execution

# Confirm each task before execution
ansible-playbook playbook.yml --step

Common Patterns and Idioms

1. Checking if a File Exists

- name: Check if file exists
  stat:
    path: /etc/myapp/config.yml
  register: config_file

- name: Create config if missing
  template:
    src: config.yml.j2
    dest: /etc/myapp/config.yml
  when: not config_file.stat.exists

2. Running Tasks Based on OS

- name: Install Apache (Debian)
  apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

- name: Install Apache (RedHat)
  yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

3. Looping Through Items

- name: Install multiple packages
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - mysql-server
    - php-fpm

- name: Create users with properties
  user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
    state: present
  loop:
    - { name: 'alice', uid: 1001 }
    - { name: 'bob', uid: 1002 }

4. Handling Failures

- name: Try to start service
  systemd:
    name: myapp
    state: started
  ignore_errors: yes

- name: Always run cleanup
  file:
    path: /tmp/install
    state: absent
  when: ansible_failed_task is defined

- name: Fail with custom message
  fail:
    msg: "Required variable 'db_password' is not defined"
  when: db_password is not defined

Ansible Configuration Best Practices

Recommended ansible.cfg

[defaults]
# Inventory location
inventory = ./inventory

# Don't create .retry files
retry_files_enabled = False

# Host key checking (disable for dynamic envs)
host_key_checking = False

# Timeout for connections
timeout = 30

# Number of parallel processes
forks = 20

# Gathering facts behavior
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600

# Output formatting
stdout_callback = yaml
bin_ansible_callbacks = True

# Python interpreter discovery
interpreter_python = auto_silent

# Privilege escalation
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

# SSH settings
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

Environment-Specific Configurations

Development

[defaults]
host_key_checking = False
gathering = explicit
forks = 5

Production

[defaults]
host_key_checking = True
gathering = smart
fact_caching = redis
forks = 50
callback_whitelist = timer, profile_tasks

Troubleshooting Common Issues

Common Problems and Solutions:
  • SSH Connection Refused: Check firewall, SSH service status, and SSH keys
  • Permission Denied: Add become: yes for sudo privileges
  • Module Not Found: Install required Python libraries (pip install module-name)
  • Slow Playbooks: Enable SSH pipelining, increase forks, cache facts
  • Variable Not Defined: Use default() filter: fallback

Testing Connectivity

# Test all hosts
ansible all -m ping

# Test specific group
ansible webservers -m ping

# Test with sudo
ansible all -m ping --become

# Test with specific user
ansible all -m ping -u admin

# Test with password prompt
ansible all -m ping --ask-pass --ask-become-pass

Quick Reference Commands

# List all hosts in inventory
ansible all --list-hosts

# Check inventory
ansible-inventory --list
ansible-inventory --graph

# View host variables
ansible -m debug -a "var=hostvars[inventory_hostname]" hostname

# Syntax check
ansible-playbook playbook.yml --syntax-check

# List tags
ansible-playbook playbook.yml --list-tags

# Run specific tags
ansible-playbook playbook.yml --tags "configuration,deploy"

# Skip tags
ansible-playbook playbook.yml --skip-tags "testing"

# Limit to specific hosts
ansible-playbook playbook.yml --limit "web01,web02"

Performance Tips

  1. Enable Pipelining: Reduces SSH connections
  2. Increase Forks: More parallel execution
  3. Cache Facts: Don't gather facts repeatedly
  4. Use async: For long-running tasks
  5. Minimize Handlers: They run after all tasks
  6. Use Blocks: Group related tasks
  7. Disable Cowsay: export ANSIBLE_NOCOWS=1
# Async example
- name: Long running task
  shell: /usr/bin/long_task.sh
  async: 3600  # Maximum runtime
  poll: 0      # Fire and forget
  register: long_task

- name: Check on async task
  async_status:
    jid: "{{ long_task.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 100
  delay: 10

Next Steps