Ansible Inventory

What is Inventory?

The inventory is a list of hosts that Ansible manages. It can be a simple static file or a dynamic script that queries cloud providers or other systems.

Static Inventory - INI Format

# inventory.ini

# Ungrouped hosts
server1.example.com
192.168.1.10

# Grouped hosts
[webservers]
web1.example.com
web2.example.com ansible_host=192.168.1.21

[databases]
db1.example.com ansible_port=2222
db2.example.com

# Group of groups
[production:children]
webservers
databases

# Group variables
[webservers:vars]
ansible_user=deploy
nginx_port=80

[databases:vars]
ansible_user=dbadmin
mysql_port=3306

Static Inventory - YAML Format

# inventory.yml
all:
  hosts:
    standalone-server:
      ansible_host: 192.168.1.5

  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
          ansible_host: 192.168.1.21
      vars:
        ansible_user: deploy
        nginx_port: 80

    databases:
      hosts:
        db1.example.com:
          ansible_port: 2222
        db2.example.com:
      vars:
        ansible_user: dbadmin
        mysql_port: 3306

    production:
      children:
        webservers:
        databases:

Host and Group Variables

Directory Structure

inventory/
├── hosts
├── group_vars/
│   ├── all.yml
│   ├── webservers.yml
│   └── databases.yml
└── host_vars/
    ├── web1.example.com.yml
    └── db1.example.com.yml

group_vars/all.yml

---
# Variables for all hosts
ntp_server: time.example.com
dns_servers:
  - 8.8.8.8
  - 8.8.4.4

group_vars/webservers.yml

---
# Variables specific to webservers
nginx_version: 1.20.2
ssl_certificate: /etc/ssl/certs/example.crt
ssl_key: /etc/ssl/private/example.key

host_vars/web1.example.com.yml

---
# Variables specific to web1
server_id: 1
backup_enabled: true

Connection Variables

SSH Connection Variables

[servers]
server1 ansible_host=192.168.1.10
server2 ansible_host=192.168.1.11 ansible_user=admin
server3 ansible_host=192.168.1.12 ansible_user=deploy ansible_port=2222
server4 ansible_host=192.168.1.13 ansible_ssh_private_key_file=~/.ssh/id_rsa_custom

Windows Connection Variables

[windows]
win1 ansible_host=192.168.1.50
win2 ansible_host=192.168.1.51

[windows:vars]
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore
ansible_user=Administrator
ansible_password=SecurePass123
ansible_port=5986

Behavioral Inventory Parameters

Parameter Description
ansible_host The IP address or hostname to connect to
ansible_port SSH port (default: 22)
ansible_user User to connect as
ansible_password SSH password (use Vault!)
ansible_ssh_private_key_file Private key file path
ansible_connection Connection type (ssh, winrm, local, docker)
ansible_become Equivalent to become: yes
ansible_become_user User to become (default: root)
ansible_python_interpreter Path to Python interpreter

Inventory Patterns

Targeting Hosts

# All hosts
ansible all -m ping

# Single host
ansible web1.example.com -m ping

# Group
ansible webservers -m ping

# Multiple groups (union)
ansible webservers:databases -m ping

# Intersection (hosts in both groups)
ansible webservers:&databases -m ping

# Exclusion
ansible webservers:!databases -m ping

# Wildcard
ansible web* -m ping
ansible *.example.com -m ping

# Range
ansible web[1:5].example.com -m ping

# Regex
ansible ~web[0-9]+ -m ping

Dynamic Inventory

Dynamic inventory allows you to query external sources for your host list.

AWS EC2 Dynamic Inventory

Install AWS collection:

ansible-galaxy collection install amazon.aws

Create aws_ec2.yml:

---
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
  - us-west-2

filters:
  tag:Environment: production

keyed_groups:
  - key: tags.Name
    prefix: tag_name
  - key: instance_type
    prefix: instance_type
  - key: placement.region
    prefix: aws_region

hostnames:
  - ip-address
  - dns-name
  - private-ip-address

compose:
  ansible_host: public_ip_address

Use it:

ansible-inventory -i aws_ec2.yml --graph
ansible-playbook -i aws_ec2.yml site.yml

Custom Dynamic Inventory Script

#!/usr/bin/env python3
import json
import sys

def get_inventory():
    inventory = {
        "webservers": {
            "hosts": ["web1.example.com", "web2.example.com"],
            "vars": {
                "ansible_user": "deploy",
                "nginx_port": 80
            }
        },
        "databases": {
            "hosts": ["db1.example.com"],
            "vars": {
                "ansible_user": "dbadmin"
            }
        },
        "_meta": {
            "hostvars": {
                "web1.example.com": {
                    "server_id": 1
                },
                "db1.example.com": {
                    "mysql_port": 3306
                }
            }
        }
    }
    return inventory

if __name__ == "__main__":
    if len(sys.argv) == 2 and sys.argv[1] == '--list':
        print(json.dumps(get_inventory()))
    elif len(sys.argv) == 3 and sys.argv[1] == '--host':
        print(json.dumps({}))
    else:
        print(json.dumps({}))

Make it executable and use it:

chmod +x dynamic_inventory.py
ansible-playbook -i dynamic_inventory.py site.yml

Inventory Best Practices

  • Use groups: Organize hosts logically (by function, environment, location)
  • Separate environments: Use different inventory files for dev, staging, production
  • Use host_vars and group_vars: Keep inventory file clean
  • Secure secrets: Never store passwords in plain text; use Ansible Vault
  • Document: Add comments to explain complex inventory structures
  • Version control: Keep inventory in git (except secrets)
  • Dynamic inventory: Use for cloud environments to auto-discover hosts
  • Test patterns: Use ansible-inventory --list to verify

Verifying Inventory

# List all hosts
ansible-inventory -i inventory.ini --list

# Show inventory graph
ansible-inventory -i inventory.ini --graph

# List hosts in a specific group
ansible-inventory -i inventory.ini --graph webservers

# Show host variables
ansible-inventory -i inventory.ini --host web1.example.com

Advanced Inventory Patterns

Complex Host Patterns

# Multiple patterns with OR
ansible 'webservers:dbservers' -m ping

# Intersection - hosts in BOTH groups
ansible 'webservers:&production' -m ping

# Exclude hosts from pattern
ansible 'all:!development' -m ping

# Combine multiple operators
ansible 'webservers:&production:!maintenance' -m ping

# Range patterns
ansible 'web[01:10].example.com' -m ping
ansible 'db[a:f].example.com' -m ping

# Regex patterns (use ~)
ansible '~web[0-9]+\.prod\..*' -m ping
ansible '~(web|app|api)[0-9]+' -m ping

# Multiple specific hosts
ansible 'web1,web2,db1' -m ping

# Slice pattern (first 5 hosts)
ansible 'webservers[0:5]' -m ping

# Every Nth host
ansible 'webservers[::2]' -m ping  # Every other host

# Combination patterns
ansible 'webservers[0:10]:&production:!maintenance' -m ping

Pattern Examples in Playbooks

---
# Run on all hosts except development
- name: Production maintenance
  hosts: all:!development
  tasks:
    - name: Update security patches
      yum:
        name: '*'
        state: latest
        security: yes

# Run on intersection of groups
- name: Load balancer backend configuration
  hosts: webservers:&production
  tasks:
    - name: Register with load balancer
      uri:
        url: http://lb.example.com/api/register
        method: POST

# Run on hosts matching pattern
- name: Database backup
  hosts: '~db[0-9]+\.prod\..*'
  tasks:
    - name: Backup databases
      mysql_db:
        state: dump
        name: all

Multi-Cloud Dynamic Inventory

AWS EC2 - Advanced Configuration

---
# aws_ec2.yml
plugin: amazon.aws.aws_ec2

# Multiple regions
regions:
  - us-east-1
  - us-west-2
  - eu-west-1
  - ap-southeast-1

# Credentials
aws_profile: production
# OR use environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY

# Filter instances
filters:
  tag:Environment:
    - production
    - staging
  instance-state-name: running

# Group instances by tags
keyed_groups:
  # Group by Environment tag
  - key: tags.Environment
    prefix: env
    separator: '_'

  # Group by instance type
  - key: instance_type
    prefix: type

  # Group by availability zone
  - key: placement.availability_zone
    prefix: az

  # Group by VPC
  - key: vpc_id
    prefix: vpc

  # Group by security groups
  - key: security_groups
    prefix: sg

# Create composite groups
groups:
  # Production web servers
  prod_web: tags.Environment == 'production' and tags.Role == 'webserver'

  # Large instances
  large_instances: instance_type.startswith('m5.2x') or instance_type.startswith('m5.4x')

  # Multi-AZ setup
  multi_az: placement.availability_zone in ['us-east-1a', 'us-east-1b']

# Define hostname to use
hostnames:
  - tag:Name
  - private-ip-address
  - ip-address

# Compose additional variables
compose:
  ansible_host: public_ip_address
  ansible_user: ec2-user
  ec2_instance_id: instance_id
  ec2_region: placement.region
  ec2_az: placement.availability_zone
  ec2_vpc_id: vpc_id

# Include/exclude hosts
include_filters:
  - tag:Managed: ansible

exclude_filters:
  - tag:DoNotManage: true

# Cache for performance
cache: yes
cache_plugin: jsonfile
cache_timeout: 3600
cache_connection: /tmp/aws_inventory_cache

Azure - Dynamic Inventory

---
# azure_rm.yml
plugin: azure.azcollection.azure_rm

# Authentication
auth_source: auto  # Uses Azure CLI, environment vars, or MSI

# Filter by subscription
include_vm_resource_groups:
  - production-rg
  - staging-rg

# Conditional groups
conditional_groups:
  # Group by tags
  linux: "'Linux' in image.offer"
  windows: "'Windows' in image.offer"
  webservers: "'webserver' in tags.role"
  databases: "'database' in tags.role"

# Group by properties
keyed_groups:
  - key: location
    prefix: azure_location
  - key: tags.environment
    prefix: env
  - key: resource_group
    prefix: rg
  - key: os_profile.computer_name
    prefix: host

# Hostname construction
hostvar_expressions:
  ansible_host: public_ipv4_addresses[0] if public_ipv4_addresses else private_ipv4_addresses[0]
  ansible_user: "'azureuser' if 'Linux' in image.offer else 'Administrator'"
  azure_vm_id: id
  azure_resource_group: resource_group
  azure_location: location
  azure_tags: tags

# Performance
use_contrib_script_compatible_sanitization: yes
plain_host_names: yes

Google Cloud Platform (GCP)

---
# gcp_compute.yml
plugin: google.cloud.gcp_compute

# Project and zone
projects:
  - my-project-id
  - another-project

regions:
  - us-central1
  - us-east1
  - europe-west1

# Filter instances
filters:
  - status = RUNNING
  - labels.environment = production

# Service account authentication
service_account_file: /path/to/service-account.json

# Grouping
keyed_groups:
  # Group by zone
  - key: zone
    prefix: gcp_zone

  # Group by machine type
  - key: machineType
    prefix: gcp_type

  # Group by labels
  - key: labels
    prefix: label

# Compose variables
compose:
  ansible_host: networkInterfaces[0].accessConfigs[0].natIP
  ansible_user: labels.ssh_user | default('ubuntu')
  gcp_instance_name: name
  gcp_zone: zone
  gcp_machine_type: machineType
  gcp_labels: labels

# Group expressions
groups:
  webservers: "'webserver' in labels.role"
  production: "labels.environment == 'production'"

VMware vSphere

---
# vmware.yml
plugin: community.vmware.vmware_vm_inventory

hostname: vcenter.example.com
username: "{{ lookup('env', 'VMWARE_USER') }}"
password: "{{ lookup('env', 'VMWARE_PASSWORD') }}"
validate_certs: no

# Filters
with_tags: yes
filters:
  - summary.runtime.powerState == "poweredOn"
  - "'production' in tag"

# Properties to retrieve
properties:
  - name
  - config.guestId
  - guest.ipAddress
  - summary.runtime.powerState
  - config.hardware.memoryMB
  - config.hardware.numCPU

