Prerequisites

basic linux knownledge

What is Ansible Linux Automation?

Ansible linux automation is an agentless, open-source IT automation platform that uses SSH to configure systems, deploy applications, and orchestrate complex workflows across multiple Linux servers. Unlike traditional scripting, Ansible uses simple YAML syntax called “playbooks” to define desired system states, making infrastructure management reproducible, scalable, and maintainable.

Quick Win Example – Deploy Apache to Multiple Servers:

---
- name: Install and configure Apache
  hosts: webservers
  become: yes
  
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
    
    - name: Start Apache service
      service:
        name: apache2
        state: started
        enabled: yes
# Run the playbook
ansible-playbook apache-setup.yml

# Result: Apache installed and running on all webservers
# No agents, no complex scripts, just declarative automation

This immediately demonstrates Ansible’s power: manage dozens of servers with a single command, ensuring consistent configuration across your entire infrastructure.


Table of Contents

  1. How Does Ansible Linux Automation Work?
  2. What Are the Core Components of Ansible?
  3. How to Install and Configure Ansible on Linux?
  4. What is an Ansible Inventory and How to Create One?
  5. How to Write Your First Ansible Playbook?
  6. What Are Ansible Modules and How to Use Them?
  7. How to Use Ansible Roles for Reusability?
  8. What is Idempotency and Why Does It Matter?
  9. How to Manage Variables and Facts in Ansible?
  10. Best Practices for Ansible Linux Automation
  11. Frequently Asked Questions
  12. Troubleshooting Common Ansible Issues

How Does Ansible Linux Automation Work?

Ansible operates on a simple yet powerful architecture: a control node (your management machine) connects to managed nodes (target servers) via SSH, executes tasks defined in playbooks, and ensures systems match the desired state. Consequently, this agentless approach eliminates the overhead of installing and maintaining software on every server.

Ansible Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    Control Node (Your Machine)     β”‚
β”‚  - Ansible CLI                      β”‚
β”‚  - Python 3.8+                      β”‚
β”‚  - Playbooks & Roles                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ SSH Connection
               β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚             β”‚          β”‚        β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Web Server 1 β”‚ β”‚  DB     β”‚ β”‚ Cache  β”‚ β”‚ Load    β”‚
β”‚ SSH Port 22  β”‚ β”‚ Server  β”‚ β”‚ Server β”‚ β”‚ Balancerβ”‚
β”‚ Python 3     β”‚ β”‚         β”‚ β”‚        β”‚ β”‚         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   Managed Nodes (No Ansible Agent Required)

Execution Flow

# 1. Read inventory file
ansible all --list-hosts

# 2. Parse playbook YAML
ansible-playbook site.yml --syntax-check

# 3. Establish SSH connections
ansible all -m ping

# 4. Execute modules on remote hosts
ansible webservers -m command -a "uptime"

# 5. Gather results and report
ansible-playbook site.yml -v

Key Advantages:

FeatureBenefit
AgentlessNo software installation on managed nodes
SSH-basedUses existing secure infrastructure
DeclarativeDefine “what” not “how”
IdempotentSafe to run repeatedly
Extensible3,000+ built-in modules

Furthermore, the System Services with systemd guide complements Ansible automation by explaining service management on target systems.


What Are the Core Components of Ansible?

Understanding Ansible’s building blocks enables you to architect effective ansible linux automation solutions. Therefore, let’s examine each component in detail.

1. Control Node

Your management machine where Ansible runs:

# Check if Ansible is installed
ansible --version

# Typical output:
# ansible [core 2.15.4]
#   config file = /etc/ansible/ansible.cfg
#   configured module search path = ['/home/user/.ansible/plugins/modules']
#   ansible python module location = /usr/lib/python3/dist-packages/ansible
#   python version = 3.11.2

# Requirements
python3 --version  # Python 3.8+
ssh -V             # OpenSSH client

2. Managed Nodes

Target servers that Ansible configures:

# Minimum requirements on managed nodes:
# - Python 3.5+ (usually pre-installed)
# - SSH server running
# - SSH key-based authentication (recommended)

# Verify SSH access
ssh user@managed-node.example.com "python3 --version"

3. Inventory

Lists of servers organized into groups:

# /etc/ansible/hosts (INI format)

[webservers]

web1.example.com ansible_host=192.168.1.10 web2.example.com ansible_host=192.168.1.11

[databases]

db1.example.com ansible_port=2222 db2.example.com

[loadbalancers]

lb1.example.com # Group variables

[webservers:vars]

ansible_user=deploy http_port=80

YAML Inventory Format:

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_host: 192.168.1.10
        web2.example.com:
          ansible_host: 192.168.1.11
      vars:
        http_port: 80
    
    databases:
      hosts:
        db1.example.com:
          ansible_port: 2222

4. Playbooks

YAML files defining tasks and configurations:

---
# site.yml - Complete infrastructure playbook
- name: Configure web servers
  hosts: webservers
  become: yes
  
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
    
    - name: Deploy website content
      copy:
        src: website/
        dest: /var/www/html/
        owner: www-data
        group: www-data
    
    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes

5. Modules

Reusable units that perform specific tasks:

# Package management
ansible webservers -m apt -a "name=nginx state=present"

# File operations
ansible webservers -m copy -a "src=/local/file dest=/remote/file"

# Service management
ansible webservers -m service -a "name=nginx state=restarted"

# Command execution
ansible webservers -m command -a "uptime"

# User management
ansible webservers -m user -a "name=deploy state=present"

6. Roles

Organized, reusable collections of tasks, variables, and files:

roles/
  webserver/
    β”œβ”€β”€ tasks/
    β”‚   └── main.yml
    β”œβ”€β”€ handlers/
    β”‚   └── main.yml
    β”œβ”€β”€ templates/
    β”‚   └── nginx.conf.j2
    β”œβ”€β”€ files/
    β”‚   └── index.html
    β”œβ”€β”€ vars/
    β”‚   └── main.yml
    β”œβ”€β”€ defaults/
    β”‚   └── main.yml
    └── meta/
        └── main.yml

According to the Ansible Documentation, roles provide the best practice structure for organizing complex automation projects.


How to Install and Configure Ansible on Linux?

Installing ansible linux automation tools varies by distribution, but the process remains straightforward. Moreover, proper initial configuration ensures smooth operation across your infrastructure.

Ubuntu/Debian

# Update package index
sudo apt update

# Install software-properties-common
sudo apt install -y software-properties-common

# Add Ansible PPA repository
sudo add-apt-repository --yes --update ppa:ansible/ansible

# Install Ansible
sudo apt install -y ansible

# Verify installation
ansible --version

RHEL/CentOS/Rocky Linux

# Enable EPEL repository
sudo dnf install -y epel-release

# Install Ansible
sudo dnf install -y ansible

# Verify installation
ansible --version

Fedora

# Ansible is in default repositories
sudo dnf install -y ansible

# Install additional Python dependencies
sudo dnf install -y python3-pip python3-argcomplete

# Enable shell completion
sudo activate-global-python-argcomplete

pip (Universal Method)

# Ensure pip is installed
sudo apt install -y python3-pip  # Debian/Ubuntu
# OR
sudo dnf install -y python3-pip  # RHEL/Fedora

# Install Ansible using pip
pip3 install ansible

