Ansible configuration management for efficient infrastructure automation

Ansible Configuration Complete Guide

14 min read

Introduction to Ansible

Ansible is a powerful open-source automation tool that simplifies configuration management, application deployment, and orchestration. It uses a simple YAML-based language (Ansible Playbooks) and requires no agents on remote nodes, making it easy to learn and deploy.

Ansible Architecture and Components

Core Components

  • Control Node: Machine where Ansible is installed
  • Managed Nodes: Servers being managed by Ansible
  • Inventory: List of managed nodes
  • Playbooks: Automation scripts written in YAML
  • Modules: Units of work Ansible executes
  • Plugins: Extend Ansible's core functionality

Ansible Installation and Setup

Installing Ansible

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

# RHEL/CentOS
sudo dnf install epel-release
sudo dnf install ansible

# macOS
brew install ansible

# Using Python pip
python3 -m pip install --user ansible

# Verify installation
ansible --version

Basic Configuration File

# ansible.cfg - Main configuration file
[defaults]
inventory = ./inventory
host_key_checking = False
remote_user = ansible-user
private_key_file = ~/.ssh/ansible_key
roles_path = ./roles
retry_files_enabled = False
stdout_callback = yaml

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

Inventory Management

Static Inventory

# inventory file
# Group definitions
[web_servers]
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

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

[load_balancers]
lb1.example.com ansible_host=192.168.1.30

# Group of groups
[production:children]
web_servers
db_servers
load_balancers

# Variables for groups
[web_servers:vars]
http_port=80
max_clients=200

[db_servers:vars]
db_port=5432
db_name=production

Dynamic Inventory

# AWS EC2 dynamic inventory
# Install boto3
pip install boto3

# ec2.py script (from Ansible GitHub)
wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/ec2.py
chmod +x ec2.py

# ec2.ini configuration
[ec2]
regions = us-east-1,us-west-2
regions_exclude =
destination_variable = private_ip_address
vpc_destination_variable = private_ip_address
route53 = False
...

# Using dynamic inventory
ansible-inventory -i ec2.py --list

Ansible Playbooks

Basic Playbook Structure

# site.yml - Main playbook
---
- name: Configure web servers
  hosts: web_servers
  become: yes
  vars:
    http_port: 80
    max_clients: 200
  tasks:
    - name: Ensure nginx is installed
      apt:
        name: nginx
        state: present
        update_cache: yes

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

    - name: Copy nginx configuration
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

Advanced Playbook with Roles

# advanced-setup.yml
---
- name: Configure infrastructure
  hosts: all
  become: yes
  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
      when: ansible_os_family == "Debian"

  roles:
    - role: common
      tags: common
    - role: nginx
      tags: web
    - role: postgresql
      tags: database

  post_tasks:
    - name: Print completion message
      debug:
        msg: "Infrastructure configuration completed"

- name: Deploy application
  hosts: web_servers
  become: yes
  roles:
    - role: app_deployment
      app_version: "1.2.3"

Ansible Roles

Role Structure

# Creating a role structure
ansible-galaxy init nginx_role

# Role directory structure
nginx_role/
├── defaults/
│   └── main.yml          # Default variables
├── files/                # Static files
├── handlers/
│   └── main.yml          # Handlers
├── meta/
│   └── main.yml          # Role metadata
├── README.md
├── tasks/
│   └── main.yml          # Main tasks
├── templates/            # Jinja2 templates
└── vars/
    └── main.yml          # Role variables

Example Nginx Role

# roles/nginx/tasks/main.yml
---
- name: Install nginx
  package:
    name: nginx
    state: present

- name: Configure nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    backup: yes
  notify: restart nginx

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

# roles/nginx/handlers/main.yml
---
- name: restart nginx
  service:
    name: nginx
    state: restarted

# roles/nginx/defaults/main.yml
---
nginx_port: 80
nginx_worker_processes: auto
nginx_worker_connections: 1024

Variables and Facts

Variable Precedence

# Variable precedence (lowest to highest):
# 1. Role defaults
# 2. Inventory variables
# 3. Playbook group_vars
# 4. Playbook host_vars
# 5. Host facts
# 6. Play variables
# 7. Task variables
# 8. Extra variables (-e)

# group_vars/all.yml
ntp_servers:
  - 0.pool.ntp.org
  - 1.pool.ntp.org
  - 2.pool.ntp.org

timezone: UTC

# group_vars/web_servers.yml
http_port: 80
app_environment: production

# host_vars/web1.example.com.yml
http_port: 8080
custom_domain: app1.example.com

Using Facts

# Gathering and using facts
- name: Gather facts
  setup:

- name: Print system information
  debug:
    msg: "This is {{ ansible_hostname }} running {{ ansible_distribution }} {{ ansible_distribution_version }}"

- name: Configure based on OS
  block:
    - name: Install packages on Debian
      apt:
        name: "{{ packages }}"
        state: present
      when: ansible_os_family == "Debian"

    - name: Install packages on RedHat
      yum:
        name: "{{ packages }}"
        state: present
      when: ansible_os_family == "RedHat"

  vars:
    packages:
      - curl
      - wget
      - vim

Templates and Jinja2

Jinja2 Template Examples

# templates/nginx.conf.j2
# Nginx configuration template
user www-data;
worker_processes {{ nginx_worker_processes }};
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections }};
}

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

    # Virtual Host configurations
    {% for domain in nginx_domains %}
    server {
        listen {{ nginx_port }};
        server_name {{ domain.name }};
        root {{ domain.root }};
        
        {% if domain.ssl_enabled %}
        listen 443 ssl;
        ssl_certificate {{ domain.ssl_cert }};
        ssl_certificate_key {{ domain.ssl_key }};
        {% endif %}
    }
    {% endfor %}
}

Template with Conditionals

# templates/config.xml.j2

    
        {{ server_name }}
        {{ server_port }}
        {% if enable_ssl %}
        true
        {{ ssl_cert_path }}
        {% else %}
        false
        {% endif %}
    
    
    
        {{ db_host }}
        {{ db_name }}
        {{ db_user }}
        {% if db_password is defined %}
        {{ db_password }}
        {% endif %}
    
    
    {# This is a Jinja2 comment #}
    

Ansible Vault for Secrets

Encrypting Sensitive Data

# Create encrypted file
ansible-vault create secrets.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# View encrypted file
ansible-vault view secrets.yml

# Encrypt existing file
ansible-vault encrypt secrets.yml

# Decrypt file
ansible-vault decrypt secrets.yml

# Example encrypted file
$ANSIBLE_VAULT;1.1;AES256
65346334633132386530343861343838636535336264643861323238386332326564
34383438636530386134386134383863653533626464386132323838633232656434
...

Using Vault in Playbooks

# Playbook using vault
---
- name: Configure database with secrets
  hosts: db_servers
  become: yes
  vars_files:
    - secrets.yml
  tasks:
    - name: Create database user
      postgresql_user:
        name: "{{ db_user }}"
        password: "{{ db_password }}"
        state: present

    - name: Create database
      postgresql_db:
        name: "{{ db_name }}"
        owner: "{{ db_user }}"
        state: present

# Running playbook with vault
ansible-playbook site.yml --ask-vault-pass
# or
ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt

Advanced Playbook Techniques

Error Handling and Retries

# Error handling examples
- name: Attempt to restart service
  service:
    name: myapp
    state: restarted
  ignore_errors: yes
  register: result
  until: result is succeeded
  retries: 3
  delay: 10

- name: Check if previous task failed
  fail:
    msg: "Service restart failed after multiple attempts"
  when: result is failed

# Blocks for error handling
- name: Configure application
  block:
    - name: Install dependencies
      package:
        name: "{{ item }}"
        state: present
      loop: "{{ dependencies }}"

    - name: Deploy application
      copy:
        src: app.tar.gz
        dest: /opt/app/

    - name: Start application
      service:
        name: myapp
        state: started

  rescue:
    - name: Handle configuration failure
      debug:
        msg: "Application configuration failed, rolling back"

    - name: Remove partially installed app
      file:
        path: /opt/app/
        state: absent

  always:
    - name: Always cleanup temporary files
      file:
        path: /tmp/deploy.lock
        state: absent

Conditionals and Loops

# Complex conditionals
- name: Install packages based on multiple conditions
  package:
    name: "{{ item }}"
    state: present
  loop: "{{ packages }}"
  when: >
    (ansible_distribution == "Ubuntu" and ansible_distribution_major_version == "20") or
    (ansible_distribution == "CentOS" and ansible_distribution_major_version == "8")

# Loops with dictionaries
- name: Create users
  user:
    name: "{{ item.key }}"
    comment: "{{ item.value.name }}"
    groups: "{{ item.value.groups }}"
    shell: "{{ item.value.shell | default('/bin/bash') }}"
  loop: "{{ users | dict2items }}"
  when: item.value.state | default('present') == 'present'

# Variables for the above
users:
  alice:
    name: Alice Developer
    groups: developers
    shell: /bin/zsh
  bob:
    name: Bob Admin
    groups: admins
    state: absent

Ansible Galaxy and Collections

Using Ansible Galaxy

# Search for roles
ansible-galaxy search nginx

# Install role
ansible-galaxy install geerlingguy.nginx

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

- src: geerlingguy.mysql
  version: 3.3.0

- src: https://github.com/example/custom-role.git
  version: main
  name: custom_role

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

Ansible Collections

# collections/requirements.yml
---
collections:
  - name: ansible.posix
    version: 1.3.0
  - name: community.general
    version: 4.8.0
  - name: amazon.aws
    version: 3.2.0

# Install collections
ansible-galaxy collection install -r collections/requirements.yml

# Using collections in playbooks
- name: Configure AWS resources
  hosts: localhost
  collections:
    - amazon.aws
  tasks:
    - name: Create S3 bucket
      amazon.aws.s3_bucket:
        name: my-bucket
        state: present

Best Practices

Project Structure

# Recommended project structure
ansible-project/
├── ansible.cfg
├── inventory/
│   ├── production
│   ├── staging
│   └── development
├── group_vars/
│   ├── all.yml
│   ├── web_servers.yml
│   └── db_servers.yml
├── host_vars/
│   ├── web1.example.com.yml
│   └── db1.example.com.yml
├── roles/
│   ├── common/
│   ├── nginx/
│   └── postgresql/
├── playbooks/
│   ├── site.yml
│   ├── webservers.yml
│   └── databases.yml
├── files/
├── templates/
└── scripts/

Security Best Practices

# Security recommendations
1. Use Ansible Vault for all secrets
2. Limit privilege escalation when possible
3. Use SSH keys instead of passwords
4. Regularly update Ansible and roles
5. Use become_user instead of direct root login
6. Validate and sanitize all inputs
7. Use checksums for file integrity
8. Implement proper SSH hardening

# Example SSH configuration in ansible.cfg
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
control_path = ~/.ssh/ansible-%%r@%%h:%%p
pipelining = True

Performance Optimization

Optimization Techniques

# ansible.cfg optimizations
[defaults]
# Enable pipelining for faster execution
pipelining = True

# Disable gathering facts if not needed
gathering = smart

# Use persistent connections
[persistent_connection]
connect_timeout = 30
command_timeout = 30

# SSH optimizations
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
control_path = ~/.ssh/ansible-%%r@%%h:%%p

# Playbook optimizations
- name: Optimized playbook
  hosts: web_servers
  gather_facts: no  # Skip if not needed
  serial: 5        # Run on 5 hosts at a time
  strategy: free   # Allow hosts to run independently
  tasks:
    - name: Gather facts only if needed
      setup:
      when: gather_facts | default(false) | bool

Testing and Validation

Playbook Testing

# Syntax checking
ansible-playbook --syntax-check site.yml

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

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

# Run with tags
ansible-playbook --tags "nginx,ssl" site.yml

# Using ansible-lint for code quality
pip install ansible-lint
ansible-lint site.yml

# Using molecule for role testing
pip install molecule[docker]
cd roles/nginx
molecule test

Real-World Examples

Complete Web Server Setup

# playbooks/webserver-setup.yml
---
- name: Configure web server
  hosts: web_servers
  become: yes
  vars:
    app_name: myapp
    app_version: 1.0.0
    domain: example.com

  tasks:
    - name: Include common role
      include_role:
        name: common

    - name: Include nginx role
      include_role:
        name: nginx
      vars:
        nginx_sites:
          - name: "{{ app_name }}"
            domain: "{{ domain }}"
            root: "/var/www/{{ app_name }}"
            ssl_enabled: true

    - name: Deploy application
      unarchive:
        src: "https://releases.example.com/{{ app_name }}-{{ app_version }}.tar.gz"
        dest: /var/www/
        remote_src: yes
        owner: www-data
        group: www-data

    - name: Set up log rotation
      copy:
        src: files/logrotate-app
        dest: /etc/logrotate.d/{{ app_name }}
        owner: root
        group: root
        mode: 0644

Conclusion

Ansible provides a powerful, agentless approach to configuration management and automation. Its simple YAML-based syntax, extensive module library, and strong community support make it an excellent choice for DevOps teams of all sizes.

Key Benefits of Ansible:

  • Agentless architecture reduces complexity
  • Simple YAML syntax is easy to learn and read
  • Idempotent operations ensure consistent results
  • Extensive module library covers most use cases
  • Strong community and enterprise support
  • Integrates well with existing DevOps tools

By following best practices for project structure, security, and performance optimization, teams can build robust, maintainable automation solutions that scale with their infrastructure needs.

© 2025 DevOps Blog - Ansible Configuration Guide

Comments

Popular posts from this blog

Real-world Terraform scenarios to test and improve your Infrastructure as Code skills

Azure Kubernetes Service (AKS) Complete Guide

Automate Your DevOps Documentation: `iac-to-docs` Lands on PyPI with AI Power