Skip to content

Ansible: architecture and practical techniques

Ansible architecture

What I like in Ansible is simplicity and agentless architecture:

  • agentless model - uses existing SSH (Linux/Unix) or WinRM (Windows) protocols to communicate with managed hosts
  • modular structure - everything is based on declarative YAML files: playbooks, roles and inventory
  • idempotency - operations can be safely repeated without risk of duplicate changes

Role-based project organization

Example of role based structure:

ansible-project/
├── 📁 inventory/
│   ├── development.yml      # Dev environment definition
│   └── production.yml       # Production environment definition
├── 📁 group_vars/
│   ├── all.yml              # Variables common to all groups
│   ├── webservers.yml       # Variables for webservers group
│   └── dbservers.yml        # Variables for dbservers group
├── 📁 host_vars/
│   └── specific-host.yml    # Variables for specific host
├── 📁 roles/
│   ├── 📁 common/              # Common role for all servers
│   │   ├── 📁 tasks/
│   │   │   └── main.yml     # Main role tasks
│   │   ├── 📁 handlers/
│   │   │   └── main.yml     # Handlers
│   │   ├── 📁 templates/
│   │   │   └── ntp.conf.j2  # Configuration file templates
│   │   ├── 📁 files/
│   │   │   └── banner.txt   # Static files
│   │   ├── 📁 vars/
│   │   │   └── main.yml     # Internal role variables
│   │   └── 📁 defaults/
│   │       └── main.yml     # Default variable values
│   ├── 📁 webserver/           # Role for web servers
│   └── 📁 database/            # Role for database servers
├── site.yml                 # Main playbook
├── webservers.yml           # Playbook for web servers
└── dbservers.yml            # Playbook for database servers

Practical techniques

Task delegation

Task delegation allows executing specific operations on a different host than the currently processed one:

- name: Register new server in load balancer
    shell: /usr/local/bin/register_node.sh {{ inventory_hostname }}
    delegate_to: load_balancer8
    # This task will execute on the load_balancer8 host

Collections instead of simple playbooks

Don't reinvent the wheel - use ready-made collections from Ansible Galaxy:

ansible-galaxy collection install community.docker

# Usage in playbook
- name: Run PostgreSQL container
    community.docker.docker_container:
        name: db
        image: postgres:13
        ports:
            - "5432:5432"

🔐 Ansible Vault for sensitive data

# Encrypting password files
ansible-vault encrypt group_vars/all/vault.yml

# Running playbooks with encrypted data
ansible-playbook site.yml --ask-vault-pass

⚡ Performance optimization

- hosts: all
    gather_facts: no  # disable fact gathering when unnecessary
    # or
    gather_subset: network  # gather only selected facts

🕵️ Diff mode

ansible-playbook webservers.yml --diff --check

Combining --diff with --check allows seeing what changes will be made without actually making them.

Execution strategies

- hosts: webservers
    strategy: free  # parallel task execution without synchronization
    # alternatives: linear (default), debug (interactive)
    tasks:
        # tasks...

Error handling and loops

- name: Start services
    service:
        name: "{{ item }}"
        state: started
    loop:
        - nginx
        - redis
        - postgresql
    ignore_errors: yes  # continue despite errors
    register: service_result

- name: Notify about errors
    mail:
        to: "admin@foobar.com"
        subject: "Service startup error"
        body: "There was a problem starting services"
    when: service_result.failed

Advanced role techniques

role dependencies

# roles/webserver/meta/main.yml
dependencies:
    - role: common
    - role: security
        vars:
            security_level: high

Reusable roles with parameters

# playbook.yml
- hosts: webservers
    roles:
        - role: nginx
            vars:
                nginx_port: 80
                ssl_enabled: true

        - role: nginx
            vars:
                nginx_port: 8080
                server_name: "api.foobar.com"

Dynamic inventory

Instead of static host lists, use dynamic inventory for cloud infrastructure:

# AWS EC2
ansible-playbook -i inventory_aws_ec2.yml site.yml

# Azure
ansible-playbook -i inventory_azure_rm.yml site.yml

Control flow - conditional handling

- name: Install distribution-specific packages
    include_tasks: "{{ ansible_os_family | lower }}.yml"
    # automatically loads debian.yml or redhat.yml depending on system

Monitoring and debugging

Variable debugging

- name: Display host information
    debug:
        var: ansible_facts
        verbosity: 1  # displays only with -v or higher level

Callback plugins

# ansible.cfg
[defaults]
callback_whitelist = profile_tasks, timer

This will enable displaying execution time for individual tasks.

🏆 Best practices

  1. maintain small, specialized roles - each role in general should have one specific purpose
  2. test roles in isolation - use molecule for role testing
  3. document variables - use comments in defaults/main.yml files
  4. never store credentials in plain text - always use ansible vault or other vault derivative
  5. use tags for selective execution - ansible-playbook site.yml --tags "configuration,packages"
  6. check changes before deployment - use --check and --diff

Summary

Ansible enables infrastructure automation without excessive complexity. Role-based approach provides modularity, code reuse, and easier management. With task delegation, collections, and execution strategies, you can create flexible and efficient automation solutions.


🔗 Useful resources: