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:

  1. Report Issues: Use GitHub issues for bugs and feature requests
  2. Submit Pull Requests: Fix bugs or add features to collections
  3. Create Collections: Package and share your modules and roles
  4. Documentation: Improve documentation and examples

Building Your Skills

Continue your automation journey:

  1. Advanced Topics:

    • Network automation with Ansible
    • Windows automation
    • Container orchestration
    • Security automation (Ansible Security Automation Platform)
  2. Certification:

    • Red Hat Certified Specialist in Ansible Automation
    • Advanced certifications in automation platforms
  3. Community Engagement:

    • Join Ansible meetups and conferences
    • Participate in forums and discussion groups
    • Contribute to open source projects

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:

  1. Start Simple: Begin with basic automation and add complexity gradually
  2. Test Everything: Always test your automation in non-production environments
  3. Document Relentlessly: Good documentation makes automation maintainable
  4. Security First: Never compromise on security for convenience
  5. 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?