Advanced Features and Next Steps
Explore advanced Ansible capabilities and discover how to continue your automation journey with dynamic inventories, custom modules, and integrations.
You now have a solid foundation in Ansible automation. This final section explores advanced features that unlock Ansible's full potential and provides guidance for continuing your automation journey. You'll learn about dynamic inventories, custom modules, integration patterns, and how to become part of the broader automation community.
Dynamic Inventories
Static inventory files work well for smaller, stable infrastructures, but cloud environments with frequently changing resources need dynamic approaches.
Cloud Provider Integration
Ansible includes inventory plugins for major cloud providers. Configure AWS dynamic inventory:
# inventories/aws.yml
---
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- us-west-2
keyed_groups:
# Group by instance type
- key: instance_type
prefix: type
# Group by environment tag
- key: tags.Environment
prefix: env
# Group by application tag
- key: tags.Application
prefix: app
compose:
# Create ansible_host from public IP
ansible_host: public_ip_address | default(private_ip_address)
# Set connection user based on AMI
ansible_user: ubuntu if image.name.startswith('ubuntu') else ec2-user
filters:
# Only include running instances
- instance-state-name: running
# Only include instances with specific tags
- tag:Managed: ansible
Use the dynamic inventory:
# List discovered hosts
ansible-inventory -i inventories/aws.yml --list
# Run playbooks against dynamic groups
ansible-playbook -i inventories/aws.yml site.yml --limit env_production
Custom Dynamic Inventories
Create custom inventory scripts for specialized environments:
#!/usr/bin/env python3
# inventories/custom_inventory.py
import json
import requests
import sys
def get_inventory():
"""Fetch inventory from custom API"""
try:
# Replace with your actual API endpoint
response = requests.get('https://api.company.com/infrastructure')
data = response.json()
inventory = {
'_meta': {
'hostvars': {}
}
}
for server in data['servers']:
# Create groups based on server attributes
role_group = f"role_{server['role']}"
env_group = f"env_{server['environment']}"
# Initialize groups if they don't exist
for group in [role_group, env_group]:
if group not in inventory:
inventory[group] = {'hosts': []}
# Add host to appropriate groups
hostname = server['hostname']
inventory[role_group]['hosts'].append(hostname)
inventory[env_group]['hosts'].append(hostname)
# Set host variables
inventory['_meta']['hostvars'][hostname] = {
'ansible_host': server['ip_address'],
'ansible_user': server.get('ssh_user', 'ubuntu'),
'server_id': server['id'],
'environment': server['environment'],
'role': server['role'],
'datacenter': server['datacenter']
}
return inventory
except Exception as e:
print(f"Error fetching inventory: {e}", file=sys.stderr)
return {}
if __name__ == '__main__':
if len(sys.argv) == 2 and sys.argv[1] == '--list':
print(json.dumps(get_inventory(), indent=2))
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
# Ansible requires this for compatibility
print(json.dumps({}))
else:
print("Usage: %s --list or %s --host <hostname>" % (sys.argv[0], sys.argv[0]))
Make the script executable and test it:
chmod +x inventories/custom_inventory.py
ansible-inventory -i inventories/custom_inventory.py --list
Advanced Playbook Patterns
Strategy Plugins
Control how Ansible executes tasks across hosts:
# Free strategy: Don't wait for all hosts to complete each task
- name: Fast parallel execution
hosts: webservers
strategy: free
gather_facts: no
tasks:
- name: Quick service restart
service:
name: nginx
state: restarted
# Debug strategy: Interactive debugging
- name: Debug problematic deployment
hosts: problem_servers
strategy: debug
tasks:
- name: Complex task that might fail
complex_module:
param: value
Delegation and Local Actions
Execute tasks on different hosts or locally:
tasks:
# Run on Ansible control machine
- name: Generate configuration locally
template:
src: config.j2
dest: '/tmp/config-{{ inventory_hostname }}.conf'
delegate_to: localhost
become: no
# Copy generated config to target host
- name: Deploy generated configuration
copy:
src: '/tmp/config-{{ inventory_hostname }}.conf'
dest: /etc/myapp/config.conf
# Update load balancer from specific host
- name: Update load balancer configuration
uri:
url: '{{ loadbalancer_api }}/servers'
method: POST
body_format: json
body:
server: '{{ inventory_hostname }}'
status: active
delegate_to: "{{ groups['loadbalancers'][0] }}"
run_once: true
# Run command on database server from web server
- name: Trigger database backup
command: /opt/backup/run-backup.sh
delegate_to: "{{ groups['databases'][0] }}"
when: inventory_hostname == groups['webservers'][0]
Advanced Loops and Conditions
Use complex iteration patterns:
tasks:
# Nested loops
- name: Configure firewall rules for each service
ufw:
rule: allow
port: '{{ item.1 }}'
from_ip: '{{ item.0.ip }}'
comment: '{{ item.0.name }} access to port {{ item.1 }}'
loop: "{{ allowed_sources | subelements('ports') }}"
vars:
allowed_sources:
- name: office
ip: 203.0.113.0/24
ports: [80, 443, 22]
- name: monitoring
ip: 198.51.100.5
ports: [9100, 9200]
# Conditional loops with complex logic
- name: Install packages by OS family
package:
name: '{{ item }}'
state: present
loop: '{{ packages[ansible_os_family] | default([]) }}'
when:
- packages is defined
- ansible_os_family in packages
vars:
packages:
Debian: ['nginx', 'python3-pip', 'htop']
RedHat: ['nginx', 'python3-pip', 'htop']
Alpine: ['nginx', 'py3-pip', 'htop']
# Dictionary iteration with complex conditions
- name: Configure services based on host role
service:
name: '{{ item.key }}'
state: '{{ item.value.state }}'
enabled: '{{ item.value.enabled }}'
loop: '{{ services | dict2items }}'
when:
- item.value.roles is undefined or
(inventory_hostname in groups[item.value.roles] if item.value.roles is string else
group_names | intersect(item.value.roles) | length > 0)
vars:
services:
nginx:
state: started
enabled: yes
roles: webservers
postgresql:
state: started
enabled: yes
roles: databases
redis:
state: started
enabled: yes
roles: [webservers, databases]
Custom Modules and Plugins
Writing Custom Modules
Create custom modules for specialized tasks:
#!/usr/bin/python3
# library/company_api.py
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
import json
def main():
module_args = dict(
api_endpoint=dict(type='str', required=True),
api_key=dict(type='str', required=True, no_log=True),
action=dict(type='str', required=True, choices=['deploy', 'rollback', 'status']),
service_name=dict(type='str', required=True),
version=dict(type='str', required=False),
timeout=dict(type='int', default=300)
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# Extract parameters
api_endpoint = module.params['api_endpoint']
api_key = module.params['api_key']
action = module.params['action']
service_name = module.params['service_name']
version = module.params['version']
timeout = module.params['timeout']
# Prepare API request
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
url = f"{api_endpoint}/services/{service_name}/{action}"
if action == 'deploy' and not version:
module.fail_json(msg="version parameter required for deploy action")
body = {}
if action == 'deploy':
body['version'] = version
# Check mode - don't make actual changes
if module.check_mode:
module.exit_json(changed=True, msg=f"Would {action} {service_name}")
# Make API call
response, info = fetch_url(
module,
url,
method='POST',
headers=headers,
data=json.dumps(body) if body else None,
timeout=timeout
)
if info['status'] == 200:
result = json.loads(response.read())
module.exit_json(
changed=True,
msg=f"Successfully executed {action} for {service_name}",
result=result
)
else:
module.fail_json(
msg=f"API call failed with status {info['status']}",
details=info
)
if __name__ == '__main__':
main()
Use your custom module:
tasks:
- name: Deploy application via company API
company_api:
api_endpoint: '{{ company_api_url }}'
api_key: '{{ company_api_key }}'
action: deploy
service_name: customer-portal
version: '{{ app_version }}'
register: deployment_result
- name: Display deployment result
debug:
var: deployment_result
Custom Filter Plugins
Create custom filters for data transformation:
# filter_plugins/custom_filters.py
class FilterModule(object):
def filters(self):
return {
'to_env_vars': self.to_env_vars,
'merge_configs': self.merge_configs,
'safe_filename': self.safe_filename
}
def to_env_vars(self, data, prefix=''):
"""Convert dictionary to environment variable format"""
env_vars = []
for key, value in data.items():
env_key = f"{prefix}{key}".upper() if prefix else key.upper()
if isinstance(value, dict):
env_vars.extend(self.to_env_vars(value, f"{env_key}_"))
else:
env_vars.append(f"{env_key}={value}")
return env_vars
def merge_configs(self, base_config, override_config):
"""Deep merge configuration dictionaries"""
import copy
result = copy.deepcopy(base_config)
def deep_merge(base, override):
for key, value in override.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
deep_merge(base[key], value)
else:
base[key] = value
deep_merge(result, override_config)
return result
def safe_filename(self, filename):
"""Convert string to safe filename"""
import re
safe = re.sub(r'[^\w\-_.]', '_', filename)
return safe.strip('_.')
Use custom filters in templates and tasks:
tasks:
- name: Generate environment file
copy:
content: |
{% for env_var in app_config | to_env_vars('APP_') %}
{{ env_var }}
{% endfor %}
dest: '/etc/{{ app_name }}/environment'
- name: Merge configurations
set_fact:
final_config: '{{ base_config | merge_configs(environment_overrides) }}'
- name: Create safe backup filename
set_fact:
backup_file: "/backups/{{ (app_name + '_' + ansible_date_time.iso8601) | safe_filename }}.tar.gz"
Integration Patterns
Ansible Tower/AWX Integration
Use Ansible Tower for enterprise automation management:
# Job template configuration for Tower/AWX
---
name: 'Deploy Web Application'
description: 'Deploy web application to specified environment'
job_type: 'run'
inventory: 'Dynamic AWS Inventory'
project: 'Infrastructure Automation'
playbook: 'deploy-webapp.yml'
credential: 'AWS SSH Key'
verbosity: 1
extra_vars:
environment: "{{ tower_environment | default('staging') }}"
app_version: '{{ tower_app_version }}'
notification_email: '{{ tower_notification_email }}'
ask_variables_on_launch: true
concurrent_jobs_enabled: false
CI/CD Pipeline Integration
Integrate Ansible with Jenkins:
// Jenkinsfile
pipeline {
agent any
parameters {
choice(
name: 'ENVIRONMENT',
choices: ['staging', 'production'],
description: 'Target environment'
)
string(
name: 'APP_VERSION',
defaultValue: 'latest',
description: 'Application version to deploy'
)
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Lint and Test') {
parallel {
stage('Ansible Lint') {
steps {
sh 'ansible-lint site.yml'
}
}
stage('YAML Lint') {
steps {
sh 'yamllint .'
}
}
stage('Syntax Check') {
steps {
sh 'ansible-playbook --syntax-check site.yml'
}
}
}
}
stage('Deploy') {
steps {
withCredentials([
file(credentialsId: 'ansible-vault-password', variable: 'VAULT_PASSWORD_FILE'),
sshUserPrivateKey(credentialsId: 'deployment-key', keyFileVariable: 'SSH_KEY')
]) {
sh """
ansible-playbook site.yml \
-i inventories/${params.ENVIRONMENT}/ \
--private-key ${SSH_KEY} \
--vault-password-file ${VAULT_PASSWORD_FILE} \
-e app_version=${params.APP_VERSION} \
-e notification_email=${env.CHANGE_AUTHOR_EMAIL}
"""
}
}
}
stage('Verify Deployment') {
steps {
sh """
ansible-playbook verify-deployment.yml \
-i inventories/${params.ENVIRONMENT}/ \
--private-key ${SSH_KEY}
"""
}
}
}
post {
success {
slackSend(
color: 'good',
message: "✅ Deployment successful: ${params.APP_VERSION} to ${params.ENVIRONMENT}"
)
}
failure {
slackSend(
color: 'danger',
message: "❌ Deployment failed: ${params.APP_VERSION} to ${params.ENVIRONMENT}"
)
}
}
}
Terraform Integration
Combine Terraform for infrastructure provisioning with Ansible for configuration:
# terraform-then-ansible.yml
---
- name: Provision and configure infrastructure
hosts: localhost
gather_facts: no
tasks:
- name: Run Terraform to provision infrastructure
terraform:
project_path: './terraform'
state: present
variables:
environment: '{{ target_environment }}'
instance_count: '{{ server_count }}'
register: terraform_output
- name: Add provisioned servers to inventory
add_host:
name: '{{ item.value.public_ip }}'
groups: webservers
ansible_host: '{{ item.value.public_ip }}'
ansible_user: ubuntu
private_ip: '{{ item.value.private_ip }}'
loop: '{{ terraform_output.outputs.web_servers.value | dict2items }}'
- name: Wait for SSH to be available
wait_for:
host: '{{ item.value.public_ip }}'
port: 22
timeout: 300
loop: '{{ terraform_output.outputs.web_servers.value | dict2items }}'
- name: Configure provisioned servers
hosts: webservers
become: yes
gather_facts: yes
roles:
- common
- webserver
- monitoring
Monitoring and Observability
Ansible Metrics Collection
Monitor your Ansible automation:
# Enable callback plugins for metrics
# ansible.cfg
[defaults]
callback_whitelist = profile_tasks, timer, json
# Custom callback plugin for metrics
# callback_plugins/metrics.py
import json
import time
from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
def __init__(self):
super(CallbackModule, self).__init__()
self.start_time = time.time()
self.task_start_time = {}
def v2_playbook_on_task_start(self, task, is_conditional):
self.task_start_time[task._uuid] = time.time()
def v2_runner_on_ok(self, result):
duration = time.time() - self.task_start_time.get(result._task._uuid, time.time())
# Send metrics to your monitoring system
self.send_metric('ansible.task.duration', duration, {
'task': result._task.name,
'host': result._host.name,
'status': 'ok'
})
def send_metric(self, metric_name, value, tags):
# Implementation depends on your monitoring system
# Examples: StatsD, Prometheus, CloudWatch, etc.
pass
Integration with Monitoring Systems
Send deployment events to monitoring systems:
tasks:
- name: Send deployment start event
uri:
url: '{{ monitoring_webhook }}'
method: POST
body_format: json
body:
event_type: deployment_start
service: '{{ app_name }}'
version: '{{ app_version }}'
environment: '{{ environment }}'
timestamp: '{{ ansible_date_time.iso8601 }}'
delegate_to: localhost
- name: Deploy application
include_role:
name: app_deployment
- name: Send deployment success event
uri:
url: '{{ monitoring_webhook }}'
method: POST
body_format: json
body:
event_type: deployment_success
service: '{{ app_name }}'
version: '{{ app_version }}'
environment: '{{ environment }}'
timestamp: '{{ ansible_date_time.iso8601 }}'
duration: '{{ ansible_play_batch | length * 60 }}' # Rough estimate
delegate_to: localhost
Community and Learning Resources
Contributing to Ansible
Contribute to the Ansible ecosystem:
- Report Issues: Use GitHub issues for bugs and feature requests
- Submit Pull Requests: Fix bugs or add features to collections
- Create Collections: Package and share your modules and roles
- Documentation: Improve documentation and examples
Building Your Skills
Continue your automation journey:
Advanced Topics:
- Network automation with Ansible
- Windows automation
- Container orchestration
- Security automation (Ansible Security Automation Platform)
Certification:
- Red Hat Certified Specialist in Ansible Automation
- Advanced certifications in automation platforms
Community Engagement:
- Join Ansible meetups and conferences
- Participate in forums and discussion groups
- Contribute to open source projects
Recommended Next Steps
Based on your infrastructure needs:
For Cloud-Native Environments:
- Explore Kubernetes automation with Ansible
- Learn about GitOps patterns
- Study service mesh automation
For Large Scale Infrastructure:
- Implement Ansible Tower/AWX
- Learn about infrastructure testing strategies
- Study compliance automation (DISA STIG, CIS benchmarks)
For Security Focus:
- Explore Ansible Security Automation Platform
- Learn about vulnerability management automation
- Study compliance as code patterns
For Development Teams:
- Learn about application deployment patterns
- Study blue/green and canary deployment strategies
- Explore infrastructure testing in CI/CD pipelines
Final Thoughts
You've completed a comprehensive journey through Ansible automation, from basic concepts to advanced production patterns. The skills you've developed provide a solid foundation for managing infrastructure at any scale.
Remember these key principles as you continue:
- Start Simple: Begin with basic automation and add complexity gradually
- Test Everything: Always test your automation in non-production environments
- Document Relentlessly: Good documentation makes automation maintainable
- Security First: Never compromise on security for convenience
- Iterate and Improve: Continuously refine your automation based on experience
Ansible automation is both an art and a science. The technical skills you've learned are just the beginning - the real expertise comes from understanding how to apply these tools to solve real business problems while maintaining reliability, security, and maintainability.
The automation community is welcoming and helpful. Don't hesitate to ask questions, share your experiences, and contribute back to the tools and practices that help everyone succeed.
Your automation journey is just beginning. Use what you've learned to make infrastructure management more reliable, predictable, and enjoyable for yourself and your teams.
Happy automating, and welcome to the community!
Found an issue?