Infrastructure as Code (IaC) transforms infrastructure management from manual processes to automated, version-controlled, and reproducible workflows. Ansible, with its agentless architecture and simple YAML syntax, has become a leading choice for configuration management and orchestration. This guide explores advanced Ansible techniques for modern infrastructure automation.
Why Ansible for IaC?
Ansible Advantages
- Agentless: Uses SSH and WinRM, no agents required on managed nodes
- Simple Syntax: YAML-based playbooks are easy to read and write
- Idempotent: Safe to run multiple times, only makes necessary changes
- Extensible: Vast ecosystem of modules and collections
- Multi-Platform: Manages Linux, Windows, network devices, and cloud services
- No Programming Required: Declarative approach suitable for sysadmins
1. Ansible Project Structure
Best Practice Directory Layout
Organize your Ansible project for scalability and maintainability:
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ ├── all.yml
│ │ ├── webservers.yml
│ │ └── databases.yml
│ └── staging/
│ └── hosts.yml
├── playbooks/
│ ├── site.yml
│ ├── webservers.yml
│ ├── databases.yml
│ └── deploy-app.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ ├── postgresql/
│ └── docker/
├── group_vars/
├── host_vars/
├── files/
├── templates/
└── vault/
└── secrets.yml
Configuration File
Optimize ansible.cfg for production use:
[defaults]
inventory = inventory/production/hosts.yml
roles_path = roles
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600
forks = 20
callback_whitelist = profile_tasks, timer
[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
2. Dynamic Inventory
Cloud-Based Dynamic Inventory
Automatically discover infrastructure from cloud providers:
plugin: aws_ec2
regions:
- us-east-1
- us-west-2
filters:
tag:Environment: production
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
- key: tags.Environment
prefix: env
- key: placement.availability_zone
prefix: az
hostnames:
- tag:Name
compose:
ansible_host: public_ip_address
For Azure:
plugin: azure_rm
include_vm_resource_groups:
- production-rg
- staging-rg
auth_source: auto
keyed_groups:
- prefix: tag
key: tags
- prefix: location
key: location
conditional_groups:
webservers: "'web' in tags.role"
databases: "'db' in tags.role"
3. Creating Reusable Roles
NGINX Role Structure
Build a comprehensive NGINX role:
---
- name: Install NGINX
apt:
name: nginx
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Install NGINX (RedHat)
yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Create NGINX directories
file:
path: "{{ item }}"
state: directory
owner: nginx
group: nginx
mode: '0755'
loop:
- /etc/nginx/sites-available
- /etc/nginx/sites-enabled
- /var/www/html
- name: Deploy NGINX configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: restart nginx
- name: Deploy virtual host configs
template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ item.name }}.conf"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_vhosts }}"
notify: reload nginx
- name: Enable virtual hosts
file:
src: "/etc/nginx/sites-available/{{ item.name }}.conf"
dest: "/etc/nginx/sites-enabled/{{ item.name }}.conf"
state: link
loop: "{{ nginx_vhosts }}"
notify: reload nginx
- name: Ensure NGINX is running
service:
name: nginx
state: started
enabled: yes
Role Variables and Defaults
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 20M
nginx_vhosts:
- name: default
listen: 80
server_name: localhost
root: /var/www/html
index: index.html index.htm
ssl: false
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_ssl_ciphers: "HIGH:!aNULL:!MD5"
nginx_gzip: yes
nginx_gzip_types:
- text/plain
- text/css
- application/json
- application/javascript
- text/xml
- application/xml
4. Advanced Playbook Patterns
Multi-Tier Application Deployment
---
- name: Deploy Load Balancers
hosts: loadbalancers
become: yes
roles:
- role: nginx
vars:
nginx_vhosts:
- name: app
listen: 80
proxy_pass: http://app_backend
- name: Deploy Application Servers
hosts: appservers
become: yes
serial: "50%" # Rolling deployment
max_fail_percentage: 25
tasks:
- name: Pull latest code from Git
git:
repo: "{{ app_git_repo }}"
dest: "{{ app_directory }}"
version: "{{ app_version }}"
notify: restart app
- name: Install Python dependencies
pip:
requirements: "{{ app_directory }}/requirements.txt"
virtualenv: "{{ app_directory }}/venv"
- name: Run database migrations
command: "{{ app_directory }}/venv/bin/python manage.py migrate"
run_once: yes
delegate_to: "{{ groups['appservers'][0] }}"
- name: Collect static files
command: "{{ app_directory }}/venv/bin/python manage.py collectstatic --noinput"
handlers:
- name: restart app
systemd:
name: myapp
state: restarted
- name: Configure Database
hosts: databases
become: yes
roles:
- postgresql
tasks:
- name: Create application database
postgresql_db:
name: "{{ db_name }}"
state: present
- name: Create database user
postgresql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
db: "{{ db_name }}"
priv: ALL
Blue-Green Deployment
---
- name: Determine inactive environment
hosts: localhost
gather_facts: no
tasks:
- name: Get current active environment
uri:
url: "http://{{ lb_ip }}/health"
register: health_check
- name: Set deployment target
set_fact:
inactive_env: "{{ 'green' if health_check.json.active == 'blue' else 'blue' }}"
active_env: "{{ health_check.json.active }}"
- name: Deploy to inactive environment
hosts: "{{ hostvars['localhost']['inactive_env'] }}_servers"
become: yes
tasks:
- name: Deploy application
include_role:
name: deploy_app
vars:
environment: "{{ hostvars['localhost']['inactive_env'] }}"
- name: Run health checks
uri:
url: "http://{{ inventory_hostname }}:8080/health"
status_code: 200
register: health
until: health.status == 200
retries: 5
delay: 10
- name: Switch load balancer
hosts: loadbalancers
become: yes
tasks:
- name: Update load balancer configuration
template:
src: lb-config.j2
dest: /etc/nginx/conf.d/app.conf
vars:
active_env: "{{ hostvars['localhost']['inactive_env'] }}"
notify: reload nginx
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
5. Ansible Vault for Secrets
Encrypting Sensitive Data
ansible-vault create vault/secrets.yml
# Encrypt existing file
ansible-vault encrypt group_vars/production/vault.yml
# Edit encrypted file
ansible-vault edit vault/secrets.yml
# Decrypt for viewing
ansible-vault view vault/secrets.yml
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
# Using password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Vault file structure:
---
vault_db_password: "supersecret123"
vault_api_key: "abc123def456"
vault_ssl_private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
-----END PRIVATE KEY-----
# group_vars/all/vars.yml (unencrypted)
---
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
ssl_private_key: "{{ vault_ssl_private_key }}"
6. Testing and Validation
Ansible Molecule for Role Testing
pip install molecule molecule-docker
# Initialize new role with tests
molecule init role my_role --driver-name docker
# Test sequence
molecule create # Create test instance
molecule converge # Run playbook
molecule verify # Run tests
molecule destroy # Clean up
# Full test cycle
molecule test
Test scenario example:
---
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Check if NGINX is installed
package:
name: nginx
state: present
check_mode: yes
register: nginx_installed
failed_when: nginx_installed.changed
- name: Check if NGINX is running
service:
name: nginx
state: started
check_mode: yes
register: nginx_running
failed_when: nginx_running.changed
- name: Test NGINX responds
uri:
url: http://localhost
status_code: 200
register: nginx_response
Ansible-lint for Code Quality
pip install ansible-lint
# Lint playbooks
ansible-lint playbooks/site.yml
# Lint entire project
ansible-lint
# Configuration file .ansible-lint
---
skip_list:
- '306' # Skip specific rule
warn_list:
- experimental
exclude_paths:
- .cache/
- test/
- molecule/
parseable: true
quiet: false
verbosity: 1
7. Performance Optimization
Optimization Techniques
[ssh_connection]
pipelining = True
# Increase forks for parallel execution
[defaults]
forks = 50
# Use fact caching
gathering = smart
fact_caching = redis
fact_caching_connection = localhost:6379:0
fact_caching_timeout = 86400
# Disable cowsay
nocows = 1
# Use mitogen for faster execution
strategy_plugins = /usr/share/ansible/plugins/mitogen/plugins/strategy
strategy = mitogen_linear
Async Tasks for Long-Running Operations
command: /usr/bin/backup-database.sh
async: 3600 # Max runtime
poll: 0 # Fire and forget
register: backup_job
- name: Check backup status later
async_status:
jid: "{{ backup_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 60
8. CI/CD Integration
GitLab CI Pipeline
stages:
- lint
- test
- deploy
lint:playbooks:
stage: lint
image: cytopia/ansible-lint
script:
- ansible-lint playbooks/
test:roles:
stage: test
image: quay.io/ansible/molecule
script:
- cd roles/nginx
- molecule test
deploy:staging:
stage: deploy
image: ansible/ansible:latest
script:
- ansible-playbook -i inventory/staging site.yml
only:
- develop
environment:
name: staging
deploy:production:
stage: deploy
image: ansible/ansible:latest
script:
- ansible-playbook -i inventory/production site.yml
only:
- main
when: manual
environment:
name: production
Best Practices Checklist
- ✓ Use version control for all Ansible code
- ✓ Organize projects with standard directory structure
- ✓ Create reusable roles with proper defaults
- ✓ Use Ansible Vault for sensitive data
- ✓ Implement idempotency in all tasks
- ✓ Tag tasks for selective execution
- ✓ Document variables and role requirements
- ✓ Test roles with Molecule before deployment
- ✓ Use dynamic inventory for cloud environments
- ✓ Implement proper error handling
- ✓ Enable fact caching for performance
- ✓ Use handlers for service restarts
- ✓ Implement CI/CD pipelines
- ✓ Regular security audits of playbooks
Conclusion
Ansible provides a powerful, flexible platform for infrastructure automation that scales from small projects to enterprise deployments. By following infrastructure as code principles, organizing code properly, testing thoroughly, and integrating with CI/CD pipelines, teams can achieve reliable, repeatable infrastructure management. The key is starting with well-structured roles, building comprehensive playbooks, and continuously refining automation as infrastructure evolves.
Additional Resources
- → Ansible Galaxy for community roles
- → Ansible Automation Platform for enterprise features
- → AWX for open-source web UI
- → Ansible collections for extended functionality