# Grouping
keyed_groups:
  - key: config.guestId
    prefix: os
  - key: summary.runtime.powerState
    prefix: power
  - key: config.hardware.numCPU
    prefix: cpu

# Host variables
compose:
  ansible_host: guest.ipAddress
  ansible_user: root
  vm_name: name
  vm_memory_mb: config.hardware.memoryMB
  vm_cpus: config.hardware.numCPU
  vm_os: config.guestId

Combining Multiple Inventories

Inventory Directory Structure

inventory/
├── 01-static-hosts.yml           # Static hosts
├── 02-aws-ec2.yml                # AWS dynamic
├── 03-azure-rm.yml               # Azure dynamic
├── 04-gcp-compute.yml            # GCP dynamic
├── 05-vmware.yml                 # VMware dynamic
├── group_vars/
│   ├── all.yml                   # Global variables
│   ├── production.yml            # Production variables
│   ├── webservers.yml           # Webserver variables
│   └── databases.yml            # Database variables
└── host_vars/
    ├── web1.example.com.yml
    └── db1.example.com.yml

Use entire directory as inventory:

ansible-playbook -i inventory/ site.yml

# Ansible automatically merges all inventory sources

Static + Dynamic Hybrid

# 01-static.yml
---
all:
  children:
    onprem:
      hosts:
        onprem-web1:
          ansible_host: 10.0.1.10
        onprem-db1:
          ansible_host: 10.0.1.20
      vars:
        datacenter: on-premises
        backup_location: /backup/onprem

# 02-aws.yml (dynamic)
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
compose:
  datacenter: "'aws-' + placement.region"
  backup_location: "'s3://backups/' + instance_id"

Constructing Inventory Programmatically

Building Inventory in Playbook

---
- name: Dynamically build inventory
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Query database for servers
      postgresql_query:
        db: cmdb
        query: "SELECT hostname, ip, role FROM servers WHERE active = true"
      register: db_servers

    - name: Add hosts to inventory
      add_host:
        name: "{{ item.hostname }}"
        ansible_host: "{{ item.ip }}"
        groups: "{{ item.role }}"
        server_source: database
      loop: "{{ db_servers.query_result }}"

    - name: Query AWS API
      ec2_instance_info:
        filters:
          "tag:Environment": production
          instance-state-name: running
      register: aws_instances

    - name: Add AWS instances
      add_host:
        name: "{{ item.tags.Name }}"
        ansible_host: "{{ item.public_ip_address }}"
        groups: aws,production
        instance_id: "{{ item.instance_id }}"
        server_source: aws
      loop: "{{ aws_instances.instances }}"

    - name: Query API endpoint
      uri:
        url: https://api.example.com/servers
        headers:
          Authorization: "Bearer {{ api_token }}"
      register: api_servers

    - name: Add API-sourced hosts
      add_host:
        name: "{{ item.name }}"
        ansible_host: "{{ item.address }}"
        groups: "{{ item.environment }},{{ item.tier }}"
        server_source: api
      loop: "{{ api_servers.json.servers }}"

# Now use the dynamically constructed inventory
- name: Configure all discovered servers
  hosts: all
  tasks:
    - name: Show server source
      debug:
        msg: "Server {{ inventory_hostname }} from {{ server_source }}"

    - name: Apply baseline configuration
      include_role:
        name: baseline

Advanced Dynamic Inventory Script

#!/usr/bin/env python3
"""
Advanced dynamic inventory with multiple sources
"""
import json
import requests
import boto3
from azure.identity import DefaultAzureCredential
from azure.mgmt.compute import ComputeManagementClient

