Ansible configuration management for efficient infrastructure automation
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 %}{# This is a Jinja2 comment #} {{ db_host }} {{ db_name }} {{ db_user }} {% if db_password is defined %}{{ db_password }} {% endif %}
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.
Comments
Post a Comment