Infrastructure automation is a cornerstone of modern DevOps. Ansible is a powerful, agentless tool for automating server provisioning, configuration, and application deployment. In this comprehensive guide, we’ll explore best practices, real-world examples, and advanced techniques for mastering Ansible.

Why Ansible?

Ansible has become the go-to automation tool for DevOps engineers worldwide. Here’s why:

  • Agentless Architecture: Uses SSH for Linux/Unix and WinRM for Windows, no agent required on managed nodes
  • Idempotent Operations: Ensures repeatable, predictable results without unintended side effects
  • Modular Design: Roles and playbooks promote code reuse and maintainability
  • Simple YAML Syntax: Easy to learn and read, lowering the barrier to entry
  • Extensive Module Library: 5000+ modules covering cloud providers, databases, networking, and more
  • Active Community: Large ecosystem with Galaxy roles and continuous improvements

Getting Started: Installation & Setup

First, let’s set up Ansible on your control node:

# Install Ansible 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

# Verify installation
ansible --version

# Install Ansible on CentOS/RHEL
sudo yum install -y epel-release
sudo yum install -y ansible

# Install using Python pip
pip3 install ansible

Basic Inventory Configuration

Create an inventory file to define your managed hosts:

# inventory/hosts.ini
[webservers]
web1.example.com ansible_host=192.168.1.10
web2.example.com ansible_host=192.168.1.11

[databases]
db1.example.com ansible_host=192.168.1.20
db2.example.com ansible_host=192.168.1.21

[production:children]
webservers
databases

[all:vars]
ansible_user=ansible
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3

Real-World Example: Provisioning a Web Server

Let’s create a complete playbook to provision and configure an Nginx web server with SSL:

# playbooks/webserver.yml
---
- name: Configure Nginx Web Server with SSL
  hosts: webservers
  become: yes
  vars:
    nginx_port: 80
    nginx_ssl_port: 443
    domain_name: example.com
    ssl_certificate_path: /etc/ssl/certs
    
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"
    
    - name: Install Nginx
      apt:
        name: nginx
        state: present
      notify: restart nginx
    
    - name: Install SSL certificates
      copy:
        src: ""
        dest: ""
        mode: '0644'
      loop:
        - { src: 'files/.crt', dest: '/.crt' }
        - { src: 'files/.key', dest: '/.key' }
      notify: restart nginx
    
    - name: Deploy Nginx configuration
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/
        mode: '0644'
      notify: reload nginx
    
    - name: Enable site configuration
      file:
        src: /etc/nginx/sites-available/
        dest: /etc/nginx/sites-enabled/
        state: link
      notify: reload nginx
    
    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
    
    - name: Configure firewall
      ufw:
        rule: allow
        port: ""
        proto: tcp
      loop:
        - ""
        - ""
  
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted
    
    - name: reload nginx
      service:
        name: nginx
        state: reloaded

Nginx Configuration Template

Create a Jinja2 template for dynamic configuration:

# templates/nginx.conf.j2
server {
    listen ;
    listen [::]:;
    server_name  www.;
    
    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen  ssl http2;
    listen [::]: ssl http2;
    server_name  www.;
    
    # SSL Configuration
    ssl_certificate /.crt;
    ssl_certificate_key /.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    root /var/www//html;
    index index.html index.htm;
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    # Logging
    access_log /var/log/nginx/_access.log;
    error_log /var/log/nginx/_error.log;
}

Best Practices: Organizing with Roles

For complex projects, use Ansible roles for better organization:

# Create role structure
ansible-galaxy init roles/webserver

# Directory structure
roles/webserver/
├── defaults/
│   └── main.yml          # Default variables
├── files/                # Static files
├── handlers/
│   └── main.yml          # Handlers
├── meta/
│   └── main.yml          # Role dependencies
├── tasks/
│   └── main.yml          # Main tasks
├── templates/            # Jinja2 templates
├── tests/                # Test playbooks
└── vars/
    └── main.yml          # Role variables

Example role-based playbook:

# playbooks/site.yml
---
- name: Configure all servers
  hosts: all
  roles:
    - common
    - security
    
