All posts

Ansible Automation: Complete Configuration Management Guide

· Pavan Y N · ansible, automation, configuration-management, devops

Published: October 30, 2025
Reading Time: 24 minutes
Tags: Ansible, Automation, Configuration Management, DevOps, IaC

# Introduction

Ansible is a powerful open-source automation tool that simplifies configuration management, application deployment, and task automation. With its agentless architecture and simple YAML syntax, Ansible has become the go-to choice for IT automation.

This comprehensive guide covers Ansible fundamentals, playbook development, best practices, and real-world automation scenarios.

# What is Ansible?

Ansible is an agentless automation platform that uses SSH to configure systems, deploy applications, and orchestrate complex workflows.

## Key Features

  • →Agentless: No software to install on managed nodes
  • →Simple: YAML-based playbooks, easy to read and write
  • →Powerful: Manage thousands of nodes simultaneously
  • →Idempotent: Safe to run multiple times
  • →Extensible: 3000+ modules and growing

## Ansible Architecture

code
┌──────────────────┐
│  Control Node    │  (Where Ansible runs)
│  - Ansible CLI   │
│  - Playbooks     │
│  - Inventory     │
└────────┬─────────┘
         │
         │ SSH/WinRM
         │
    ┌────┴────┬────────┬────────┐
    │         │        │        │
┌───▼───┐ ┌──▼───┐ ┌──▼───┐ ┌──▼───┐
│ Node 1│ │Node 2│ │Node 3│ │Node N│
│(Linux)│ │(Linux)│ │(Win) │ │(...) │
└───────┘ └──────┘ └──────┘ └──────┘

# Installation and Setup

## Installing Ansible

code
# Ubuntu/Debian
sudo apt update
sudo apt install ansible

# RHEL/CentOS
sudo yum install ansible

# macOS
brew install ansible

# Python pip
pip install ansible

# Verify installation
ansible --version

## Initial Configuration

ansible.cfg:

code
[defaults]
inventory = ./inventory
remote_user = ansible
private_key_file = ~/.ssh/ansible_key
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400