# Add to PATH if needed
echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.bashrc
source ~/.bashrc

# Verify
ansible --version

Initial Configuration

# Create Ansible configuration directory
mkdir -p ~/.ansible

# Create custom ansible.cfg
cat > ~/.ansible/ansible.cfg << 'EOF'

[defaults]

inventory = ./inventory host_key_checking = False retry_files_enabled = False gathering = smart fact_caching = jsonfile fact_caching_connection = /tmp/ansible_facts fact_caching_timeout = 3600

[privilege_escalation]

become = True become_method = sudo become_user = root become_ask_pass = False

[ssh_connection]

ssh_args = -o ControlMaster=auto -o ControlPersist=60s pipelining = True EOF # Set environment variable to use this config echo ‘export ANSIBLE_CONFIG=~/.ansible/ansible.cfg’ >> ~/.bashrc source ~/.bashrc

SSH Key Setup for Passwordless Authentication

# Generate SSH key pair (if not exists)
if [ ! -f ~/.ssh/id_rsa ]; then
    ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
fi

# Copy public key to managed nodes
ssh-copy-id user@web1.example.com
ssh-copy-id user@web2.example.com
ssh-copy-id user@db1.example.com

# Test SSH connection
ssh user@web1.example.com "echo 'SSH connection successful'"

# Test Ansible connectivity
ansible all -m ping

Ansible Configuration Priority

Ansible searches for configuration in this order:

# 1. ANSIBLE_CONFIG environment variable
export ANSIBLE_CONFIG=/path/to/ansible.cfg

# 2. ansible.cfg in current directory
./ansible.cfg

# 3. ~/.ansible.cfg in home directory
~/.ansible.cfg

# 4. /etc/ansible/ansible.cfg system-wide
/etc/ansible/ansible.cfg

# View current configuration
ansible-config dump --only-changed

The Linux Foundation Training offers comprehensive courses on configuration management and automation best practices.


What is an Ansible Inventory and How to Create One?

The inventory defines which servers Ansible manages and how to connect to them. Consequently, a well-structured inventory simplifies playbook development and execution.

Static Inventory – INI Format

# inventory/production.ini

# Ungrouped hosts
standalone.example.com

# Web servers group

[webservers]

web1.example.com web2.example.com ansible_host=192.168.1.11 web3.example.com ansible_port=2222 # Database servers

[databases]

db-primary.example.com ansible_user=dbadmin db-replica1.example.com db-replica2.example.com # Application servers

[appservers]

app[01:10].example.com # Expands to app01-app10 # Load balancers

[loadbalancers]

lb1.example.com lb2.example.com # Group of groups

[production:children]

webservers databases appservers loadbalancers # Variables for all production servers

[production:vars]

ansible_user=deploy ansible_python_interpreter=/usr/bin/python3 env=production # Variables specific to webservers

[webservers:vars]

http_port=80 max_clients=200 # Variables for databases

[databases:vars]

mysql_port=3306 backup_enabled=true

Static Inventory – YAML Format

# inventory/production.yml
all:
  hosts:
    standalone.example.com:
  
  children:
    production:
      children:
        webservers:
          hosts:
            web1.example.com:
            web2.example.com:
              ansible_host: 192.168.1.11
            web3.example.com:
              ansible_port: 2222
          vars:
            http_port: 80
            max_clients: 200
        
        databases:
          hosts:
            db-primary.example.com:
              ansible_user: dbadmin
              role: primary
            db-replica1.example.com:
              role: replica
            db-replica2.example.com:
              role: replica
          vars:
            mysql_port: 3306
            backup_enabled: true
        
        appservers:
          hosts:
            app[01:10].example.com:
        
        loadbalancers:
          hosts:
            lb1.example.com:
            lb2.example.com:
      
      vars:
        ansible_user: deploy
        ansible_python_interpreter: /usr/bin/python3
        env: production

Dynamic Inventory

#!/usr/bin/env python3
# inventory/dynamic_inventory.py
"""
Dynamic inventory script for AWS EC2 instances
"""
import json
import boto3

def get_inventory():
    ec2 = boto3.client('ec2', region_name='us-east-1')
    
    inventory = {
        'all': {
            'hosts': [],
            'vars': {}
        },
        'webservers': {
            'hosts': [],
            'vars': {'http_port': 80}
        },
        '_meta': {
            'hostvars': {}
        }
    }
    
    # Query EC2 instances
    response = ec2.describe_instances(
        Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
    )
    
    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            # Get instance details
            instance_id = instance['InstanceId']
            public_ip = instance.get('PublicIpAddress', '')
            private_ip = instance.get('PrivateIpAddress', '')
            
            # Get tags
            tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
            name = tags.get('Name', instance_id)
            role = tags.get('Role', 'undefined')
            
            # Add to inventory
            inventory['all']['hosts'].append(name)
            
            # Add to role-specific group
            if role not in inventory:
                inventory[role] = {'hosts': [], 'vars': {}}
            inventory[role]['hosts'].append(name)
            
            # Add host vars
            inventory['_meta']['hostvars'][name] = {
                'ansible_host': public_ip or private_ip,
                'instance_id': instance_id,
                'instance_type': instance['InstanceType'],
                'tags': tags
            }
    
    return inventory

if __name__ == '__main__':
    print(json.dumps(get_inventory(), indent=2))
# Make dynamic inventory executable
chmod +x inventory/dynamic_inventory.py

# Test dynamic inventory
./inventory/dynamic_inventory.py

# Use with ansible
ansible -i inventory/dynamic_inventory.py all --list-hosts

# Use in playbook
ansible-playbook -i inventory/dynamic_inventory.py site.yml

Inventory Commands and Queries

# List all hosts
ansible all --list-hosts

# List hosts in specific group
ansible webservers --list-hosts

# Show inventory graph
ansible-inventory --graph

# Show inventory in JSON
ansible-inventory --list

# Show host variables
ansible-inventory --host web1.example.com

# Test connectivity
ansible all -m ping

# Run ad-hoc command
ansible webservers -m command -a "uptime"

# Check which hosts match pattern
ansible "web*" --list-hosts
ansible "~(web|app).*" --list-hosts  # Regex pattern

Inventory Best Practices

# Organize inventory by environment
inventory/
  β”œβ”€β”€ production/
  β”‚   β”œβ”€β”€ hosts.yml
  β”‚   └── group_vars/
  β”‚       β”œβ”€β”€ all.yml
  β”‚       β”œβ”€β”€ webservers.yml
  β”‚       └── databases.yml
  β”œβ”€β”€ staging/
  β”‚   β”œβ”€β”€ hosts.yml
  β”‚   └── group_vars/
  β”‚       └── all.yml
  └── development/
      β”œβ”€β”€ hosts.yml
      └── group_vars/
          └── all.yml

# Use inventory with specific environment
ansible-playbook -i inventory/production site.yml
ansible-playbook -i inventory/staging site.yml

Additionally, the Bash Scripting Basics guide explains shell scripting concepts useful for creating dynamic inventory scripts.


How to Write Your First Ansible Playbook?

Playbooks are the heart of ansible linux automation, defining tasks in simple YAML syntax. Therefore, mastering playbook creation enables you to automate complex infrastructure management.

Basic Playbook Structure