class MultiCloudInventory:
    def __init__(self):
        self.inventory = {
            '_meta': {
                'hostvars': {}
            }
        }

    def add_group(self, group_name, hosts=None, vars=None, children=None):
        """Add a group to inventory"""
        if group_name not in self.inventory:
            self.inventory[group_name] = {}

        if hosts:
            self.inventory[group_name]['hosts'] = hosts
        if vars:
            self.inventory[group_name]['vars'] = vars
        if children:
            self.inventory[group_name]['children'] = children

    def add_host(self, hostname, group, hostvars=None):
        """Add a host to a group with variables"""
        if group not in self.inventory:
            self.add_group(group, hosts=[])

        if 'hosts' not in self.inventory[group]:
            self.inventory[group]['hosts'] = []

        self.inventory[group]['hosts'].append(hostname)

        if hostvars:
            self.inventory['_meta']['hostvars'][hostname] = hostvars

    def get_aws_instances(self):
        """Fetch AWS EC2 instances"""
        ec2 = boto3.client('ec2', region_name='us-east-1')

        response = ec2.describe_instances(
            Filters=[
                {'Name': 'instance-state-name', 'Values': ['running']},
                {'Name': 'tag:Managed', 'Values': ['ansible']}
            ]
        )

        for reservation in response['Reservations']:
            for instance in reservation['Instances']:
                hostname = self.get_tag(instance, 'Name', instance['InstanceId'])
                environment = self.get_tag(instance, 'Environment', 'unknown')
                role = self.get_tag(instance, 'Role', 'unknown')

                # Add to multiple groups
                self.add_host(
                    hostname,
                    'aws',
                    hostvars={
                        'ansible_host': instance.get('PublicIpAddress', instance['PrivateIpAddress']),
                        'ansible_user': 'ec2-user',
                        'instance_id': instance['InstanceId'],
                        'instance_type': instance['InstanceType'],
                        'cloud_provider': 'aws',
                        'region': instance['Placement']['AvailabilityZone'][:-1]
                    }
                )

                # Add to environment and role groups
                self.add_host(hostname, f'env_{environment}')
                self.add_host(hostname, f'role_{role}')

    def get_azure_vms(self):
        """Fetch Azure VMs"""
        credential = DefaultAzureCredential()
        subscription_id = "your-subscription-id"
        compute_client = ComputeManagementClient(credential, subscription_id)

        for vm in compute_client.virtual_machines.list_all():
            tags = vm.tags or {}

            self.add_host(
                vm.name,
                'azure',
                hostvars={
                    'ansible_host': self.get_azure_ip(vm),
                    'ansible_user': 'azureuser',
                    'vm_size': vm.hardware_profile.vm_size,
                    'cloud_provider': 'azure',
                    'location': vm.location,
                    'resource_group': vm.id.split('/')[4]
                }
            )

            # Group by tags
            if 'environment' in tags:
                self.add_host(vm.name, f"env_{tags['environment']}")
            if 'role' in tags:
                self.add_host(vm.name, f"role_{tags['role']}")

    def get_api_servers(self):
        """Fetch from custom API"""
        response = requests.get('https://api.example.com/servers')
        servers = response.json()

        for server in servers:
            self.add_host(
                server['hostname'],
                'api_managed',
                hostvars={
                    'ansible_host': server['ip_address'],
                    'ansible_user': server.get('ssh_user', 'root'),
                    'server_id': server['id'],
                    'datacenter': server.get('datacenter', 'unknown')
                }
            )

            # Add to environment group
            if 'environment' in server:
                self.add_host(server['hostname'], server['environment'])

    def get_tag(self, instance, key, default=''):
        """Get tag value from AWS instance"""
        for tag in instance.get('Tags', []):
            if tag['Key'] == key:
                return tag['Value']
        return default

    def get_azure_ip(self, vm):
        """Get Azure VM IP address"""
        # Simplified - would need to query network interfaces
        return "0.0.0.0"

    def generate(self):
        """Generate complete inventory"""
        # Fetch from all sources
        self.get_aws_instances()
        self.get_azure_vms()
        self.get_api_servers()

        # Create meta groups
        self.add_group('cloud', children=['aws', 'azure'])
        self.add_group('production', children=['env_production'])
        self.add_group('webservers', children=['role_webserver'])

        return self.inventory

if __name__ == '__main__':
    import sys

    inventory = MultiCloudInventory()

    if len(sys.argv) == 2 and sys.argv[1] == '--list':
        print(json.dumps(inventory.generate(), indent=2))
    elif len(sys.argv) == 3 and sys.argv[1] == '--host':
        # Return empty dict - hostvars in _meta
        print(json.dumps({}))
    else:
        print(json.dumps({}))

