DevOps

Configuration Management

Learn Ansible for configuration management. Master playbooks, roles, inventory management, and automating server configuration at scale.

By TechCoder TeamLast updated: 2026-06-02
In a Nutshell

Learn Ansible for configuration management. Master playbooks, roles, inventory management, and automating server configuration at scale. This hands-on tutorial focuses on practical implementation of configuration management concepts.

Configuration Management with Ansible

Ansible is an agentless automation tool for configuration management, application deployment, and task automation.

Why Ansible?

┌─────────────────────────────────────────────────────────────────┐
│                     Ansible Philosophy                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ✅ Agentless - Uses SSH/WinRM (no daemon needed)              │
│   ✅ Simple - YAML playbooks, easy to read/write                │
│   ✅ Idempotent - Safe to run multiple times                    │
│   ✅ Powerful - Batteries included with 3000+ modules         │
│   ✅ Extensible - Write custom modules in any language          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Installation

# macOS
brew install ansible

# Ubuntu/Debian
sudo apt update
sudo apt install ansible

# RHEL/CentOS
sudo yum install ansible

# Python pip
pip install ansible

# Verify
ansible --version

Core Concepts

Inventory

Defines the hosts and groups to manage.

# inventory.ini (INI format)
[webservers]
web1.example.com ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/web.pem
web2.example.com ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/web.pem

[dbservers]
db1.example.com ansible_user=ubuntu

[production:children]
webservers
dbservers

[production:vars]
ansible_python_interpreter=/usr/bin/python3
env=production
# inventory.yml (YAML format)
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_user: ubuntu
          ansible_ssh_private_key_file: ~/.ssh/web.pem
        web2.example.com:
          ansible_user: ubuntu
    dbservers:
      hosts:
        db1.example.com:
          ansible_user: ubuntu
    
    production:
      children:
        webservers:
        dbservers:
      vars:
        env: production
        ansible_python_interpreter: /usr/bin/python3

Ad-hoc Commands

# Ping all hosts
ansible all -i inventory.ini -m ping

# Run command
ansible webservers -i inventory.ini -a "uptime"

# Run as sudo
ansible webservers -i inventory.ini -a "apt update" --become

# Copy file
ansible webservers -i inventory.ini -m copy -a "src=app.conf dest=/etc/app.conf"

# Install package
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become

# Manage service
ansible webservers -i inventory.ini -m service -a "name=nginx state=started enabled=yes" --become

# Gather facts
ansible webservers -i inventory.ini -m setup

Playbooks

YAML files defining automation workflows.

Basic Playbook

---
- name: Configure web servers
  hosts: webservers
  become: yes
  
  vars:
    http_port: 80
    max_clients: 200
    
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
      
    - name: Install nginx
      apt:
        name: nginx
        state: present
      
    - name: Copy nginx configuration
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: restart nginx
      
    - name: Create web root directory
      file:
        path: /var/www/html
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
      
    - name: Copy index.html
      copy:
        src: index.html
        dest: /var/www/html/index.html
        owner: www-data
        group: www-data
        mode: '0644'
      
    - name: Ensure nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
  
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

Variables

# group_vars/webservers.yml
---
http_port: 80
max_clients: 200
document_root: /var/www/html
# host_vars/web1.example.com.yml
---
max_clients: 500
env: production
# Using variables in playbook
- name: Deploy application
  hosts: webservers
  vars:
    app_version: "1.0.0"
    
  vars_files:
    - vars/secrets.yml
    
  tasks:
    - name: Print variables
      debug:
        msg: "Deploying version {{ app_version }} to port {{ http_port }}"
        
    - name: Access nested variables
      debug:
        var: users.admin.name
        
    - name: Use facts
      debug:
        msg: "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"

Conditionals and Loops

- name: Install packages by OS
  hosts: all
  tasks:
    - name: Install nginx on Debian
      apt:
        name: nginx
        state: present
      when: ansible_os_family == "Debian"
      
    - name: Install nginx on RedHat
      yum:
        name: nginx
        state: present
      when: ansible_os_family == "RedHat"

- name: Configure multiple users
  hosts: all
  tasks:
    - name: Create users
      user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        shell: "{{ item.shell | default('/bin/bash') }}"
      loop:
        - { name: 'alice', groups: 'sudo', shell: '/bin/zsh' }
        - { name: 'bob', groups: 'developers' }
        - { name: 'carol', groups: 'sudo,developers' }

