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.
- 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"
Ansible Configuration
Ansible reads configuration from ansible.cfg in this order:
ANSIBLE_CONFIGenvironment variableansible.cfgin current directory~/.ansible.cfgin home directory/etc/ansible/ansible.cfgsystem-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:
- Read: Ansible reads the playbook and inventory
- Connect: Establishes SSH/WinRM connections to target hosts
- Execute: Copies Python modules to targets and executes them
- Cleanup: Removes temporary files from targets
- Report: Returns results to the control node
- 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)
- 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 / cached set_facts
- Play vars
- Play vars_prompt
- Play vars_files
- Role vars (defined in role/vars/main.yml)
- Block vars (only for tasks in block)
- Task vars (only for the task)
- 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
- SSH Connection Refused: Check firewall, SSH service status, and SSH keys
- Permission Denied: Add
become: yesfor 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
- Enable Pipelining: Reduces SSH connections
- Increase Forks: More parallel execution
- Cache Facts: Don't gather facts repeatedly
- Use async: For long-running tasks
- Minimize Handlers: They run after all tasks
- Use Blocks: Group related tasks
- 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
- Learn about Inventory Management
- Write your first Playbook
- Explore common Modules
- Master Ansible Vault for secrets
- Try the Playground with interactive examples