Ansible Automation

Automate configuration management, application deployment, and IT orchestration with agentless automation.

What is Ansible?

Beginner

Ansible is an open-source automation tool by Red Hat that automates configuration management, application deployment, and orchestration. It's agentless — it connects to targets via SSH and requires no software installed on managed nodes.

Ansible vs Terraform

Terraform is best for provisioning infrastructure (creating servers, networks, etc.). Ansible is best for configuring and managing software on those servers. They complement each other perfectly.

Key Features

Installation

Beginner
# Install on Ubuntu/Debian
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible

# Or install via pip
pip install ansible

# Verify
ansible --version

# Test connectivity to a host
ansible all -m ping -i "webserver1.example.com,"

Inventory

Beginner

The inventory file defines the hosts and groups Ansible manages.

# inventory/hosts.ini
[webservers]
web1.example.com ansible_host=10.0.1.10
web2.example.com ansible_host=10.0.1.11

[databases]
db1.example.com ansible_host=10.0.2.10
db2.example.com ansible_host=10.0.2.11

[loadbalancers]
lb1.example.com ansible_host=10.0.0.10

# Group of groups
[production:children]
webservers
databases
loadbalancers

# Group variables
[webservers:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/prod-key.pem
http_port=80
# YAML format inventory (inventory/hosts.yml)
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_host: 10.0.1.10
        web2.example.com:
          ansible_host: 10.0.1.11
      vars:
        ansible_user: ubuntu
        http_port: 80
    databases:
      hosts:
        db1.example.com:
          ansible_host: 10.0.2.10

Ad-hoc Commands

Beginner
# Ping all hosts
ansible all -m ping -i inventory/hosts.ini

# Run a command on webservers
ansible webservers -m shell -a "uptime" -i inventory/hosts.ini

# Install a package
ansible webservers -m apt -a "name=nginx state=present" --become -i inventory/hosts.ini

# Copy a file
ansible webservers -m copy -a "src=./index.html dest=/var/www/html/index.html" --become

# Gather facts about hosts
ansible webservers -m setup -i inventory/hosts.ini

# Restart a service
ansible webservers -m service -a "name=nginx state=restarted" --become

Playbooks

Beginner

Playbooks are YAML files that define a series of tasks to execute on hosts.

# playbooks/setup-webserver.yml
---
- name: Setup Web Server
  hosts: webservers
  become: yes
  vars:
    app_name: myapp
    app_port: 3000

  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Install required packages
      apt:
        name:
          - nginx
          - curl
          - unzip
        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: ../app/
        dest: /opt/{{ app_name }}/
        owner: www-data
        group: www-data

    - name: Configure Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
        mode: '0644'
      notify: Reload Nginx

    - name: Enable Nginx site
      file:
        src: /etc/nginx/sites-available/{{ app_name }}
        dest: /etc/nginx/sites-enabled/{{ app_name }}
        state: link
      notify: Reload Nginx

    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes

  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded
# Run the playbook
ansible-playbook playbooks/setup-webserver.yml -i inventory/hosts.ini

# Dry run (check mode)
ansible-playbook playbooks/setup-webserver.yml --check

# Limit to specific hosts
ansible-playbook playbooks/setup-webserver.yml --limit web1.example.com

# With extra variables
ansible-playbook playbooks/setup-webserver.yml -e "app_port=8080"

Variables & Facts

Intermediate

Variables can be defined at multiple levels with a clear precedence order.

# group_vars/webservers.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
app_environment: production
ssl_enabled: true

# host_vars/web1.example.com.yml
---
nginx_worker_processes: 4
custom_config: true
# Using variables and conditionals in tasks
- name: Install SSL certificate
  copy:
    src: "{{ ssl_cert_path }}"
    dest: /etc/ssl/certs/app.crt
  when: ssl_enabled | bool

- name: Configure based on OS
  template:
    src: "{{ item }}"
    dest: /etc/myapp/config.conf
  with_first_found:
    - "templates/{{ ansible_distribution }}.conf.j2"
    - "templates/default.conf.j2"

# Using Ansible facts
- name: Show system info
  debug:
    msg: |
      Hostname: {{ ansible_hostname }}
      OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
      IP: {{ ansible_default_ipv4.address }}
      Memory: {{ ansible_memtotal_mb }} MB
      CPUs: {{ ansible_processor_vcpus }}

Handlers & Templates

Intermediate

Jinja2 Templates

# templates/nginx.conf.j2
server {
    listen {{ http_port | default(80) }};
    server_name {{ ansible_fqdn }};

{% if ssl_enabled %}
    listen 443 ssl;
    ssl_certificate /etc/ssl/certs/{{ app_name }}.crt;
    ssl_certificate_key /etc/ssl/private/{{ app_name }}.key;
{% endif %}

    location / {
        proxy_pass http://127.0.0.1:{{ app_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
{% for header in extra_headers | default([]) %}
        proxy_set_header {{ header.name }} {{ header.value }};
{% endfor %}
    }

    access_log /var/log/nginx/{{ app_name }}_access.log;
    error_log /var/log/nginx/{{ app_name }}_error.log;
}

Roles

Intermediate

Roles organize playbooks into reusable, shareable components.

# Create a role scaffold
ansible-galaxy role init roles/webserver

# Role structure:
# roles/webserver/
# ├── defaults/main.yml      # Default variables (lowest precedence)
# ├── files/                  # Static files to copy
# ├── handlers/main.yml       # Handler definitions
# ├── meta/main.yml           # Role metadata & dependencies
# ├── tasks/main.yml          # Main task list
# ├── templates/              # Jinja2 templates
# ├── tests/                  # Tests
# └── vars/main.yml           # Variables (high precedence)
# roles/webserver/tasks/main.yml
---
- name: Install Nginx
  apt:
    name: nginx
    state: present
  tags: [install]

- name: Deploy configuration
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: nginx -t -c %s
  notify: Restart Nginx
  tags: [config]

- name: Ensure service is running
  service:
    name: nginx
    state: started
    enabled: yes
  tags: [service]
# Using roles in a playbook
---
- name: Configure Production Stack
  hosts: all
  become: yes

  roles:
    - role: common
    - role: webserver
      vars:
        http_port: 80
        ssl_enabled: true
    - role: monitoring-agent
      when: enable_monitoring | default(true)

Ansible Vault

Intermediate

Ansible Vault encrypts sensitive data like passwords, API keys, and certificates.

# Create an encrypted file
ansible-vault create secrets.yml

# Encrypt an existing file
ansible-vault encrypt group_vars/production/secrets.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# Decrypt a file
ansible-vault decrypt secrets.yml

# Run playbook with vault password
ansible-playbook site.yml --ask-vault-pass

# Or use a password file
ansible-playbook site.yml --vault-password-file .vault_pass

# Encrypt a single string
ansible-vault encrypt_string 'my_secret_password' --name 'db_password'
# Using encrypted variables in playbooks
# group_vars/production/secrets.yml (encrypted)
---
db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  616362383330...
api_key: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  336139653833...

Ansible Galaxy

Advanced
# Install roles from Galaxy
ansible-galaxy install geerlingguy.docker
ansible-galaxy install geerlingguy.nginx

# Install from requirements file
# requirements.yml
---
roles:
  - name: geerlingguy.docker
    version: 7.1.0
  - name: geerlingguy.nginx
    version: 3.2.0

collections:
  - name: amazon.aws
    version: 7.0.0
  - name: community.docker
    version: 3.4.0

# Install all requirements
ansible-galaxy install -r requirements.yml
ansible-galaxy collection install -r requirements.yml

Dynamic Inventory

Advanced

Dynamic inventory automatically discovers hosts from cloud providers or other sources.

# AWS EC2 dynamic inventory plugin
# inventory/aws_ec2.yml
---
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
  - us-west-2

filters:
  tag:Environment:
    - production
    - staging
  instance-state-name: running

keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: tags.Role
    prefix: role
  - key: placement.availability_zone
    prefix: az

compose:
  ansible_host: public_ip_address
# Use dynamic inventory
ansible-inventory -i inventory/aws_ec2.yml --graph

# Run playbook with dynamic inventory
ansible-playbook -i inventory/aws_ec2.yml site.yml

Best Practices

Advanced
  1. Use roles — Organize playbooks into reusable roles
  2. Use Ansible Vault — Encrypt all sensitive data
  3. Idempotency — Ensure playbooks can run safely multiple times
  4. Use tags — Tag tasks for selective execution
  5. Use handlers — Avoid unnecessary service restarts
  6. Test with Molecule — Use Molecule for role testing
  7. Lint your code — Use ansible-lint for best practices
  8. Separate environments — Use different inventory files per environment
  9. Version pin roles — Pin Galaxy roles to specific versions
  10. Use --check mode — Always dry-run before applying changes
Project Structure

A well-organized Ansible project follows this structure:

ansible-project/
├── ansible.cfg
├── requirements.yml
├── site.yml                  # Master playbook
├── inventory/
│   ├── production/
│   │   ├── hosts.yml
│   │   └── group_vars/
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
├── playbooks/
│   ├── webservers.yml
│   ├── databases.yml
│   └── monitoring.yml
├── roles/
│   ├── common/
│   ├── webserver/
│   ├── database/
│   └── monitoring/
└── files/