- name: Conditional task with multiple conditions
  tasks:
    - name: Special config for production web servers
      template:
        src: production.conf.j2
        dest: /etc/app/config.conf
      when:
        - ansible_hostname in groups['webservers']
        - env == 'production'
        - ansible_distribution_major_version | int >= 20

Roles

Reusable, self-contained units of automation.

Role Structure

roles/
└── nginx/
    ├── defaults/
    │   └── main.yml          # Default variables (lowest priority)
    ├── vars/
    │   └── main.yml          # Variables (high priority)
    ├── tasks/
    │   ├── main.yml          # Entry point
    │   ├── install.yml
    │   └── configure.yml
    ├── handlers/
    │   └── main.yml
    ├── templates/
    │   └── nginx.conf.j2
    ├── files/
    │   └── index.html
    ├── meta/
    │   └── main.yml          # Dependencies
    └── README.md

Creating a Role

# roles/nginx/tasks/main.yml
---
- include_tasks: install.yml
- include_tasks: configure.yml

# roles/nginx/tasks/install.yml
---
- name: Install nginx package
  package:
    name: nginx
    state: present
  
- name: Ensure nginx is started
  service:
    name: nginx
    state: started
    enabled: yes

# roles/nginx/tasks/configure.yml
---
- name: Copy nginx configuration
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: reload nginx
  
- name: Create sites directory
  file:
    path: /etc/nginx/sites-enabled
    state: directory
    
- name: Copy site configuration
  template:
    src: site.conf.j2
    dest: /etc/nginx/sites-enabled/default
  notify: reload nginx

# roles/nginx/templates/nginx.conf.j2
user {{ nginx_user }};
worker_processes {{ ansible_processor_vcpus }};
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections | default(1024) }};
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log /var/log/nginx/access.log main;
    
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    
    include /etc/nginx/sites-enabled/*;
}

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

# roles/nginx/defaults/main.yml
---
nginx_user: www-data
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65

Using Roles

# site.yml
---
- name: Configure all servers
  hosts: all
  become: yes
  
  roles:
    - common
    - { role: nginx, when: "'webservers' in group_names" }
    - { role: postgresql, tags: ['database'] }
    - role: application
      vars:
        app_version: "2.0.0"
# Install role from Ansible Galaxy
ansible-galaxy install geerlingguy.nginx

# Install requirements
ansible-galaxy install -r requirements.yml
# requirements.yml
---
roles:
  - name: geerlingguy.nginx
    version: 3.1.0
  - name: geerlingguy.postgresql
    
collections:
  - name: community.general
  - name: amazon.aws

Ansible Vault

Encrypt sensitive data.

# Create encrypted file
ansible-vault create secrets.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# Encrypt existing file
ansible-vault encrypt secrets.yml

# Decrypt file
ansible-vault decrypt secrets.yml

# View encrypted file
ansible-vault view secrets.yml

# Change password
ansible-vault rekey secrets.yml

# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# Encrypted vars file example
# secrets.yml (encrypted)
database_password: supersecret
api_key: abc123xyz

Best Practices

# 1. Use tags for selective execution
- name: Install dependencies
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - python3
    - python3-pip
  tags: ['install', 'setup']

# 2. Use blocks for error handling
- name: Setup application
  block:
    - name: Clone repository
      git:
        repo: https://github.com/example/app.git
        dest: /opt/app
        
    - name: Install dependencies
      pip:
        requirements: /opt/app/requirements.txt
        
  rescue:
    - name: Log failure
      debug:
        msg: "Application setup failed"
        
  always:
    - name: Cleanup temp files
      file:
        path: /tmp/app-setup
        state: absent

# 3. Use handlers for efficiency
- name: Update configuration
  template:
    src: config.j2
    dest: /etc/app/config.conf
  notify: restart app

# 4. Idempotency is key
- name: Ensure user exists
  user:
    name: deploy
    state: present
    # Will not recreate if exists

# 5. Use check mode for dry runs
# ansible-playbook site.yml --check --diff

Quiz

Quiz

Question 1 of 5

What makes Ansible agentless?

It uses SSH/WinRM to connect to managed nodes without requiring agent software
It doesn't require a control machine
It uses a web interface only
It uses SNMP for management

Next Steps

Now let's explore monitoring and logging—the eyes and ears of your infrastructure.