Devops
DevopsIntermediate

Configuration Management with Ansible: A Comprehensive Guide

DeveloperHat Team
8 min read
AnsibleConfiguration ManagementInfrastructure as CodeAutomation

TL;DR

Learn how to master configuration management with Ansible, including playbooks, roles, best practices, and advanced automation techniques

import { MermaidDiagram } from '@/components/mermaid-diagram'

Learn how to effectively manage infrastructure configuration using Ansible. This comprehensive guide covers key concepts, playbook development, role management, and best practices for automation.

diagram={

graph TB

subgraph "Control Node"

Inventory["Inventory"]

Playbooks["Playbooks"]

Config["ansible.cfg"]

end

subgraph "Managed Nodes"

Linux["Linux Hosts"]

Windows["Windows Hosts"]

Network["Network Devices"]

end

Inventory --> Linux

Inventory --> Windows

Inventory --> Network

Playbooks --> Linux

Playbooks --> Windows

Playbooks --> Network

Config --> Inventory

Config --> Playbooks

style Inventory fill:#3b82f6,stroke:#2563eb,color:white

style Playbooks fill:#3b82f6,stroke:#2563eb,color:white

style Config fill:#3b82f6,stroke:#2563eb,color:white

style Linux fill:#f1f5f9,stroke:#64748b

style Windows fill:#f1f5f9,stroke:#64748b

style Network fill:#f1f5f9,stroke:#64748b

}

/>

$1

Ansible is an agentless automation tool that uses:

1. Declarative Language: YAML-based playbooks

2. Agentless Architecture: SSH/WinRM for communication

3. Idempotency: Consistent state management

4. Extensibility: Modules and plugins

$1

$1

Define your infrastructure inventory:

``ini

inventory/production.ini

[web_servers]

web1.example.com ansible_host=10.0.1.10

web2.example.com ansible_host=10.0.1.11

[db_servers]

db1.example.com ansible_host=10.0.2.10

db2.example.com ansible_host=10.0.2.11

[load_balancers]

lb1.example.com ansible_host=10.0.3.10

lb2.example.com ansible_host=10.0.3.11

[production:children]

web_servers

db_servers

load_balancers

[production:vars]

ansible_user=devops

ansible_ssh_private_key_file=~/.ssh/id_rsa

ansible_become=yes

ansible_become_method=sudo

environment=production

monitoring_enabled=true

backup_enabled=true

`

$1

Configure Ansible behavior:

`ini

ansible.cfg

[defaults]

inventory = ./inventory/production.ini

remote_user = devops

private_key_file = ~/.ssh/id_rsa

host_key_checking = False

forks = 20

timeout = 30

log_path = ./logs/ansible.log

roles_path = ./roles

vault_password_file = ./.vault_pass

retry_files_enabled = True

retry_files_save_path = ./retry/

[privilege_escalation]

become = True

become_method = sudo

become_user = root

become_ask_pass = False

[ssh_connection]

pipelining = True

control_path = /tmp/ansible-ssh-%%h-%%p-%%r

ssh_args = -o ControlMaster=auto -o ControlPersist=60s

[colors]

highlight = white

verbose = blue

warn = bright purple

error = red

debug = dark gray

deprecate = purple

skip = cyan

unreachable = red

ok = green

changed = yellow

diff_add = green

diff_remove = red

diff_lines = cyan

`

$1

Create a comprehensive role structure:

`bash

roles/

├── common/

│ ├── defaults/

│ │ └── main.yml

│ ├── files/

│ │ ├── motd

│ │ └── sshd_config

│ ├── handlers/

│ │ └── main.yml

│ ├── meta/

│ │ └── main.yml

│ ├── tasks/

│ │ ├── main.yml

│ │ ├── packages.yml

│ │ ├── users.yml

│ │ └── security.yml

│ ├── templates/

│ │ ├── ntp.conf.j2

│ │ └── sysctl.conf.j2

│ └── vars/

│ └── main.yml

├── web_server/

│ ├── defaults/

│ │ └── main.yml

│ ├── files/

│ │ ├── nginx.conf

│ │ └── ssl/

│ ├── handlers/

│ │ └── main.yml

│ ├── meta/

│ │ └── main.yml

│ ├── tasks/

│ │ ├── main.yml