[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
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

## Inventory Management

Static Inventory (inventory/hosts.ini):

code
[webservers]
web1.example.com ansible_host=192.168.1.10
web2.example.com ansible_host=192.168.1.11
web3.example.com ansible_host=192.168.1.12

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

[loadbalancers]
lb1.example.com ansible_host=192.168.1.30

[production:children]
webservers
databases
loadbalancers

[production:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
environment=production

Dynamic Inventory (AWS):

code
# aws_ec2.yml
plugin: aws_ec2
regions:
  - us-east-1
  - us-west-2
filters:
  tag:Environment: production
  instance-state-name: running
keyed_groups:
  - key: tags.Role
    prefix: role
  - key: tags.Environment
    prefix: env
  - key: placement.availability_zone
    prefix: az
hostnames:
  - tag:Name
  - private-ip-address
compose:
  ansible_host: public_ip_address
code
# Test dynamic inventory
ansible-inventory -i aws_ec2.yml --list
ansible-inventory -i aws_ec2.yml --graph

# Ansible Playbooks

## Basic Playbook Structure

code
---
- name: Configure web servers
  hosts: webservers
  become: yes
  vars:
    app_name: myapp
    app_port: 8080
  
  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: /opt/{{ app_name }}
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
    
    - name: Copy application files
      copy:
        src: files/app/
        dest: /opt/{{ app_name }}/
        owner: www-data
        group: www-data
        mode: '0644'
      notify: Restart nginx
    
    - name: Configure nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
      notify: Restart nginx
    
    - name: Enable nginx site
      file:
        src: /etc/nginx/sites-available/{{ app_name }}
        dest: /etc/nginx/sites-enabled/{{ app_name }}
        state: link
      notify: Restart nginx
    
    - name: Ensure nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
  
  handlers:
    - name: Restart nginx
      service:
        name: nginx
        state: restarted

## Advanced Playbook Patterns

1. Roles:

code
roles/
└── webserver/
    ├── tasks/
    │   ├── main.yml
    │   ├── install.yml
    │   └── configure.yml
    ├── handlers/
    │   └── main.yml
    ├── templates/
    │   └── nginx.conf.j2
    ├── files/
    │   └── index.html
    ├── vars/
    │   └── main.yml
    ├── defaults/
    │   └── main.yml
    ├── meta/
    │   └── main.yml
    └── README.md

roles/webserver/tasks/main.yml:

code
---
- name: Include OS-specific variables
  include_vars: "{{ ansible_os_family }}.yml"

- name: Install packages
  include_tasks: install.yml

- name: Configure web server
  include_tasks: configure.yml

- name: Ensure web server is running
  service:
    name: "{{ web_server_service }}"
    state: started
    enabled: yes

Using Roles:

code
---
- name: Deploy web application
  hosts: webservers
  roles:
    - role: common
      tags: common
    - role: webserver
      tags: webserver
    - role: monitoring
      tags: monitoring

2. Conditionals:

code
---
- name: Install packages based on OS
  hosts: all
  tasks:
    - name: Install Apache on Debian
      apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"
    
    - name: Install Apache on RedHat
      yum:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"
    
    - name: Configure firewall for production
      firewalld:
        service: http
        permanent: yes
        state: enabled
      when:
        - ansible_os_family == "RedHat"
        - environment == "production"
    
    - name: Install development tools
      apt:
        name:
          - build-essential
          - gdb
        state: present
      when: environment != "production"

3. Loops:

code
---
- name: Create multiple users
  hosts: all
  vars:
    users:
      - name: alice
        uid: 1001
        groups: ['developers', 'docker']
      - name: bob
        uid: 1002
        groups: ['developers']
      - name: charlie
        uid: 1003
        groups: ['ops', 'docker']
  
  tasks:
    - name: Create user groups
      group:
        name: "{{ item }}"
        state: present
      loop:
        - developers
        - ops
        - docker
    
    - name: Create users
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        groups: "{{ item.groups }}"
        shell: /bin/bash
        create_home: yes
      loop: "{{ users }}"
    
    - name: Add SSH keys
      authorized_key:
        user: "{{ item.name }}"
        key: "{{ lookup('file', 'files/ssh_keys/' + item.name + '.pub') }}"
      loop: "{{ users }}"

4. Blocks and Error Handling:

code
---
- name: Deploy application with error handling
  hosts: webservers
  tasks:
    - name: Deployment block
      block:
        - name: Stop application
          service:
            name: myapp
            state: stopped
        
        - name: Backup current version
          archive:
            path: /opt/myapp
            dest: /backup/myapp-{{ ansible_date_time.iso8601 }}.tar.gz
        
        - name: Deploy new version
          unarchive:
            src: files/myapp-v2.0.tar.gz
            dest: /opt/myapp
        
        - name: Run database migrations
          command: /opt/myapp/migrate.sh
        
        - name: Start application
          service:
            name: myapp
            state: started
      
      rescue:
        - name: Restore from backup
          unarchive:
            src: "{{ backup_file }}"
            dest: /opt/myapp
        
        - name: Start application
          service:
            name: myapp
            state: started
        
        - name: Send failure notification
          mail:
            to: ops@example.com
            subject: "Deployment failed on {{ inventory_hostname }}"
            body: "Deployment failed and was rolled back"
      
      always:
        - name: Clean up temporary files
          file:
            path: /tmp/deployment
            state: absent

5. Templates (Jinja2):

templates/nginx.conf.j2:

code
upstream {{ app_name }}_backend {
{% for host in groups['webservers'] %}
    server {{ hostvars[host]['ansible_default_ipv4']['address'] }}:{{ app_port }};
{% endfor %}
}

server {
    listen 80;
    server_name {{ server_name }};

{% if ssl_enabled %}
    listen 443 ssl http2;
    ssl_certificate {{ ssl_cert_path }};
    ssl_certificate_key {{ ssl_key_path }};
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
{% endif %}

    location / {
        proxy_pass http://{{ app_name }}_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }

{% if environment == 'production' %}
    # Production-specific configuration
    client_max_body_size 100M;
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
{% endif %}
}

# Ansible Modules

## Common Modules

1. System Modules:

code
# User management
- name: Create user
  user:
    name: deploy
    shell: /bin/bash
    groups: sudo
    append: yes

# Service management
- name: Manage service
  service:
    name: nginx
    state: started
    enabled: yes

# Cron jobs
- name: Add cron job
  cron:
    name: "Backup database"
    minute: "0"
    hour: "2"
    job: "/usr/local/bin/backup.sh"

2. Package Management:

code
# APT (Debian/Ubuntu)
- name: Install packages
  apt:
    name:
      - nginx
      - postgresql
    state: present
    update_cache: yes

# YUM (RHEL/CentOS)
- name: Install packages
  yum:
    name:
      - httpd
      - mariadb-server
    state: present

# Pip
- name: Install Python packages
  pip:
    name:
      - django
      - celery
    virtualenv: /opt/myapp/venv

3. File Operations:

code
# Copy files
- name: Copy configuration
  copy:
    src: files/config.yml
    dest: /etc/myapp/config.yml
    owner: root
    group: root
    mode: '0644'
    backup: yes

# Template files
- name: Generate config from template
  template:
    src: templates/app.conf.j2
    dest: /etc/myapp/app.conf

# Create directories
- name: Create directory structure
  file:
    path: /opt/myapp/{{ item }}
    state: directory
    owner: www-data
    mode: '0755'
  loop:
    - logs
    - data
    - tmp

# Download files
- name: Download file
  get_url:
    url: https://example.com/file.tar.gz
    dest: /tmp/file.tar.gz
    checksum: sha256:abc123...

4. Cloud Modules:

code
# AWS EC2
- name: Launch EC2 instance
  ec2_instance:
    name: web-server
    key_name: my-key
    instance_type: t3.micro
    image_id: ami-0c55b159cbfafe1f0
    region: us-east-1
    vpc_subnet_id: subnet-12345678
    security_group: web-sg
    tags:
      Environment: production
      Role: webserver

# AWS S3
- name: Upload to S3
  aws_s3:
    bucket: my-bucket
    object: /backups/db-backup.sql
    src: /tmp/db-backup.sql
    mode: put
    encrypt: yes

# Azure VM
- name: Create Azure VM
  azure_rm_virtualmachine:
    resource_group: myResourceGroup
    name: myVM
    vm_size: Standard_B2s
    admin_username: azureuser
    ssh_password_enabled: false
    ssh_public_keys:
      - path: /home/azureuser/.ssh/authorized_keys
        key_data: "{{ ssh_public_key }}"

5. Docker Modules:

code
# Docker container
- name: Run Docker container
  docker_container:
    name: myapp
    image: myregistry.azurecr.io/myapp:latest
    state: started
    restart_policy: always
    ports:
      - "8080:8080"
    env:
      DATABASE_URL: "postgresql://db:5432/myapp"
      REDIS_URL: "redis://redis:6379"
    volumes:
      - /opt/myapp/data:/data
    networks:
      - name: app-network

# Docker image
- name: Build Docker image
  docker_image:
    name: myapp
    tag: latest
    source: build
    build:
      path: /opt/myapp
      dockerfile: Dockerfile
    push: yes
    repository: myregistry.azurecr.io/myapp

# Ansible Vault

## Encrypting Sensitive Data

code
# 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

# View encrypted file
ansible-vault view secrets.yml

# Change vault password
ansible-vault rekey secrets.yml

Using Vault in Playbooks:

code
---
- name: Deploy with secrets
  hosts: webservers
  vars_files:
    - vars/common.yml
    - vars/secrets.yml  # Encrypted file
  
  tasks:
    - name: Configure database connection
      template:
        src: templates/database.yml.j2
        dest: /etc/myapp/database.yml
      no_log: true  # Don't log sensitive data

Running with Vault:

code
# Prompt for password
ansible-playbook deploy.yml --ask-vault-pass

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

# Use multiple vault IDs
ansible-playbook deploy.yml --vault-id prod@~/.vault_pass_prod

# STAR Approach: Ansible Implementation

Situation: Company managing 500+ servers manually, taking 2-3 days for configuration changes and frequent inconsistencies.

Task: Implement Ansible to automate configuration management, reduce deployment time, and ensure consistency.

Action:

  1. •

    Infrastructure Audit:

    • →Documented all server configurations
    • →Identified common patterns and variations
    • →Created inventory of all systems
  2. •

    Ansible Setup:

    • →Installed Ansible on control nodes
    • →Configured SSH key-based authentication
    • →Set up dynamic inventory for AWS and Azure
    • →Implemented Ansible Tower for GUI and RBAC
  3. •

    Playbook Development:

    • →Created 50+ reusable roles
    • →Implemented standardized server baseline
    • →Developed application deployment playbooks
    • →Added comprehensive error handling
  4. •

    Security Implementation:

    • →Encrypted all sensitive data with Ansible Vault
    • →Implemented role-based access control
    • →Added audit logging for all changes
    • →Integrated with HashiCorp Vault
  5. •

    CI/CD Integration:

    • →Automated playbook testing with Molecule
    • →Integrated with Jenkins for automated deployments
    • →Implemented GitOps workflow
    • →Added Slack notifications

Result:

  • →Configuration deployment time reduced from days to 15 minutes
  • →Configuration drift eliminated (100% compliance)
  • →Server provisioning automated (30 minutes vs 2 days)
  • →Reduced configuration errors by 95%
  • →Enabled self-service for developers
  • →Improved disaster recovery time by 80%

# Common Issues & Troubleshooting

## Issue 1: SSH Connection Failures

Symptoms:

  • →"Permission denied" errors
  • →Connection timeouts

Diagnosis:

code
# Test SSH connection
ansible all -m ping -vvv

# Check SSH configuration
ssh -vvv user@host

Solutions:

code
# ansible.cfg
[defaults]
host_key_checking = False
timeout = 30

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

## Issue 2: Privilege Escalation Failures

Symptoms:

  • →"sudo: a password is required" errors
  • →Permission denied on system operations

Solutions:

code
# In playbook
- name: Task requiring sudo
  apt:
    name: nginx
    state: present
  become: yes
  become_method: sudo
  become_user: root
code
# Run with password prompt
ansible-playbook deploy.yml --ask-become-pass

## Issue 3: Idempotency Issues

Symptoms:

  • →Tasks always report "changed"
  • →Unnecessary operations on each run

Solutions:

code
# Use creates parameter
- name: Download file
  get_url:
    url: https://example.com/file.tar.gz
    dest: /tmp/file.tar.gz
  args:
    creates: /tmp/file.tar.gz

# Check before action
- name: Check if service exists
  stat:
    path: /etc/systemd/system/myapp.service
  register: service_file

- name: Install service
  template:
    src: myapp.service.j2
    dest: /etc/systemd/system/myapp.service
  when: not service_file.stat.exists

# Best Practices

## 1. Playbook Organization

  • →Use roles for reusability
  • →Keep playbooks simple and focused
  • →Use meaningful names
  • →Document complex logic

## 2. Variables

  • →Use group_vars and host_vars
  • →Encrypt sensitive data with Vault
  • →Use defaults for optional variables
  • →Validate variable types

## 3. Idempotency

  • →Design tasks to be idempotent
  • →Use appropriate modules
  • →Test playbooks multiple times
  • →Avoid shell/command when possible

## 4. Error Handling

  • →Use blocks for error handling
  • →Implement rescue and always
  • →Add meaningful error messages
  • →Log important operations

## 5. Testing

  • →Test in non-production first
  • →Use check mode (--check)
  • →Implement automated testing
  • →Validate with Molecule

# Conclusion

Ansible has transformed configuration management and automation by providing a simple yet powerful platform that doesn't require agents. Its declarative approach and extensive module library make it ideal for managing infrastructure at any scale.

Whether automating server configuration, deploying applications, or orchestrating complex workflows, Ansible provides the tools needed to build reliable, repeatable automation.

# Additional Resources


Ready to automate with Ansible? Start with simple tasks and gradually build more complex playbooks as you learn!