Let me introduce a clean, enterprise-grade Ansible inventory pattern that scales to many customers + multiple environments + several host roles with zero duplicated host lists i.e. you define hosts once! Key idea is that you define each host once and then you auto-build groups using inventory plugins: most importantly “constructed” plugin: https://www.redhat.com/en/blog/how-to-use-the-new-constructed-inventory-feature-in-aap-2.4.
You can keep variables DRY:
- common defaults in
group_vars/all.yml - per-env in
group_vars/env_prod.yml,group_vars/env_test.yml - per-role in
group_vars/role_db.ymletc - per-customer in
group_vars/customer_acme.yml - “cross-cutting” combos like
customer_acme_env_prod.ymlif needed
ansible/ ansible.cfg inventories/ main/ 01-hosts.yml 20-constructed.yml group_vars/ all.yml env_prod.yml env_test.yml role_app.yml role_proxy.yml role_db.yml customer_internal.yml customer_acme.yml customer_globex.yml customer_acme_env_prod.yml
# ansible.cfg[defaults]inventory = inventories/mainhost_key_checking = Falseinterpreter_python = auto_silent# Enable pluginsenable_plugins = host_list, script, auto, yaml, ini, constructed
# inventories/main/01-hosts.yml# metadataall: hosts: int-test-app-01: ansible_host: 10.10.10.11 dns: int-test-app-01.internal.example.net customer: internal env: test role: app subnet: 10.10.10.0/24 int-prod-db-01: ansible_host: 10.10.20.31 dns: int-prod-db-01.internal.example.net customer: internal env: prod role: db subnet: 10.10.20.0/24 acme-test-proxy-01: ansible_host: 10.20.10.21 dns: acme-test-proxy-01.acme.example.net customer: acme env: test role: proxy subnet: 10.20.10.0/24 acme-prod-app-01: ansible_host: 10.20.20.11 dns: acme-prod-app-01.acme.example.net customer: acme env: prod role: app subnet: 10.20.20.0/24 globex-prod-db-01: ansible_host: 10.30.20.31 dns: globex-prod-db-01.globex.example.net customer: globex env: prod role: db subnet: 10.30.20.0/24
20-constructed.yml (auto-create groups) uses Ansible’s constructed inventory plugin to generate groups from host variables. What this gives you automatically (examples): customer_acme, env_prod, role_db, customer_acme_env_prod, customer_acme_env_role_prod_app (depending on prefix/sep choices)
Note that nothing is really generated, but you can verify with “ansible-inventory -i inventories/main –graph)”: if you use “–list” option you will get JSON. What happens is that
- Loads 01-hosts.yml
- Loads 20-constructed.yml
- The constructed plugin dynamically builds groups
- Inventory is ready in memory
# inventories/main/20-constructed.ymlplugin: constructedstrict: false# Create groups like: customer_acme, env_prod, role_dbkeyed_groups: - key: customer prefix: customer_ separator: "" - key: env prefix: env_ separator: "" - key: role prefix: role_ separator: ""# Optional: create combined groups like customer_acme_env_prod, customer_internal_env_test# This is super useful for targeting “customer X in prod” without duplicating lists.groups: customers: true roles: truecompose: customer_env: "{{ customer }}_{{ env }}" customer_env_role: "{{ customer }}_{{ env }}_{{ role }}"keyed_groups: - key: customer prefix: customer_ separator: "" - key: env prefix: env_ separator: "" - key: role prefix: role_ separator: "" - key: customer_env prefix: customer_ separator: "_env_" - key: customer_env_role prefix: customer_ separator: "_env_role_"
group_vars examples
Common defaults
# inventories/main/group_vars/all.ymlansible_user: deployansible_ssh_common_args: "-o ServerAliveInterval=30"
Environment differences
# inventories/main/group_vars/env_test.ymlapp_log_level: debugfeature_flags: enable_new_ui: true# inventories/main/group_vars/env_prod.ymlapp_log_level: infofeature_flags: enable_new_ui: false
Role differences
# inventories/main/group_vars/role_db.ymldb_port: 5432backup_enabled: true# inventories/main/group_vars/role_proxy.ymlproxy_listen_port: 443
Customer differences
# inventories/main/group_vars/customer_acme.ymlcustomer_domain: acme.example.netsupport_sla: gold# inventories/main/group_vars/customer_internal.ymlcustomer_domain: internal.example.netsupport_sla: internal
Customer + env overrides (optional but powerful)
# inventories/main/group_vars/customer_acme_env_prod.ymlansible_user: prod-deploy
Playbook examples
# Assume:# - Inventory directory: inventories/main# - Playbook file: site.yml# 1) Run playbook against ALL hostsansible-playbook -i inventories/main site.yml# 2) Run playbook against ALL hosts of a specific customer# Example: customer = acmeansible-playbook -i inventories/main site.yml -l customer_acme# 3) Run playbook against ALL hosts of a specific customer environment# Example: customer = acme, env = prodansible-playbook -i inventories/main site.yml -l customer_acme_env_prod# Another example: internal testansible-playbook -i inventories/main site.yml -l customer_internal_env_test# 4) Run playbook against ALL hosts with a specific role# Example: role = dbansible-playbook -i inventories/main site.yml -l role_db# 5) Example: role = appansible-playbook -i inventories/main site.yml -l role_app

You must be logged in to post a comment.