Inventory Plugins

Custom Inventory Plugin

# plugins/inventory/custom_cmdb.py
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable

DOCUMENTATION = '''
    name: custom_cmdb
    plugin_type: inventory
    short_description: Custom CMDB inventory source
    description: Fetches inventory from internal CMDB
    options:
        plugin:
            description: Name of plugin
            required: true
        cmdb_url:
            description: CMDB API endpoint
            required: true
        api_token:
            description: API authentication token
            required: true
'''

class InventoryModule(BaseInventoryPlugin, Constructable):
    NAME = 'custom_cmdb'

    def verify_file(self, path):
        """Verify inventory file"""
        if super(InventoryModule, self).verify_file(path):
            return path.endswith(('cmdb.yml', 'cmdb.yaml'))
        return False

    def parse(self, inventory, loader, path, cache=True):
        """Parse inventory"""
        super(InventoryModule, self).parse(inventory, loader, path, cache)

        # Read configuration
        self._read_config_data(path)

        cmdb_url = self.get_option('cmdb_url')
        api_token = self.get_option('api_token')

        # Fetch data from CMDB
        import requests
        headers = {'Authorization': f'Bearer {api_token}'}
        response = requests.get(f'{cmdb_url}/servers', headers=headers)
        servers = response.json()

        # Process servers
        for server in servers:
            hostname = server['hostname']

            # Add host
            self.inventory.add_host(hostname)

            # Set variables
            self.inventory.set_variable(hostname, 'ansible_host', server['ip'])
            self.inventory.set_variable(hostname, 'ansible_user', server.get('user', 'root'))

            # Add to groups
            for group in server.get('groups', []):
                self.inventory.add_group(group)
                self.inventory.add_child(group, hostname)

            # Construct composed variables
            strict = self.get_option('strict')
            self._set_composite_vars(
                self.get_option('compose'),
                self.inventory.get_host(hostname).get_vars(),
                hostname,
                strict
            )

            # Add to keyed groups
            self._add_host_to_keyed_groups(
                self.get_option('keyed_groups'),
                server,
                hostname,
                strict
            )

Usage configuration:

# cmdb.yml
plugin: custom_cmdb
cmdb_url: https://cmdb.example.com/api
api_token: your-api-token-here

compose:
  datacenter: server_location
  backup_enabled: backup_flag

keyed_groups:
  - key: environment
    prefix: env
  - key: application
    prefix: app

Performance Optimization

Caching Dynamic Inventory

# ansible.cfg
[inventory]
cache = yes
cache_plugin = jsonfile
cache_timeout = 3600
cache_connection = /tmp/ansible_inventory_cache
cache_prefix = ansible_inventory

# Or use Redis for distributed caching
# cache_plugin = redis
# cache_connection = redis://localhost:6379/0

Parallel Inventory Loading

[inventory]
# Enable inventory plugins
enable_plugins = host_list, script, auto, yaml, ini, toml, amazon.aws.aws_ec2, azure.azcollection.azure_rm

# Inventory parsing
inventory_unparsed_warning = False

# Performance
inventory_cache_enabled = True
fact_caching = redis
fact_caching_connection = localhost:6379:0
fact_caching_timeout = 86400

Advanced Inventory Techniques

Inventory Variables from External Sources

---
# group_vars/all.yml
# Fetch variables from HashiCorp Vault
db_password: "{{ lookup('hashi_vault', 'secret=secret/data/db:password') }}"
api_key: "{{ lookup('hashi_vault', 'secret=secret/data/api:key') }}"

# Fetch from AWS Secrets Manager
aws_secret: "{{ lookup('aws_secret', 'production/database', region='us-east-1') }}"

# Fetch from environment
proxy_host: "{{ lookup('env', 'HTTP_PROXY') }}"

# Fetch from file
ssh_key: "{{ lookup('file', '/path/to/key.pem') }}"

# Fetch from AWS Parameter Store
parameter: "{{ lookup('aws_ssm', '/production/config/param1', region='us-east-1') }}"