│ │ ├── install.yml

│ │ ├── configure.yml

│ │ └── security.yml

│ ├── templates/

│ │ ├── vhost.conf.j2

│ │ └── php-fpm.conf.j2

│ └── vars/

│ └── main.yml

└── db_server/

├── defaults/

│ └── main.yml

├── files/

│ └── my.cnf

├── handlers/

│ └── main.yml

├── meta/

│ └── main.yml

├── tasks/

│ ├── main.yml

│ ├── install.yml

│ ├── configure.yml

│ └── backup.yml

├── templates/

│ └── backup.sh.j2

└── vars/

└── main.yml

`

$1

Create comprehensive playbooks:

`yaml

site.yml

---

  • name: Configure Common Settings
  • hosts: all

    roles:

    - role: common

    tags: ['common', 'security']

  • name: Configure Web Servers
  • hosts: web_servers

    roles:

    - role: web_server

    tags: ['web', 'nginx']

    vars:

    nginx_worker_processes: auto

    nginx_worker_connections: 1024

    php_version: "8.2"

    ssl_enabled: true

    monitoring_enabled: true

  • name: Configure Database Servers
  • hosts: db_servers

    roles:

    - role: db_server

    tags: ['database', 'mysql']

    vars:

    mysql_version: "8.0"

    mysql_root_password: "{{ vault_mysql_root_password }}"

    mysql_databases:

    - name: app_db

    encoding: utf8mb4

    collation: utf8mb4_unicode_ci

    mysql_users:

    - name: app_user

    password: "{{ vault_app_db_password }}"

    priv: "app_db.*:ALL"

    host: "%"

    roles/web_server/tasks/main.yml

    ---

  • name: Include installation tasks
  • ansible.builtin.include_tasks:

    file: install.yml

    apply:

    tags: ['install']

    tags: ['install']

  • name: Include configuration tasks
  • ansible.builtin.include_tasks:

    file: configure.yml

    apply:

    tags: ['configure']

    tags: ['configure']

  • name: Include security tasks
  • ansible.builtin.include_tasks:

    file: security.yml

    apply:

    tags: ['security']

    tags: ['security']

    roles/web_server/tasks/install.yml

    ---

  • name: Install NGINX
  • ansible.builtin.apt:

    name: nginx

    state: present

    update_cache: yes

    notify: restart nginx

  • name: Install PHP and extensions
  • ansible.builtin.apt:

    name:

    - "php{{ php_version }}-fpm"

    - "php{{ php_version }}-mysql"

    - "php{{ php_version }}-curl"

    - "php{{ php_version }}-gd"

    - "php{{ php_version }}-mbstring"

    - "php{{ php_version }}-xml"

    - "php{{ php_version }}-zip"

    state: present

    notify: restart php-fpm

    roles/web_server/tasks/configure.yml

    ---

  • name: Configure NGINX
  • ansible.builtin.template:

    src: nginx.conf.j2

    dest: /etc/nginx/nginx.conf

    owner: root

    group: root

    mode: '0644'

    notify: reload nginx

  • name: Configure virtual hosts
  • ansible.builtin.template:

    src: vhost.conf.j2

    dest: "/etc/nginx/sites-available/{{ item.name }}"

    owner: root

    group: root

    mode: '0644'

    loop: "{{ nginx_vhosts }}"

    notify: reload nginx

  • name: Enable virtual hosts
  • ansible.builtin.file:

    src: "/etc/nginx/sites-available/{{ item.name }}"

    dest: "/etc/nginx/sites-enabled/{{ item.name }}"

    state: link

    loop: "{{ nginx_vhosts }}"

    notify: reload nginx

    roles/web_server/tasks/security.yml

    ---

  • name: Configure SSL
  • ansible.builtin.template:

    src: ssl.conf.j2

    dest: /etc/nginx/conf.d/ssl.conf

    owner: root

    group: root

    mode: '0644'

    when: ssl_enabled | bool

    notify: reload nginx

  • name: Configure security headers
  • ansible.builtin.template:

    src: security-headers.conf.j2

    dest: /etc/nginx/conf.d/security-headers.conf

    owner: root

    group: root

    mode: '0644'

    notify: reload nginx

  • name: Configure firewall
  • ansible.builtin.ufw:

    rule: allow

    port: "{{ item }}"

    proto: tcp

    loop:

    - 80

    - 443

    when: firewall_enabled | bool

    `

    $1

    Organize variables by environment:

    `yaml

    group_vars/all.yml

    ---

    ntp_servers:

    - 0.pool.ntp.org

    - 1.pool.ntp.org

    - 2.pool.ntp.org

    backup_retention_days: 7

    monitoring_interval: 60

    group_vars/web_servers.yml

    ---

    nginx_worker_processes: auto

    nginx_worker_connections: 1024

    php_version: "8.2"

    nginx_vhosts:

    - name: example.com

    server_name: example.com www.example.com

    root: /var/www/example.com

    ssl: true

    ssl_certificate: /etc/letsencrypt/live/example.com/fullchain.pem

    ssl_certificate_key: /etc/letsencrypt/live/example.com/privkey.pem

    group_vars/db_servers.yml

    ---

    mysql_version: "8.0"

    mysql_root_password: "{{ vault_mysql_root_password }}"

    mysql_databases:

    - name: app_db

    encoding: utf8mb4

    collation: utf8mb4_unicode_ci

    mysql_users:

    - name: app_user

    password: "{{ vault_app_db_password }}"

    priv: "app_db.*:ALL"

    host: "%"

    group_vars/production/vault.yml (encrypted)

    ---

    vault_mysql_root_password: supersecret

    vault_app_db_password: appsecret

    `

    $1

    Create dynamic configuration files:

    `jinja

    templates/nginx.conf.j2

    user www-data;

    worker_processes {{ nginx_worker_processes }};

    pid /run/nginx.pid;

    events {

    worker_connections {{ nginx_worker_connections }};

    multi_accept on;

    }

    http {

    include /etc/nginx/mime.types;

    default_type application/octet-stream;

    access_log /var/log/nginx/access.log combined buffer=512k flush=1m;

    error_log /var/log/nginx/error.log warn;

    sendfile on;

    tcp_nopush on;

    tcp_nodelay on;

    keepalive_timeout 65;

    types_hash_max_size 2048;

    ssl_protocols TLSv1.2 TLSv1.3;

    ssl_prefer_server_ciphers off;

    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    include /etc/nginx/conf.d/*.conf;

    include /etc/nginx/sites-enabled/*;

    }

    templates/vhost.conf.j2

    server {

    listen 80;

    listen [::]:80;

    server_name {{ item.server_name }};

    {% if item.ssl | default(false) %}

    return 301 https://$server_name$request_uri;

    }

    server {

    listen 443 ssl http2;

    listen [::]:443 ssl http2;

    server_name {{ item.server_name }};

    ssl_certificate {{ item.ssl_certificate }};

    ssl_certificate_key {{ item.ssl_certificate_key }};

    {% endif %}

    root {{ item.root }};

    index index.php index.html index.htm;

    location / {

    try_files $uri $uri/ /index.php?$args;

    }

    location ~ \.php$ {

    include snippets/fastcgi-php.conf;

    fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    include fastcgi_params;

    }

    location ~ /\.ht {

    deny all;

    }

    }

    `

    $1

    Break down complex tasks:

    `yaml

    roles/common/tasks/main.yml

    ---

  • name: Include package management tasks
  • ansible.builtin.include_tasks:

    file: packages.yml

    apply:

    tags: ['packages']

    tags: ['packages']

  • name: Include user management tasks
  • ansible.builtin.include_tasks:

    file: users.yml

    apply:

    tags: ['users']

    tags: ['users']

  • name: Include security tasks
  • ansible.builtin.include_tasks:

    file: security.yml

    apply:

    tags: ['security']

    tags: ['security']

    roles/common/tasks/packages.yml

    ---

  • name: Update package cache
  • ansible.builtin.apt:

    update_cache: yes

    cache_valid_time: 3600

  • name: Install common packages
  • ansible.builtin.apt:

    name:

    - vim

    - curl

    - wget

    - git

    - htop

    - ntp

    - ufw

    - fail2ban

    state: present

    roles/common/tasks/users.yml

    ---

  • name: Create system users
  • ansible.builtin.user:

    name: "{{ item.name }}"

    shell: "{{ item.shell | default('/bin/bash') }}"

    groups: "{{ item.groups | default([]) }}"

    append: yes

    loop: "{{ system_users }}"

  • name: Add SSH keys
  • ansible.builtin.authorized_key:

    user: "{{ item.name }}"

    key: "{{ item.ssh_key }}"

    state: present

    loop: "{{ system_users }}"

    when: item.ssh_key is defined

    roles/common/tasks/security.yml

    ---

  • name: Configure SSH
  • ansible.builtin.template:

    src: sshd_config.j2

    dest: /etc/ssh/sshd_config

    owner: root

    group: root

    mode: '0600'

    notify: restart ssh

  • name: Configure firewall
  • ansible.builtin.ufw:

    state: enabled

    policy: deny

  • name: Allow SSH
  • ansible.builtin.ufw:

    rule: allow

    port: ssh

    proto: tcp

    `

    $1

    Improve playbook execution:

    `yaml

    ansible.cfg

    [defaults]

    forks = 20

    pipelining = True

    fact_caching = jsonfile

    fact_caching_connection = /tmp/ansible_facts

    fact_caching_timeout = 7200

    gathering = smart

    host_key_checking = False

    [ssh_connection]

    pipelining = True

    ssh_args = -o ControlMaster=auto -o ControlPersist=60s

    site.yml

    ---

  • name: High Performance Configuration
  • hosts: all

    gather_facts: yes

    strategy: free

    serial: "25%"

    vars:

    ansible_forks: 20

    pre_tasks:

    - name: Gather service facts

    ansible.builtin.service_facts:

    tags: ['always']

    roles:

    - role: common

    - role: web_server

    when: "'web_servers' in group_names"

    - role: db_server

    when: "'db_servers' in group_names"

    `

    $1

    Implement testing:

    `yaml

    molecule/default/molecule.yml

    ---

    dependency:

    name: galaxy

    driver:

    name: docker

    platforms:

    - name: instance

    image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible

    command: ${MOLECULE_DOCKER_COMMAND:-""}

    volumes:

    - /sys/fs/cgroup:/sys/fs/cgroup:ro

    privileged: true

    pre_build_image: true

    provisioner:

    name: ansible

    playbooks:

    converge: ${MOLECULE_PLAYBOOK:-converge.yml}

    verifier:

    name: ansible

    molecule/default/verify.yml

    ---

  • name: Verify
  • hosts: all

    gather_facts: false

    tasks:

    - name: Check if NGINX is running

    ansible.builtin.service_facts:

    register: services_state

    - name: Assert NGINX is running

    ansible.builtin.assert:

    that:

    - "'nginx' in services_state.ansible_facts.services"

    - "services_state.ansible_facts.services['nginx.service'].state == 'running'"

    fail_msg: "NGINX is not running"

    success_msg: "NGINX is running"

    - name: Check NGINX configuration

    ansible.builtin.command: nginx -t

    changed_when: false

    register: nginx_conf_test

    - name: Assert NGINX configuration is valid

    ansible.builtin.assert:

    that:

    - nginx_conf_test.rc == 0

    fail_msg: "NGINX configuration is invalid"

    success_msg: "NGINX configuration is valid"

    ``

    $1

    1. Repository Structure

    - Clear organization

    - Role-based design

    - Environment separation

    - Version control

    2. Security

    - Vault integration

    - Key rotation

    - Least privilege

    - Audit logging

    3. Performance

    - Fact caching

    - Parallel execution

    - Task optimization

    - Connection pooling

    4. Testing

    - Molecule testing

    - Syntax validation

    - Integration tests

    - CI/CD integration

    $1

    Effective Ansible implementation requires:

    1. Clear structure

    2. Security focus

    3. Performance optimization

    4. Comprehensive testing

    5. Documentation

    Remember to:

  • Follow best practices
  • Test thoroughly
  • Monitor execution
  • Document changes
  • Review regularly
  • $1

    1. [Ansible Documentation](https://docs.ansible.com/)

    2. [Ansible Galaxy](https://galaxy.ansible.com/)

    3. [Ansible Best Practices](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html)

    4. [Molecule Documentation](https://molecule.readthedocs.io/)

    5. [Ansible Security Guide](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vault.html)

    Why This Matters

    Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.

    Trade-offs to Consider

    Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.

    When NOT to Use This

    Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.

    Decision Framework

    Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.