---
# playbook.yml - Basic structure
- name: Descriptive play name
  hosts: target_group
  become: yes  # Run with sudo
  vars:
    variable_name: value
  
  tasks:
    - name: Descriptive task name
      module_name:
        parameter: value

Complete Web Server Deployment Playbook

---
# webserver-deploy.yml
- name: Deploy and configure web application
  hosts: webservers
  become: yes
  vars:
    app_name: myapp
    app_version: "1.0.0"
    app_user: www-data
    document_root: "/var/www/{{ app_name }}"
  
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
    
    - name: Install required packages
      apt:
        name:
          - nginx
          - python3-pip
          - git
        state: present
    
    - name: Create application directory
      file:
        path: "{{ document_root }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0755'
    
    - name: Deploy application files
      copy:
        src: "app/"
        dest: "{{ document_root }}/"
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
      notify: Restart nginx
    
    - name: Configure Nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
      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
    
    - name: Open firewall for HTTP
      ufw:
        rule: allow
        port: '80'
        proto: tcp
  
  handlers:
    - name: Restart nginx
      service:
        name: nginx
        state: restarted
    
    - name: Reload nginx
      service:
        name: nginx
        state: reloaded

Nginx Configuration Template

{# templates/nginx.conf.j2 #}
server {
    listen 80;
    server_name {{ ansible_fqdn }};
    
    root {{ document_root }};
    index index.html index.htm;
    
    access_log /var/log/nginx/{{ app_name }}_access.log;
    error_log /var/log/nginx/{{ app_name }}_error.log;
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    location /api/ {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Running Playbooks

# Basic execution
ansible-playbook webserver-deploy.yml

# Dry run (check mode)
ansible-playbook webserver-deploy.yml --check

# Show differences
ansible-playbook webserver-deploy.yml --check --diff

# Limit to specific hosts
ansible-playbook webserver-deploy.yml --limit web1.example.com

# Use specific inventory
ansible-playbook -i inventory/production webserver-deploy.yml

# Increase verbosity
ansible-playbook webserver-deploy.yml -v   # Basic
ansible-playbook webserver-deploy.yml -vv  # More detail
ansible-playbook webserver-deploy.yml -vvv # Debug level

# Start at specific task
ansible-playbook webserver-deploy.yml --start-at-task="Configure Nginx"

# Use tags
ansible-playbook webserver-deploy.yml --tags "configuration"
ansible-playbook webserver-deploy.yml --skip-tags "deployment"

Multi-Play Playbook

---
# site.yml - Complete infrastructure deployment
- name: Configure load balancers
  hosts: loadbalancers
  become: yes
  roles:
    - haproxy
    - keepalived

- name: Setup web servers
  hosts: webservers
  become: yes
  roles:
    - common
    - nginx
    - application

- name: Configure databases
  hosts: databases
  become: yes
  serial: 1  # One at a time
  roles:
    - common
    - mysql
    - replication

- name: Deploy monitoring
  hosts: all
  become: yes
  roles:
    - prometheus-node-exporter
    - filebeat

Conditional Execution

---
- name: OS-specific package installation
  hosts: all
  become: yes
  
  tasks:
    - name: Install Apache on Debian/Ubuntu
      apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"
    
    - name: Install Apache on RHEL/CentOS
      yum:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"
    
    - name: Ensure service is running
      service:
        name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
        state: started
        enabled: yes

Loops in Playbooks

---
- name: Create multiple users
  hosts: all
  become: yes
  
  vars:
    users:
      - name: alice
        uid: 1001
        groups: ['sudo', 'developers']
      - name: bob
        uid: 1002
        groups: ['developers']
      - name: charlie
        uid: 1003
        groups: ['operators']
  
  tasks:
    - name: Create user accounts
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        groups: "{{ item.groups }}"
        state: present
        shell: /bin/bash
      loop: "{{ users }}"
    
    - name: Install packages
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - vim
        - git
        - htop
        - curl
        - wget

Moreover, the Regular Expressions in Linux guide helps with pattern matching in Ansible filters and conditionals.


What Are Ansible Modules and How to Use Them?

Modules are the workhorses of ansible linux automation, providing idempotent operations for virtually every administrative task. Furthermore, understanding commonly used modules accelerates your automation development.

Essential System Modules

---
- name: System administration tasks
  hosts: all
  become: yes
  
  tasks:
    # Package management (apt)
    - name: Install packages on Debian/Ubuntu
      apt:
        name:
          - nginx
          - python3-pip
          - git
        state: present
        update_cache: yes
    
    # Package management (yum/dnf)
    - name: Install packages on RHEL/CentOS
      yum:
        name:
          - httpd
          - python3
          - git
        state: latest
    
    # Service management
    - name: Ensure service is running
      service:
        name: nginx
        state: started
        enabled: yes
    
    # systemd module (more advanced)
    - name: Manage systemd service
      systemd:
        name: nginx
        state: restarted
        enabled: yes
        daemon_reload: yes
    
    # User management
    - name: Create user account
      user:
        name: deploy
        uid: 1500
        groups: sudo,www-data
        shell: /bin/bash
        create_home: yes
        state: present
    
    # Group management
    - name: Create group
      group:
        name: developers
        gid: 2000
        state: present
    
    # Cron jobs
    - name: Schedule backup job
      cron:
        name: "Daily backup"
        minute: "0"
        hour: "2"
        job: "/usr/local/bin/backup.sh"
        user: root

File and Directory Modules

---
- name: File system operations
  hosts: all
  become: yes
  
  tasks:
    # Copy files
    - name: Copy configuration file
      copy:
        src: files/app.conf
        dest: /etc/app/app.conf
        owner: root
        group: root
        mode: '0644'
        backup: yes
    
    # Template files (Jinja2)
    - name: Deploy config from template
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        validate: 'nginx -t -c %s'
      notify: Reload nginx
    
    # Create directories
    - name: Ensure directory exists
      file:
        path: /var/www/myapp
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
        recurse: yes
    
    # Create symbolic links
    - name: Create symlink
      file:
        src: /etc/nginx/sites-available/myapp
        dest: /etc/nginx/sites-enabled/myapp
        state: link
    
    # Set file permissions
    - name: Set permissions
      file:
        path: /opt/scripts/deploy.sh
        mode: '0755'
    
    # Fetch files from remote
    - name: Fetch log files
      fetch:
        src: /var/log/application.log
        dest: logs/{{ inventory_hostname }}/
        flat: yes
    
    # Synchronize directories
    - name: Sync files with rsync
      synchronize:
        src: /local/path/
        dest: /remote/path/
        delete: yes
        recursive: yes

Command Execution Modules

---
- name: Execute commands
  hosts: all
  become: yes
  
  tasks:
    # Simple command
    - name: Check disk usage
      command: df -h
      register: disk_usage
    
    - name: Display disk usage
      debug:
        var: disk_usage.stdout_lines
    
    # Shell command (with pipes and redirects)
    - name: Complex shell command
      shell: |
        ps aux | grep nginx | wc -l
      register: nginx_processes
    
    # Raw command (no Python required)
    - name: Install Python on fresh system
      raw: apt-get install -y python3
      when: ansible_python_interpreter is not defined
    
    # Script execution
    - name: Run local script remotely
      script: scripts/setup.sh
      args:
        creates: /etc/app/configured
    
    # Expect module (interactive commands)
    - name: Change password interactively
      expect:
        command: passwd deploy
        responses:
          (?i)password: "{{ new_password }}"

Network Modules

---
- name: Network configuration
  hosts: all
  become: yes
  
  tasks:
    # Firewall (UFW)
    - name: Configure firewall rules
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - '22'
        - '80'
        - '443'
    
    # Firewall (firewalld)
    - name: Open port in firewalld
      firewalld:
        port: 8080/tcp
        permanent: yes
        state: enabled
        immediate: yes
    
    # Network interface configuration
    - name: Configure static IP
      template:
        src: network-interface.j2
        dest: /etc/network/interfaces
      notify: Restart networking
    
    # DNS configuration
    - name: Set DNS servers
      lineinfile:
        path: /etc/resolv.conf
        line: "nameserver {{ item }}"
      loop:
        - 8.8.8.8
        - 8.8.4.4
    
    # Download files
    - name: Download file from URL
      get_url:
        url: https://example.com/file.tar.gz
        dest: /tmp/file.tar.gz
        checksum: sha256:abc123...

Database Modules

---
- name: Database management
  hosts: databases
  become: yes
  
  tasks:
    # MySQL database
    - name: Create MySQL database
      mysql_db:
        name: myapp_db
        state: present
        encoding: utf8mb4
        collation: utf8mb4_unicode_ci
    
    # MySQL user
    - name: Create MySQL user
      mysql_user:
        name: myapp_user
        password: "{{ db_password }}"
        priv: 'myapp_db.*:ALL'
        host: '%'
        state: present
    
    # PostgreSQL database
    - name: Create PostgreSQL database
      postgresql_db:
        name: myapp_db
        encoding: UTF-8
        state: present
    
    # PostgreSQL user
    - name: Create PostgreSQL user
      postgresql_user:
        name: myapp_user
        password: "{{ db_password }}"
        db: myapp_db
        priv: ALL
        state: present
    
    # Execute SQL
    - name: Run SQL script
      mysql_db:
        name: myapp_db
        state: import
        target: /tmp/schema.sql

Cloud Modules (AWS Example)

---
- name: AWS infrastructure
  hosts: localhost
  gather_facts: no
  
  tasks:
    # EC2 instance
    - name: Launch EC2 instance
      amazon.aws.ec2_instance:
        name: web-server-01
        instance_type: t3.micro
        image_id: ami-0c55b159cbfafe1f0
        region: us-east-1
        key_name: my-key-pair
        vpc_subnet_id: subnet-abc123
        security_group: web-sg
        tags:
          Environment: production
          Role: webserver
        state: running
    
    # S3 bucket
    - name: Create S3 bucket
      amazon.aws.s3_bucket:
        name: my-app-backups
        region: us-east-1
        versioning: yes
        state: present
    
    # RDS instance
    - name: Create RDS database
      amazon.aws.rds_instance:
        db_instance_identifier: myapp-db
        engine: mysql
        db_instance_class: db.t3.micro
        allocated_storage: 20
        master_username: admin
        master_user_password: "{{ rds_password }}"
        state: present

Module Documentation

# List all modules
ansible-doc -l

# View module documentation
ansible-doc apt
ansible-doc copy
ansible-doc template

# Search for modules
ansible-doc -l | grep mysql

# View module examples
ansible-doc -s user

The Ansible Module Index provides comprehensive documentation for all 3,000+ built-in modules.


How to Use Ansible Roles for Reusability?

Roles organize ansible linux automation code into reusable, shareable components. Consequently, well-designed roles accelerate development and improve maintainability across projects.

Role Directory Structure

# Create role skeleton
ansible-galaxy init webserver

# Resulting structure:
webserver/
β”œβ”€β”€ README.md
β”œβ”€β”€ defaults/
β”‚   └── main.yml          # Default variables (lowest priority)
β”œβ”€β”€ files/
β”‚   └── index.html        # Static files to copy
β”œβ”€β”€ handlers/
β”‚   └── main.yml          # Handlers (triggered by notify)
β”œβ”€β”€ meta/
β”‚   └── main.yml          # Role metadata and dependencies
β”œβ”€β”€ tasks/
β”‚   └── main.yml          # Main task list
β”œβ”€β”€ templates/
β”‚   └── nginx.conf.j2     # Jinja2 templates
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ inventory
β”‚   └── test.yml
└── vars/
    └── main.yml          # Role variables (high priority)

Complete Web Server Role

roles/webserver/defaults/main.yml

---
# Default variables
webserver_port: 80
webserver_user: www-data
webserver_document_root: /var/www/html
webserver_max_clients: 100
webserver_packages:
  - nginx
  - python3-pip

roles/webserver/vars/main.yml

---
# Role-specific variables
nginx_config_path: /etc/nginx
nginx_sites_available: "{{ nginx_config_path }}/sites-available"
nginx_sites_enabled: "{{ nginx_config_path }}/sites-enabled"

roles/webserver/tasks/main.yml

---
# Main tasks file
- name: Include OS-specific variables
  include_vars: "{{ ansible_os_family }}.yml"

- name: Install web server packages
  apt:
    name: "{{ webserver_packages }}"
    state: present
    update_cache: yes
  when: ansible_os_family == "Debian"

- name: Create document root
  file:
    path: "{{ webserver_document_root }}"
    state: directory
    owner: "{{ webserver_user }}"
    group: "{{ webserver_user }}"
    mode: '0755'

- name: Deploy default index page
  copy:
    src: index.html
    dest: "{{ webserver_document_root }}/index.html"
    owner: "{{ webserver_user }}"
    group: "{{ webserver_user }}"
    mode: '0644'

- name: Configure Nginx
  template:
    src: nginx.conf.j2
    dest: "{{ nginx_config_path }}/nginx.conf"
    validate: 'nginx -t -c %s'
  notify: Reload nginx

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

- name: Configure firewall
  ufw:
    rule: allow
    port: "{{ webserver_port }}"
    proto: tcp

roles/webserver/handlers/main.yml

---
# Handlers
- name: Reload nginx
  service:
    name: nginx
    state: reloaded

- name: Restart nginx
  service:
    name: nginx
    state: restarted

roles/webserver/templates/nginx.conf.j2

user {{ webserver_user }};
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections {{ webserver_max_clients }};
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    gzip on;

    include {{ nginx_sites_enabled }}/*;
}

roles/webserver/meta/main.yml

---
galaxy_info:
  author: Your Name
  description: Nginx web server configuration
  company: LinuxTips.pro
  license: MIT
  min_ansible_version: 2.9
  
  platforms:
    - name: Ubuntu
      versions:
        - focal
        - jammy
    - name: Debian
      versions:
        - bullseye
        - bookworm
  
  galaxy_tags:
    - web
    - nginx
    - webserver

dependencies:
  - role: common
    vars:
      ntp_enabled: true

Using Roles in Playbooks

---
# site.yml - Using roles
- name: Configure web infrastructure
  hosts: webservers
  become: yes
  
  roles:
    - role: common
      tags: ['common']
    
    - role: webserver
      tags: ['web']
      webserver_port: 8080
      webserver_max_clients: 200
    
    - role: ssl
      tags: ['ssl']
      when: ssl_enabled | default(false)

# Alternative syntax with tasks
- name: Mixed approach
  hosts: webservers
  become: yes
  
  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
  
  roles:
    - webserver
  
  post_tasks:
    - name: Verify web server is responding
      uri:
        url: http://localhost
        return_content: yes
      register: result
      failed_when: "'Welcome' not in result.content"

Role Dependencies

# roles/application/meta/main.yml
---
dependencies:
  - role: common
  - role: webserver
    webserver_port: 8080
  - role: database
    db_name: myapp

Installing Roles from Ansible Galaxy

# Install role from Galaxy
ansible-galaxy install geerlingguy.nginx

# Install specific version
ansible-galaxy install geerlingguy.nginx,2.8.0

# Install from requirements file
# requirements.yml
---
- src: geerlingguy.nginx
  version: 2.8.0

- src: geerlingguy.mysql

- src: https://github.com/username/ansible-role-custom
  name: custom-role

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

# Install to specific path
ansible-galaxy install -r requirements.yml -p ./roles/

# List installed roles
ansible-galaxy list

# Remove role
ansible-galaxy remove geerlingguy.nginx

The Ansible Galaxy hosts thousands of community-contributed roles for common automation tasks.


What is Idempotency and Why Does It Matter?

Idempotency is a core principle of ansible linux automation ensuring that running the same playbook multiple times produces the same result. Therefore, understanding and maintaining idempotency prevents configuration drift and enables safe reexecution.

Understanding Idempotent Operations

---
- name: Idempotency examples
  hosts: all
  become: yes
  
  tasks:
    # IDEMPOTENT: Package installation
    - name: Ensure nginx is installed
      apt:
        name: nginx
        state: present
    # Result: Installs if missing, no change if present
    
    # IDEMPOTENT: Service state
    - name: Ensure nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
    # Result: Starts if stopped, no change if running
    
    # IDEMPOTENT: File content
    - name: Ensure configuration line exists
      lineinfile:
        path: /etc/nginx/nginx.conf
        line: 'worker_processes auto;'
        regexp: '^worker_processes'
    # Result: Adds if missing, no change if present
    
    # NOT IDEMPOTENT: Append without check
    - name: BAD - Appends every time
      shell: echo "log entry" >> /var/log/app.log
    # Problem: Creates duplicate entries on each run
    
    # IDEMPOTENT FIX: Use creates parameter
    - name: GOOD - Run only once
      shell: echo "initialized" > /var/log/app.log
      args:
        creates: /var/log/app.log
    # Result: Runs once, skips on subsequent runs

Making Commands Idempotent

---
- name: Idempotent command patterns
  hosts: all
  become: yes
  
  tasks:
    # Use creates parameter
    - name: Download file once
      command: wget https://example.com/file.tar.gz
      args:
        chdir: /tmp
        creates: /tmp/file.tar.gz
    
    # Use removes parameter
    - name: Clean up old files
      command: rm -f /tmp/old-file
      args:
        removes: /tmp/old-file
    
    # Check before executing
    - name: Check if already configured
      stat:
        path: /etc/app/configured
      register: configured
    
    - name: Run configuration script
      script: configure-app.sh
      when: not configured.stat.exists
    
    - name: Create marker file
      file:
        path: /etc/app/configured
        state: touch
      when: not configured.stat.exists

Testing Idempotency

# Run playbook first time
ansible-playbook site.yml

# Expected output:
# TASK [Install nginx] *******
# changed: [web1]  ← Changes made

# Run playbook second time
ansible-playbook site.yml

# Expected output:
# TASK [Install nginx] *******
# ok: [web1]  ← No changes (idempotent)

# Check for changes
ansible-playbook site.yml --check --diff

# Count changes
ansible-playbook site.yml | grep -c changed=

# Should be 0 on second run for idempotent playbook

Non-Idempotent Patterns to Avoid

---
# ANTI-PATTERNS - DO NOT USE

- name: BAD - Appends every run
  shell: echo "export PATH=$PATH:/opt/bin" >> ~/.bashrc

- name: BAD - Creates duplicates
  lineinfile:
    path: /etc/hosts
    line: "192.168.1.10 server1"
    # Missing regexp to check if exists

- name: BAD - Always changes
  command: date > /tmp/timestamp.txt

- name: BAD - Increments value
  shell: expr $(cat /tmp/counter) + 1 > /tmp/counter

# CORRECT PATTERNS

- name: GOOD - Idempotent PATH addition
  lineinfile:
    path: ~/.bashrc
    line: 'export PATH=$PATH:/opt/bin'
    regexp: '^export PATH=.*:/opt/bin'

- name: GOOD - Prevents duplicates
  lineinfile:
    path: /etc/hosts
    line: "192.168.1.10 server1"
    regexp: '^192\.168\.1\.10'

- name: GOOD - Updates only if changed
  copy:
    content: "{{ ansible_date_time.iso8601 }}"
    dest: /tmp/timestamp.txt
  # Only changes if content differs

- name: GOOD - Sets value idempotently
  copy:
    content: "10"
    dest: /tmp/counter
  # Sets to 10 regardless of current value

Similarly, the Error Handling in Bash Scripts guide demonstrates defensive programming patterns applicable to Ansible task design.


How to Manage Variables and Facts in Ansible?

Variables and facts provide dynamic data to ansible linux automation playbooks, enabling flexible, reusable configurations. Consequently, mastering variable management improves playbook adaptability across environments.

Variable Definition Locations

# 1. Playbook variables
---
- name: Using playbook variables
  hosts: all
  vars:
    app_name: myapp
    app_version: "1.0.0"
  
  tasks:
    - name: Deploy application
      debug:
        msg: "Deploying {{ app_name }} version {{ app_version }}"

# 2. Inventory variables (host_vars)
# host_vars/web1.example.com.yml
---
ansible_host: 192.168.1.10
http_port: 8080
ssl_enabled: true

# 3. Inventory variables (group_vars)
# group_vars/webservers.yml
---
nginx_worker_processes: 4
nginx_max_clients: 100

# 4. Role variables
# roles/webserver/vars/main.yml
---
default_document_root: /var/www/html

# 5. Extra variables (command line)
ansible-playbook site.yml -e "env=production debug=false"

Variable Precedence (Lowest to Highest)

1.  role defaults (defaults/main.yml)
2.  inventory file or script group vars
3.  inventory group_vars/all
4.  playbook group_vars/all
5.  inventory group_vars/*
6.  playbook group_vars/*
7.  inventory file or 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 (vars/main.yml)
15. block vars (only for tasks in block)
16. task vars (only for the task)
17. include_vars
18. set_facts / registered vars
19. role (and include_role) params
20. include params
21. extra vars (-e in CLI)

Working with Facts

---
- name: Using Ansible facts
  hosts: all
  gather_facts: yes
  
  tasks:
    - name: Display all facts
      debug:
        var: ansible_facts
    
    - name: Show specific facts
      debug:
        msg: |
          Hostname: {{ ansible_hostname }}
          OS Family: {{ ansible_os_family }}
          Distribution: {{ ansible_distribution }}
          Version: {{ ansible_distribution_version }}
          Python: {{ ansible_python_version }}
          IP Address: {{ ansible_default_ipv4.address }}
          CPU Cores: {{ ansible_processor_cores }}
          Memory: {{ ansible_memtotal_mb }} MB
    
    - name: OS-specific task
      apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"
    
    - name: Memory-based decision
      debug:
        msg: "High memory system"
      when: ansible_memtotal_mb >= 8192

Custom Facts

# Create custom fact script
sudo mkdir -p /etc/ansible/facts.d

# /etc/ansible/facts.d/application.fact
sudo tee /etc/ansible/facts.d/application.fact << 'EOF'
#!/bin/bash
cat << JSON
{
    "app_name": "myapp",
    "app_version": "1.0.0",
    "environment": "production",
    "last_deployment": "$(date -I)"
}
JSON
EOF

sudo chmod +x /etc/ansible/facts.d/application.fact
---
- name: Using custom facts
  hosts: all
  gather_facts: yes
  
  tasks:
    - name: Show custom facts
      debug:
        msg: "{{ ansible_local.application }}"
    
    - name: Use custom fact in task
      debug:
        msg: "App: {{ ansible_local.application.app_name }}"

Registered Variables

---
- name: Using registered variables
  hosts: all
  
  tasks:
    - name: Check if file exists
      stat:
        path: /etc/app/config.yml
      register: config_file
    
    - name: Show file status
      debug:
        msg: "Config exists: {{ config_file.stat.exists }}"
    
    - name: Get disk usage
      command: df -h /
      register: disk_usage
      changed_when: false
    
    - name: Parse command output
      debug:
        var: disk_usage.stdout_lines
    
    - name: Complex registration
      shell: |
        echo "Database backups:"
        ls -lh /backup/db/*.sql 2>/dev/null | wc -l
      register: backup_count
      failed_when: backup_count.rc != 0
      changed_when: false
    
    - name: Use registered variable
      debug:
        msg: "Found {{ backup_count.stdout_lines[1] }} backups"

Variable Templating with Jinja2

---
- name: Advanced variable usage
  hosts: all
  vars:
    app_config:
      name: myapp
      port: 8080
      debug: false
      database:
        host: localhost
        port: 3306
        name: myapp_db
  
  tasks:
    - name: Use nested variables
      debug:
        msg: "DB: {{ app_config.database.host }}:{{ app_config.database.port }}"
    
    - name: Conditional variable
      debug:
        msg: "{{ 'Debug enabled' if app_config.debug else 'Production mode' }}"
    
    - name: List manipulation
      debug:
        msg: "{{ ['web1', 'web2', 'web3'] | join(', ') }}"
    
    - name: Dictionary manipulation
      debug:
        msg: "{{ app_config | to_nice_json }}"

Variable Files

---
# vars/production.yml
env: production
debug: false
database_host: db.prod.example.com
api_endpoint: https://api.prod.example.com

# vars/staging.yml
env: staging
debug: true
database_host: db.staging.example.com
api_endpoint: https://api.staging.example.com

# Playbook using variable files
- name: Environment-specific deployment
  hosts: all
  vars_files:
    - "vars/{{ env }}.yml"
  
  tasks:
    - name: Show environment
      debug:
        msg: "Deploying to {{ env }} environment"

The Jinja2 Template Designer Documentation provides comprehensive information about template syntax and filters available in Ansible.


Best Practices for Ansible Linux Automation

Professional ansible linux automation requires adherence to established patterns that ensure scalability, maintainability, and security. Therefore, following these practices elevates your automation from functional to production-grade.

1. Project Organization

# Recommended directory structure
ansible-project/
β”œβ”€β”€ ansible.cfg              # Ansible configuration
β”œβ”€β”€ inventory/
β”‚   β”œβ”€β”€ production/
β”‚   β”‚   β”œβ”€β”€ hosts.yml
β”‚   β”‚   └── group_vars/
β”‚   β”‚       β”œβ”€β”€ all.yml
β”‚   β”‚       └── webservers.yml
β”‚   └── staging/
β”‚       β”œβ”€β”€ hosts.yml
β”‚       └── group_vars/
β”‚           └── all.yml
β”œβ”€β”€ roles/
β”‚   β”œβ”€β”€ common/
β”‚   β”œβ”€β”€ webserver/
β”‚   └── database/
β”œβ”€β”€ playbooks/
β”‚   β”œβ”€β”€ site.yml
β”‚   β”œβ”€β”€ webservers.yml
β”‚   └── databases.yml
β”œβ”€β”€ group_vars/
β”‚   └── all.yml
β”œβ”€β”€ host_vars/
β”œβ”€β”€ files/
β”œβ”€β”€ templates/
β”œβ”€β”€ vars/
β”‚   β”œβ”€β”€ production.yml
β”‚   └── staging.yml
β”œβ”€β”€ library/              # Custom modules
β”œβ”€β”€ filter_plugins/       # Custom filters
└── requirements.yml      # Role dependencies

2. Use Version Control

# Initialize git repository
git init

# Create .gitignore
cat > .gitignore << 'EOF'
*.retry
*.pyc
__pycache__/
.vault_pass
vault_password
*.swp
.DS_Store
EOF

# Add and commit
git add .
git commit -m "Initial Ansible project structure"

# Create feature branch
git checkout -b feature/new-role

# After testing
git checkout main
git merge feature/new-role

3. Encrypt Sensitive Data with Ansible Vault

# Create encrypted file
ansible-vault create secrets.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# Encrypt existing file
ansible-vault encrypt vars/production.yml

# Decrypt file
ansible-vault decrypt vars/production.yml

# Change vault password
ansible-vault rekey secrets.yml

# View encrypted file
ansible-vault view secrets.yml

# Use password file
echo "my_vault_password" > .vault_pass
chmod 600 .vault_pass

# In ansible.cfg

[defaults]

vault_password_file = .vault_pass

Encrypted Variables Example:

# secrets.yml (encrypted)
---
db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653966616265626566393537623931323865613766396236633030
  3762633137326632376532636235626538353030653834330a303434

api_key: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  39653139353535353438656538343365636531636432383437333237636334
  3966393636396665620a39336265353764373264343765376334353936386462

4. Use Check Mode and Diff

# Dry run (check mode)
ansible-playbook site.yml --check

# Show differences
ansible-playbook site.yml --check --diff

# Limit to specific hosts
ansible-playbook site.yml --check --limit web1.example.com

# Step through tasks
ansible-playbook site.yml --step

5. Implement Proper Error Handling

---
- name: Robust error handling
  hosts: all
  
  tasks:
    - name: Attempt risky operation
      command: /usr/local/bin/might-fail.sh
      register: result
      failed_when: false  # Don't fail playbook
      changed_when: result.rc == 0
    
    - name: Handle failure
      debug:
        msg: "Operation failed: {{ result.stderr }}"
      when: result.rc != 0
    
    - name: Ensure cleanup happens
      file:
        path: /tmp/cleanup-needed
        state: absent
      always:  # Runs even if previous tasks fail
    
    - name: Critical task with retry
      uri:
        url: https://api.example.com/health
        status_code: 200
      register: health_check
      until: health_check.status == 200
      retries: 5
      delay: 10
    
    - name: Graceful degradation
      block:
        - name: Try primary method
          command: method1.sh
      rescue:
        - name: Fallback to secondary
          command: method2.sh
      always:
        - name: Log attempt
          lineinfile:
            path: /var/log/deployment.log
            line: "{{ ansible_date_time.iso8601 }} - Deployment attempted"

6. Use Tags Effectively

---
- name: Tagged playbook
  hosts: all
  
  tasks:
    - name: Install packages
      apt:
        name: nginx
        state: present
      tags:
        - packages
        - nginx
    
    - name: Configure Nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      tags:
        - configuration
        - nginx
    
    - name: Deploy application
      copy:
        src: app/
        dest: /var/www/html/
      tags:
        - deployment
        - application
# Run only specific tags
ansible-playbook site.yml --tags "configuration"

# Run multiple tags
ansible-playbook site.yml --tags "nginx,application"

# Skip tags
ansible-playbook site.yml --skip-tags "deployment"

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

7. Document Your Playbooks

---
# webserver-setup.yml
# Purpose: Deploy and configure Nginx web servers
# Author: DevOps Team
# Last Updated: 2025-10-08
# Dependencies: roles/common, roles/nginx
# Usage: ansible-playbook -i inventory/production webserver-setup.yml

- name: Configure web servers for production
  hosts: webservers
  become: yes
  
  # Pre-flight checks
  pre_tasks:
    - name: Verify connectivity
      ping:
    
    - name: Check disk space
      assert:
        that: ansible_facts.mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 5000000000
        fail_msg: "Insufficient disk space (need 5GB free)"
  
  roles:
    - role: common
      tags: ['common']
    
    - role: nginx
      tags: ['nginx']
  
  # Post-deployment validation
  post_tasks:
    - name: Verify web server is responding
      uri:
        url: http://{{ ansible_default_ipv4.address }}
        status_code: 200
      register: health_check
      failed_when: health_check.status != 200

8. Use Ansible Lint

# Install ansible-lint
pip3 install ansible-lint

# Lint playbook
ansible-lint playbook.yml

# Lint entire project
ansible-lint

# Auto-fix issues (where possible)
ansible-lint --fix playbook.yml

# Custom rules
# .ansible-lint
---
skip_list:
  - '306'  # Shells that use pipes should set the pipefail option
  - '204'  # Lines should be no longer than 160 chars

exclude_paths:
  - roles/external/
  - inventory/

9. Test Your Automation

# tests/test_webserver.yml
---
- name: Test web server deployment
  hosts: webservers
  gather_facts: yes
  
  tasks:
    - name: Check Nginx is installed
      command: nginx -v
      register: nginx_version
      failed_when: nginx_version.rc != 0
      changed_when: false
    
    - name: Verify Nginx service is running
      service_facts:
    
    - name: Assert Nginx is active
      assert:
        that:
          - ansible_facts.services['nginx.service'].state == 'running'
        fail_msg: "Nginx is not running"
    
    - name: Test HTTP response
      uri:
        url: http://localhost
        return_content: yes
      register: response
    
    - name: Validate response content
      assert:
        that:
          - response.status == 200
          - "'Welcome' in response.content"
        fail_msg: "Web server not responding correctly"

The Red Hat Ansible Best Practices documentation provides additional guidance on professional automation development.


Frequently Asked Questions

Is Ansible only for Linux systems?

No, while Ansible excels at Linux automation, it also supports:

  • Windows: Using WinRM protocol instead of SSH
  • Network Devices: Cisco, Juniper, Arista, etc.
  • Cloud Platforms: AWS, Azure, GCP, OpenStack
  • Containers: Docker, Kubernetes
  • MacOS: For workstation management
# Windows example
- name: Manage Windows servers
  hosts: windows_servers
  tasks:
    - name: Install IIS
      win_feature:
        name: Web-Server
        state: present

How does Ansible compare to Chef or Puppet?

FeatureAnsibleChefPuppet
Agent RequiredNoYesYes
LanguageYAMLRuby DSLPuppet DSL
Learning CurveLowModerateModerate
Push/PullPushPullPull
Best ForSimplicityComplex workflowsLarge enterprises

Can Ansible manage Docker containers?

Yes, Ansible has extensive Docker support:

---
- name: Manage Docker containers
  hosts: docker_hosts
  
  tasks:
    - name: Pull Docker image
      docker_image:
        name: nginx
        source: pull
    
    - name: Run container
      docker_container:
        name: web
        image: nginx
        state: started
        ports:
          - "80:80"
        volumes:
          - /data:/usr/share/nginx/html

How do I handle secrets in Ansible?

Use Ansible Vault for encrypting sensitive data:

# Encrypt variable
ansible-vault encrypt_string 'secret_password' --name 'db_password'

# Result to include in playbook:
db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  ...encrypted content...

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

Can Ansible replace Bash scripts?

Ansible complements rather than replaces Bash:

  • Use Ansible for: Configuration management, orchestration, idempotent operations
  • Use Bash for: Complex logic, system-specific operations, rapid prototyping
# Ansible can call Bash scripts
- name: Run complex bash script
  script: complex-logic.sh
  args:
    executable: /bin/bash

How do I speed up Ansible playbooks?

# Enable pipelining (ansible.cfg)

[ssh_connection]

pipelining = True # Use strategy plugins – name: Fast execution hosts: all strategy: free # Don’t wait for all hosts tasks: – name: Parallel task command: long-running-command # Increase forks

[defaults]

forks = 20 # Limit fact gathering – name: Skip facts hosts: all gather_facts: no

What’s the difference between ansible and ansible-playbook?

# ansible: Ad-hoc commands (quick tasks)
ansible all -m ping
ansible webservers -m service -a "name=nginx state=restarted"

# ansible-playbook: Run playbooks (complex automation)
ansible-playbook site.yml

Troubleshooting Common Ansible Issues

Issue: SSH Connection Failures

Symptom: “Failed to connect to host via ssh”

# Test SSH manually
ssh -vvv user@host

# Common fixes:
# 1. Add SSH key
ssh-copy-id user@host

# 2. Specify SSH key in inventory

[webservers]

web1 ansible_ssh_private_key_file=~/.ssh/custom_key # 3. Disable host key checking (testing only) export ANSIBLE_HOST_KEY_CHECKING=False # 4. Check SSH agent eval $(ssh-agent) ssh-add ~/.ssh/id_rsa # 5. Use password authentication ansible-playbook site.yml –ask-pass

Issue: Python Not Found on Target

Symptom: “/bin/sh: python: not found”

# Solution 1: Specify Python interpreter

[webservers:vars]

ansible_python_interpreter=/usr/bin/python3 # Solution 2: Auto-discover Python ansible_python_interpreter=auto_silent # Solution 3: Install Python first – name: Install Python raw: apt-get update && apt-get install -y python3 become: yes

Issue: Privilege Escalation Failures

Symptom: “Missing sudo password”

# Prompt for sudo password
ansible-playbook site.yml --ask-become-pass

# Use NOPASSWD in sudoers
# /etc/sudoers.d/ansible
deploy ALL=(ALL) NOPASSWD: ALL

# Test privilege escalation
ansible all -m command -a "id" -b

Issue: Module Not Found

Symptom: “The module X was not found”

# Check module availability
ansible-doc -l | grep module_name

# Install collection
ansible-galaxy collection install community.general

# Verify module location
ansible-config dump | grep DEFAULT_MODULE_PATH

# Use full module name
- name: Use full module path
  community.general.snap:
    name: hello-world

Issue: Playbook Hanging

Symptom: Task runs indefinitely without completion

# Increase timeout

[defaults]

timeout = 30 # Use async tasks – name: Long running task command: /usr/local/bin/long-task.sh async: 3600 # Maximum runtime poll: 10 # Check every 10 seconds # Debug with verbosity ansible-playbook site.yml -vvv

Issue: Templating Errors

Symptom: “AnsibleUndefinedVariable: ‘variable’ is undefined”

# Use default filter
{{ variable_name | default('default_value') }}

# Check if variable is defined
{% if variable_name is defined %}
  {{ variable_name }}
{% endif %}

# Use mandatory filter (fail if undefined)
{{ variable_name | mandatory }}

# Debug variables
- name: Show all variables
  debug:
    var: hostvars[inventory_hostname]

Issue: Facts Not Updating

Symptom: Stale cached facts

# Clear fact cache
rm -rf /tmp/ansible_facts/*

# Disable fact caching

[defaults]

gathering = smart fact_caching = False # Force fact gathering ansible all -m setup –tree /tmp/facts # Gather subset of facts – name: Gather minimal facts hosts: all gather_facts: yes gather_subset: – ‘!all’ – ‘network’

Issue: Slow Playbook Execution

Symptom: Playbooks taking too long

# Enable profiling
# ansible.cfg

[defaults]

callbacks_enabled = profile_tasks, timer # Increase parallel execution

[defaults]

forks = 20 # Use mitogen (significant speedup) pip install mitogen # ansible.cfg

[defaults]

strategy_plugins = /path/to/mitogen/ansible_mitogen/plugins/strategy strategy = mitogen_linear # Profile playbook execution ANSIBLE_STRATEGY=profile_tasks ansible-playbook site.yml

The Ansible Troubleshooting Guide provides comprehensive debugging strategies and solutions.


Real-World Use Cases

1: Complete LAMP Stack Deployment

---
# lamp-stack.yml
- name: Deploy LAMP Stack
  hosts: webservers
  become: yes
  vars:
    mysql_root_password: "{{ vault_mysql_root_password }}"
    app_db_name: myapp
    app_db_user: myapp_user
    app_db_password: "{{ vault_app_db_password }}"
  
  tasks:
    - name: Install LAMP packages
      apt:
        name:
          - apache2
          - mysql-server
          - php
          - php-mysql
          - python3-pymysql
        state: present
        update_cache: yes
    
    - name: Start services
      service:
        name: "{{ item }}"
        state: started
        enabled: yes
      loop:
        - apache2
        - mysql
    
    - name: Secure MySQL installation
      mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
    
    - name: Create application database
      mysql_db:
        name: "{{ app_db_name }}"
        state: present
        login_user: root
        login_password: "{{ mysql_root_password }}"
    
    - name: Create database user
      mysql_user:
        name: "{{ app_db_user }}"
        password: "{{ app_db_password }}"
        priv: "{{ app_db_name }}.*:ALL"
        state: present
        login_user: root
        login_password: "{{ mysql_root_password }}"
    
    - name: Deploy PHP application
      copy:
        src: webapp/
        dest: /var/www/html/
        owner: www-data
        group: www-data
    
    - name: Configure Apache virtual host
      template:
        src: vhost.conf.j2
        dest: /etc/apache2/sites-available/myapp.conf
      notify: Restart Apache
    
    - name: Enable site
      command: a2ensite myapp.conf
      notify: Restart Apache
  
  handlers:
    - name: Restart Apache
      service:
        name: apache2
        state: restarted

2: Multi-Tier Application with Load Balancer

---
# deploy-infrastructure.yml
- name: Configure load balancers
  hosts: loadbalancers
  become: yes
  roles:
    - haproxy
  vars:
    backend_servers: "{{ groups['webservers'] }}"

- name: Deploy web tier
  hosts: webservers
  become: yes
  serial: 2  # Rolling deployment
  roles:
    - common
    - nginx
    - application
  
  tasks:
    - name: Remove from load balancer
      haproxy:
        state: disabled
        host: "{{ inventory_hostname }}"
        backend: webapp
      delegate_to: "{{ item }}"
      loop: "{{ groups['loadbalancers'] }}"
    
    - name: Deploy new version
      include_role:
        name: application
    
    - name: Health check
      uri:
        url: http://localhost/health
        status_code: 200
      register: health
      until: health.status == 200
      retries: 10
      delay: 3
    
    - name: Add back to load balancer
      haproxy:
        state: enabled
        host: "{{ inventory_hostname }}"
        backend: webapp
      delegate_to: "{{ item }}"
      loop: "{{ groups['loadbalancers'] }}"

- name: Configure database cluster
  hosts: databases
  become: yes
  serial: 1
  roles:
    - mysql
    - mysql-replication

3: Security Hardening

---
# security-hardening.yml
- name: Harden Linux servers
  hosts: all
  become: yes
  
  tasks:
    - name: Update all packages
      apt:
        upgrade: dist
        update_cache: yes
      when: ansible_os_family == "Debian"
    
    - name: Disable root login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin'
        line: 'PermitRootLogin no'
      notify: Restart SSH
    
    - name: Disable password authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PasswordAuthentication'
        line: 'PasswordAuthentication no'
      notify: Restart SSH
    
    - name: Configure firewall
      ufw:
        rule: "{{ item.rule }}"
        port: "{{ item.port }}"
        proto: "{{ item.proto }}"
      loop:
        - { rule: 'limit', port: '22', proto: 'tcp' }
        - { rule: 'allow', port: '80', proto: 'tcp' }
        - { rule: 'allow', port: '443', proto: 'tcp' }
    
    - name: Enable firewall
      ufw:
        state: enabled
    
    - name: Install fail2ban
      apt:
        name: fail2ban
        state: present
    
    - name: Configure fail2ban
      copy:
        src: jail.local
        dest: /etc/fail2ban/jail.local
      notify: Restart fail2ban
  
  handlers:
    - name: Restart SSH
      service:
        name: sshd
        state: restarted
    
    - name: Restart fail2ban
      service:
        name: fail2ban
        state: restarted

Additional Resources

Official Documentation

Learning Resources

Related LinuxTips.pro Guides

Tools and Utilities


Conclusion

Mastering ansible linux automation transforms infrastructure management from manual, error-prone tasks into reliable, repeatable processes. By leveraging Ansible’s agentless architecture, declarative YAML syntax, and extensive module library, you can automate everything from simple package installations to complex multi-tier application deployments.

Furthermore, Ansible’s idempotent nature ensures safe reexecution, making it ideal for maintaining configuration compliance across dynamic infrastructure. The combination of roles for code reusability, variables for flexibility, and Ansible Vault for security creates a complete automation framework suitable for organizations of any size.

Remember to start with simple playbooks, gradually incorporate roles and best practices, and always test in non-production environments before deploying changes. Proper use of version control, documentation, and testing ensures your automation remains maintainable and reliable as your infrastructure grows.

Start implementing ansible linux automation in your environment today, and you’ll immediately experience reduced manual effort, improved consistency, and faster deployment cycles across your entire Linux infrastructure.


Last Updated: October 2025

Mark as Complete

Did you find this guide helpful? Track your progress by marking it as completed.