Scaling: dynamic inventory and AWX
Cloud and CMDB inventory plugins, inventory caching, delegation and serial strategies, delegated facts, and a controller with AWX or Automation Platform workflows.
Dynamic inventory
# inventory/aws_ec2.yml: a plugin, not a script
plugin: amazon.aws.aws_ec2
regions: [eu-west-2]
filters:
tag:Environment: production
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
separator: _
- key: placement.availability_zone
prefix: az
hostnames:
- tag:Name
- private-ip-address # fall back when a name tag is missing
compose:
ansible_host: private_ip_address
datacenter: placement.availability_zone
cache: true
cache_plugin: ansible.builtin.jsonfile
cache_timeout: 900
cache_connection: ./.cache/aws_inventory# a dynamic inventory is reached like any other
ansible-inventory -i inventory/aws_ec2.yml --graph
ansible-inventory -i inventory/aws_ec2.yml --host ec2-host-1
ansible all -i inventory/aws_ec2.yml -m ping --limit role_web
# combine sources: static inventory plus two clouds
# ansible.cfg:
# [defaults]
# inventory = ./inventory/static.ini,./inventory/aws_ec2.yml,./inventory/gcp.yml- Enable caching. A plugin that calls the cloud API on every playbook adds seconds to minutes and can hit a rate limit during an incident.
composeturns raw API attributes into the variable names your roles already expect, so the playbooks do not need to know the cloud provider.keyed_groupsis what makes a dynamic inventory useful for targeting: group by role, environment or zone and then use--limit.- A static file for the bastion or the database plus a dynamic plugin for the compute fleet is a normal and healthy combination.
Controlling a large rollout
- name: Rolling restart across the fleet
hosts: web
become: true
serial: "25%" # 25 percent of hosts at a time
max_fail_percentage: 5 # abort the whole rollout if too many fail
order: sorted # deterministic order, easier to reason about
gather_facts: false
tasks:
- name: Drain this host from the load balancer
ansible.builtin.uri:
url: "http://lb.internal/api/drain/{{ inventory_hostname }}"
method: POST
delegate_to: localhost
changed_when: false
- name: Restart the application
ansible.builtin.systemd:
name: app
state: restarted
notify: Pause for health
- name: Return this host to rotation
ansible.builtin.uri:
url: "http://lb.internal/api/enable/{{ inventory_hostname }}"
method: POST
delegate_to: localhost
changed_when: false
handlers:
- name: Pause for health
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}:8080/health"
status_code: 200
register: health
until: health.status == 200
retries: 20
delay: 3
delegate_to: localhost| Tool | Purpose | Note |
|---|---|---|
serial | Batch size for a rollout | A percentage scales better than a number |
max_fail_percentage | Abort when too many hosts fail | Use with serial, not instead of it |
delegate_to | Run a task somewhere else | The facts stay those of the original host |
run_once | Run a task on one host | Combine with delegate_to: localhost |
delegate_facts: true | Attach the result to the delegate | Rarely what you want by default |
A controller and workflow
AWX / Automation Platform objects:
Credential a machine, source control or cloud credential, stored encrypted
Inventory static, sourced from a project file, or a cloud plugin
Project a git repository holding the playbooks
Job Template project + playbook + inventory + credentials + survey
Workflow job templates chained with success, failure and always edges
Schedule a cron-like trigger on a job template or workflow
A deployment workflow might be:
checkout (project sync)
-> lint and syntax check
-> deploy to staging
-> integration test job
-> approval node (manual, by a named team)
-> deploy to production (serial 25%, can fail over to a rollback job)
-> smoke test⚠️
A controller multiplatforms the same credential across hundreds of hosts and keeps an audit trail of who ran what — which also means a leaked controller credential is worse than a leaked SSH key. Scope credentials per environment, use the approval node rather than a shared production login, and review who can edit a job template.
FAQ
Dynamic inventory or an inventory built by a job?
A plugin is live and needs no pipeline, but it depends on API access at run time. Generating a static inventory in a scheduled job gives you a reviewable artefact and works when the cloud API is unreachable, at the cost of being stale.
How do I restart a large fleet without downtime?
Take hosts out of rotation in batches with
serial, wait for health after each batch, and abort the rollout when a percentage of hosts fail. The load balancer has to be part of the procedure, not an afterthought.Related
Testing automation: lint, check mode and Molecule Inventories and ad-hoc commands
Last refreshed 2026-09-18.