Ansible Dynamic Inventory: Complete Guide to AWS, Azure, GCP, and Custom Plugins
By Luca Berton · Published 2024-01-01 · Category: installation
Master Ansible dynamic inventory. Configure AWS EC2, Azure, GCP, Proxmox, VMware, and custom inventory plugins.
What Is Dynamic Inventory?
Static inventory files work when your infrastructure is fixed — a known list of servers that rarely changes. But in cloud environments where instances scale up and down, containers spawn and die, and infrastructure is ephemeral, you need inventory that discovers hosts automatically.
Dynamic inventory queries external sources (AWS, Azure, VMware, a CMDB, a database) and builds the host list at runtime. No manual updates needed.
See also: AAP 2.6 Cloud Automation: AWS, Azure, and GCP with Ansible
Inventory Plugins vs Scripts
Ansible supports two approaches:
| Feature | Inventory Plugin (recommended) | Inventory Script (legacy) | |---------|-------------------------------|--------------------------| | Format | YAML configuration file | Executable script (Python, Bash) | | Caching | Built-in | Manual | | Composable | Yes (combine with static) | Limited | | Maintained | Actively | Deprecated for most use cases | | Performance | Optimized | Varies |
Always use inventory plugins unless you have a unique data source with no existing plugin.
AWS EC2 Dynamic Inventory
Setup
# Install AWS collection
ansible-galaxy collection install amazon.aws
# Install boto3
pip install boto3 botocore
# Configure AWS credentials
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
export AWS_DEFAULT_REGION="us-east-1"
# Or use ~/.aws/credentials
Inventory Configuration
# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
# Regions to query
regions:
- us-east-1
- eu-west-1
# Filter instances
filters:
instance-state-name: running
"tag:Environment":
- production
- staging
# Group by tags, instance type, region
keyed_groups:
- key: tags.Environment
prefix: env
separator: "_"
- key: instance_type
prefix: type
- key: placement.region
prefix: region
- key: tags.Role
prefix: role
# Set connection variables
compose:
ansible_host: private_ip_address
# Use public IP if no VPN:
# ansible_host: public_ip_address | default(private_ip_address)
ansible_user: "'ubuntu'"
ansible_ssh_private_key_file: "'~/.ssh/aws-key.pem'"
# Create groups from tags
groups:
webservers: tags.Role == 'web'
databases: tags.Role == 'database'
monitoring: tags.Role == 'monitoring'
# Caching (optional, speeds up repeated runs)
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/aws_inventory_cache
cache_timeout: 300 # 5 minutes
Test It
# List all discovered hosts
ansible-inventory -i inventory/aws_ec2.yml --graph
# Output:
# @all:
# |--@env_production:
# | |--web-01
# | |--web-02
# | |--db-01
# |--@env_staging:
# | |--staging-web-01
# |--@role_web:
# | |--web-01
# | |--web-02
# |--@type_t3_medium:
# | |--web-01
# | |--web-02
# Show all variables for a host
ansible-inventory -i inventory/aws_ec2.yml --host web-01
# Use in a playbook
ansible-playbook -i inventory/aws_ec2.yml site.yml
See also: Ansible for Cloud Automation: AWS, Azure, and GCP Complete Guide
Azure Dynamic Inventory
# Install Azure collection
ansible-galaxy collection install azure.azcollection
pip install azure-identity azure-mgmt-compute azure-mgmt-network azure-mgmt-resource
# inventory/azure_rm.yml
plugin: azure.azcollection.azure_rm
# Authentication (or use env vars / managed identity)
auth_source: auto
# Include only running VMs
include_vm_resource_groups:
- production-rg
- staging-rg
# Conditional groups
conditional_groups:
webservers: "'web' in tags.Role | default('')"
databases: "'db' in tags.Role | default('')"
# Keyed groups
keyed_groups:
- key: tags.Environment | default('untagged')
prefix: env
- key: location
prefix: region
- key: os_profile.system | default('linux')
prefix: os
# Connection settings
compose:
ansible_host: private_ipv4_addresses[0] | default(public_ipv4_addresses[0])
ansible_user: "'azureadmin'"
# Exclude deallocated VMs
exclude_host_filters:
- powerstate != "running"
# Caching
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/azure_inventory_cache
cache_timeout: 600
GCP Dynamic Inventory
ansible-galaxy collection install google.cloud
pip install google-auth requests
# inventory/gcp.yml
plugin: google.cloud.gcp_compute
# Authentication
auth_kind: serviceaccount
service_account_file: /path/to/service-account.json
# Projects to query
projects:
- my-gcp-project
# Zones
zones:
- us-central1-a
- us-central1-b
- europe-west1-b
# Filters
filters:
- status = RUNNING
- labels.environment = production
# Grouping
keyed_groups:
- key: labels.role
prefix: role
- key: zone
prefix: zone
- key: machine_type | basename
prefix: type
compose:
ansible_host: networkInterfaces[0].networkIP
ansible_user: "'ansible'"
groups:
webservers: labels.role == "web"
databases: labels.role == "database"
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/gcp_inventory_cache
cache_timeout: 300
See also: Ansible Terraform Integration: Orchestrate Infrastructure and Configuration Together
VMware Dynamic Inventory
ansible-galaxy collection install community.vmware
pip install pyvmomi
# inventory/vmware.yml
plugin: community.vmware.vmware_vm_inventory
hostname: vcenter.company.com
username: "ansible@vsphere.local"
password: "{{ vault_vcenter_password }}"
validate_certs: false
# Only powered-on VMs
with_nested_properties: true
resources:
- datacenter:
- DC1
resources:
- compute_resource:
- Cluster1
filters:
- runtime.powerState == "poweredOn"
keyed_groups:
- key: config.guestId
prefix: os
- key: guest.net[0].network | default('unknown')
prefix: network
compose:
ansible_host: guest.ipAddress
ansible_user: "'ansible'"
properties:
- config.name
- config.guestId
- guest.ipAddress
- runtime.powerState
- config.hardware.memoryMB
- config.hardware.numCPU
Combining Multiple Inventory Sources
Create an inventory directory with multiple sources:
inventory/
├── 01-static.yml # Static hosts (network devices, etc.)
├── 02-aws_ec2.yml # AWS dynamic inventory
├── 03-azure_rm.yml # Azure dynamic inventory
├── group_vars/
│ ├── all.yml # Variables for all hosts
│ ├── webservers.yml # Variables for web group
│ └── databases.yml # Variables for db group
└── host_vars/
└── special-host.yml # Host-specific variables
# Ansible reads ALL files in the directory
ansible-playbook -i inventory/ site.yml
# Or set in ansible.cfg
[defaults]
inventory = ./inventory/
Custom Dynamic Inventory Script
When no plugin exists for your data source, write a custom script:
#!/usr/bin/env python3
"""Custom dynamic inventory from CMDB API."""
import json
import argparse
import requests
CMDB_URL = "https://cmdb.company.com/api/v1"
API_KEY = "your-api-key"
def get_inventory():
"""Fetch hosts from CMDB and build Ansible inventory."""
headers = {"Authorization": f"Bearer {API_KEY}"}
hosts = requests.get(f"{CMDB_URL}/hosts", headers=headers).json()
inventory = {
"_meta": {"hostvars": {}},
"all": {"children": []}
}
groups = {}
for host in hosts:
hostname = host["fqdn"]
# Add to groups
for group in host.get("groups", []):
if group not in groups:
groups[group] = {"hosts": []}
inventory["all"]["children"].append(group)
groups[group]["hosts"].append(hostname)
# Host variables
inventory["_meta"]["hostvars"][hostname] = {
"ansible_host": host["ip_address"],
"ansible_user": host.get("ssh_user", "ansible"),
"os_type": host.get("os", "linux"),
"datacenter": host.get("datacenter", "unknown"),
"environment": host.get("environment", "unknown"),
}
inventory.update(groups)
return inventory
def get_host(hostname):
"""Fetch single host variables."""
headers = {"Authorization": f"Bearer {API_KEY}"}
host = requests.get(f"{CMDB_URL}/hosts/{hostname}", headers=headers).json()
return {
"ansible_host": host["ip_address"],
"ansible_user": host.get("ssh_user", "ansible"),
}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--list", action="store_true")
parser.add_argument("--host", type=str)
args = parser.parse_args()
if args.list:
print(json.dumps(get_inventory(), indent=2))
elif args.host:
print(json.dumps(get_host(args.host), indent=2))
# Make executable
chmod +x cmdb_inventory.py
# Test
./cmdb_inventory.py --list | jq
# Use
ansible-playbook -i cmdb_inventory.py site.yml
Inventory Plugin Development
For a reusable, cacheable plugin:
# plugins/inventory/cmdb.py
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
DOCUMENTATION = """
name: cmdb
plugin_type: inventory
short_description: CMDB dynamic inventory
options:
api_url:
description: CMDB API endpoint
required: true
api_key:
description: API authentication key
required: true
env:
- name: CMDB_API_KEY
"""
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = "cmdb"
def verify_file(self, path):
return super().verify_file(path) and path.endswith(("cmdb.yml", "cmdb.yaml"))
def parse(self, inventory, loader, path, cache=True):
super().parse(inventory, loader, path, cache)
self._read_config_data(path)
api_url = self.get_option("api_url")
api_key = self.get_option("api_key")
# Fetch and populate inventory
# self.inventory.add_host(hostname)
# self.inventory.add_group(group)
# self.inventory.add_child(group, hostname)
# self.inventory.set_variable(hostname, key, value)
Best Practices
Use Caching
# In your inventory plugin config
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/ansible_inventory_cache
cache_timeout: 300
# Clear cache when needed
ansible-inventory -i inventory/ --flush-cache
Tag Everything
Dynamic inventory grouping relies on tags/labels. Establish a tagging standard:
# Required tags for all cloud resources
tags:
Environment: production # Groups: env_production
Role: web # Groups: role_web
Team: platform # Groups: team_platform
Application: api # Groups: app_api
CostCenter: engineering # For billing
Use Compose for Connection Variables
compose:
# Choose IP based on network access
ansible_host: >-
private_ip_address if 'vpn' in group_names
else public_ip_address | default(private_ip_address)
# Set user based on OS
ansible_user: >-
'ec2-user' if image_id.startswith('ami-amazon')
else 'ubuntu' if image_id.startswith('ami-ubuntu')
else 'centos'
FAQ
How does Ansible dynamic inventory work?
Ansible queries an external data source (cloud API, CMDB, database) at runtime to build the list of hosts and their variables. Instead of maintaining a static file, the inventory is generated dynamically every time you run a playbook.
Can I mix static and dynamic inventory?
Yes. Put both static YAML files and dynamic inventory plugin configs in the same directory. Ansible merges them automatically. Static inventory is great for network devices, bastion hosts, and other fixed infrastructure alongside dynamic cloud hosts.
How do I debug dynamic inventory issues?
Use ansible-inventory -i your_inventory.yml --list to see the full generated inventory as JSON. Add --graph for a tree view. Check ansible-inventory -i your_inventory.yml --host hostname for specific host variables.
Which inventory plugin should I use for AWS?
Use amazon.aws.aws_ec2 (the collection-based plugin). The older aws_ec2 plugin from community.aws is deprecated. Always use the FQCN: plugin: amazon.aws.aws_ec2.
How do I filter hosts in dynamic inventory?
Use the filters parameter in your plugin config. For AWS, filters match EC2 API filters. For all plugins, keyed_groups with Jinja2 expressions let you create groups from any host attribute, and groups with conditions include/exclude hosts.
Conclusion
Dynamic inventory is essential for cloud-native Ansible automation. Use inventory plugins (not scripts) for AWS, Azure, GCP, VMware, and Proxmox. Tag your infrastructure consistently, use compose for connection variables, enable caching for performance, and combine multiple inventory sources in a single directory. When no plugin exists for your data source, write a custom script or develop a reusable inventory plugin.
Related Articles
• Install Ansible Complete Guide • Ansible Documentation Complete Guide • Ansible for Proxmox: Complete Guide • Ansible VMware Dynamic Inventory • AAP 2.6 Cloud Automation: AWS, Azure, GCP • Ansible VMware Dynamic Inventory • Create Network Infrastructure on AWS • Creating an Azure Virtual Network • Creating an Azure VM Scale SetCategory: installation