Conditional Inventory

---
# inventory/production.yml
all:
  children:
    webservers:
      hosts:
        web[01:10].prod.example.com:
      vars:
        ansible_user: deploy
        max_connections: 1000

    databases:
      hosts:
        db[01:03].prod.example.com:
      vars:
        ansible_user: dbadmin

    # Only include maintenance group during maintenance window
    maintenance:
      hosts: "{{ groups['all'] if maintenance_mode | default(false) else [] }}"

Real-World Inventory Examples

Multi-Tier Application

---
all:
  children:
    # Load Balancers
    loadbalancers:
      hosts:
        lb01.example.com:
          lb_priority: primary
        lb02.example.com:
          lb_priority: secondary
      vars:
        ansible_user: lbadmin
        haproxy_version: 2.4

    # Web Tier
    webservers:
      children:
        frontend:
          hosts:
            web[01:10].example.com:
          vars:
            nginx_worker_processes: 4
            nginx_worker_connections: 2048

        api:
          hosts:
            api[01:05].example.com:
          vars:
            app_workers: 8
            app_threads: 2

      vars:
        ansible_user: webapp
        app_version: 2.1.0

    # Application Tier
    appservers:
      hosts:
        app[01:20].example.com:
      vars:
        ansible_user: appuser
        jvm_memory: 4096m

    # Database Tier
    databases:
      children:
        primary:
          hosts:
            db-master.example.com:
          vars:
            mysql_role: master
            mysql_replicate: yes

        replicas:
          hosts:
            db-replica[01:02].example.com:
          vars:
            mysql_role: replica
            mysql_read_only: yes

      vars:
        ansible_user: dbadmin
        mysql_version: 8.0

    # Cache Tier
    cache:
      hosts:
        redis[01:03].example.com:
      vars:
        ansible_user: redis
        redis_maxmemory: 8gb
        redis_cluster_enabled: yes

    # Message Queue
    messagequeue:
      hosts:
        mq[01:03].example.com:
      vars:
        ansible_user: mqadmin
        rabbitmq_cluster: yes

    # Monitoring
    monitoring:
      hosts:
        mon01.example.com:
      vars:
        ansible_user: monitor
        prometheus_retention: 30d

    # Environment-based grouping
    production:
      children:
        - loadbalancers
        - webservers
        - appservers
        - databases
        - cache
        - messagequeue
      vars:
        environment: production
        backup_enabled: yes
        monitoring_enabled: yes

Troubleshooting Inventory

Debug Inventory Issues

# Verbose inventory parsing
ANSIBLE_DEBUG=1 ansible-inventory -i inventory --list

# Check specific host
ansible-inventory -i inventory --host web01 --yaml

# Validate inventory syntax
ansible-inventory -i inventory --list > /dev/null && echo "OK" || echo "ERROR"

# Show inventory variables for debugging
ansible all -i inventory -m debug -a "var=hostvars[inventory_hostname]"

# Test connectivity
ansible all -i inventory -m ping -vvv

# Check group membership
ansible-inventory -i inventory --graph --vars

# Export inventory to JSON for analysis
ansible-inventory -i inventory --list --export > inventory.json

Best Practices

  • Use groups: Organize hosts logically (by function, environment, location)
  • Separate environments: Use different inventory files for dev, staging, production
  • Use host_vars and group_vars: Keep inventory file clean and maintainable
  • Secure secrets: Never store passwords in plain text; use Ansible Vault
  • Document: Add comments to explain complex inventory structures
  • Version control: Keep inventory in git (except secrets)
  • Dynamic inventory: Use for cloud environments to auto-discover hosts
  • Test patterns: Use ansible-inventory --list to verify
  • Cache aggressively: Enable caching for dynamic inventories
  • Merge sources: Combine static and dynamic inventories in a directory
  • Use inventory plugins: More powerful than scripts
  • Leverage compose: Create computed variables in dynamic inventory
  • Group strategically: By function, environment, location, and provider
  • Avoid duplication: Use group_vars and inheritance
  • Monitor performance: Large inventories can slow down playbooks

Next Steps