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 5What 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.