- name: Configure web servers
  hosts: webservers
  roles:
    - nginx
    - ssl
    - monitoring
    
- name: Configure database servers
  hosts: databases
  roles:
    - postgresql
    - backup
    - monitoring

Security: Using Ansible Vault

Never store sensitive data in plain text. Use Ansible Vault:

# Create encrypted variable file
ansible-vault create group_vars/production/vault.yml

# Edit encrypted file
ansible-vault edit group_vars/production/vault.yml

# Encrypt existing file
ansible-vault encrypt secrets.yml

# Decrypt file
ansible-vault decrypt secrets.yml

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

# Use vault password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt

Example vault content:

# group_vars/production/vault.yml (encrypted)
vault_db_password: "SuperSecurePassword123!"
vault_api_key: "sk-1234567890abcdef"
vault_ssh_private_key: |
  -----BEGIN RSA PRIVATE KEY-----
  MIIEpAIBAAKCAQEA...
  -----END RSA PRIVATE KEY-----

Reference vault variables in your playbooks:

---
- name: Deploy application with secrets
  hosts: webservers
  vars:
    db_password: ""
    api_key: ""
  tasks:
    - name: Configure application
      template:
        src: app_config.j2
        dest: /etc/app/config.yml

Advanced Techniques

Dynamic Inventory for AWS

Use dynamic inventory to automatically discover EC2 instances:

# Install AWS plugin
pip3 install boto3 botocore

# Create inventory plugin configuration
cat > inventory/aws_ec2.yml <<EOF
plugin: aws_ec2
regions:
  - us-east-1
  - us-west-2
filters:
  tag:Environment: production
keyed_groups:
  - key: tags.Role
    prefix: role
  - key: tags.Environment
    prefix: env
hostnames:
  - private-ip-address
compose:
  ansible_host: private_ip_address
EOF

# Test dynamic inventory
ansible-inventory -i inventory/aws_ec2.yml --graph

Testing with Molecule

Ensure your roles work correctly before deployment:

# Install Molecule
pip3 install molecule molecule-docker

# Initialize molecule in role directory
cd roles/webserver
molecule init scenario --driver-name docker

# Run tests
molecule test

# Test sequence breakdown
molecule create    # Create test instance
molecule converge  # Run playbook
molecule verify    # Run verification
molecule destroy   # Cleanup

Performance Optimization

Speed up playbook execution:

# ansible.cfg
[defaults]
forks = 20                    # Parallel execution
gathering = smart             # Smart fact gathering
fact_caching = jsonfile       # Cache facts
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600   # Cache for 1 hour
host_key_checking = False
pipelining = True             # SSH pipelining

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

Monitoring & Logging

Integrate with monitoring tools:

---
- name: Setup monitoring
  hosts: all
  roles:
    - prometheus_node_exporter
  
  tasks:
    - name: Install Filebeat for log shipping
      apt:
        name: filebeat
        state: present
    
    - name: Configure Filebeat
      template:
        src: filebeat.yml.j2
        dest: /etc/filebeat/filebeat.yml
      notify: restart filebeat
    
    - name: Enable and start Filebeat
      systemd:
        name: filebeat
        enabled: yes
        state: started

Troubleshooting Tips

Common debugging techniques:

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

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

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

# Verbose output
ansible-playbook playbook.yml -vvv

# Debug specific task
ansible-playbook playbook.yml --tags "debug" -vvv

# List all tasks
ansible-playbook playbook.yml --list-tasks

# List all hosts
ansible-playbook playbook.yml --list-hosts

Conclusion

Ansible is a powerful automation tool that scales from simple tasks to complex multi-tier applications. By following these best practices:

✅ Use roles for modular, reusable code
✅ Store secrets securely with Ansible Vault
✅ Test playbooks with Molecule before production
✅ Leverage dynamic inventory for cloud environments
✅ Implement proper error handling and idempotency
✅ Document your playbooks and roles thoroughly

You’ll build robust, maintainable automation that accelerates your infrastructure operations.

Additional Resources


Have questions or feedback? Feel free to reach out or leave a comment below!

Continue Reading