How to design an Ansible directory structure

I’m just starting out with Ansible, and I’m trying to design my Ansible catalog in an efficient way for the future. My understanding is that this is how a directory structure should look:

ansible/

group_vars/
host_vars/
library/
roles/
host_or_inventory_file
playbook.yml

It seems to me that over time, the ansible/ directory is going to be flooded with hundreds of host files and playbooks, and that seems so inefficient and messy that I can’t believe Ansible would have designed it that way. I am going to support hundreds of customer environments with at least dozens of playbooks for each customer; putting them all in the same directory will make that directory quite difficult to read and manage, even if they all run the same set of plays/playbooks.

So is my understanding of this wrong here? How does everybody else do it? What is best practice?

That is a way to organize (especially when first getting accustomed with Ansible), not necessarily the way. Ansible allows you to fully configure the inventory path, library path, role path, etc with either environment variables or using ansible.cfg

If you wanted to manage your customers as separate entities but use common roles, you could have the following setup. Just make sure you set the roles_path in ansible.cfg.

`
ansible/

– customers

– bar

– inventory

– group_vars
-- all – hosts
-- site.yml – foo
– inventory
– group_vars
-- all – hosts
-- site.yml – roles
– database
`-- webserver

`

  • James

I like that one. I tend to do:

deploy/
inventory/
live/
group_vars/
hosts
staging/
group_vars/
hosts
local/

roles/

site.yml

A deployment to the staging servers then becomes:

ansible-playbook -i inventory/staging site.yml

from within the ‘deploy’ directory.

I checked through the documentation for ansible.cfg, and the only setting I found was roles_path. I know everything else can be set via command switches, but that’s another issue.

There will be very little difference in the playbooks themselves across customer, so we decided to go with something like this:

ansible/

– group_vars/
– inventories/

– customer1/

– host_vars/
-- customer1 – customer2/
– host_vars/
-- customer2 -- library/ -- playbooks/ -- roles/ -- database/ – webserver/

That way ansible/ should remain completely empty.

Hi guys,

Starting here too with Ansible, this is what we are using for now. Do you see any problems when using the following topology?

.ansible

Believe it. Ansible is a bit of a mess when it comes to composability, but it’s slowly becoming better with each release (mostly.)

You can always host logic within subdirectories. For example, I keep all of my tasks broken down into “task include” files in my “tasks/” subdirectory. Each task include file is composed such that it expects every necessary variable to be passed into it.

There’s still a bit of a problem with include files “returning” values up the chain. Thus, it’s hard to write playbook submodules that, for instance, inspect the state of a system and then return it to the master for further use in other submodules. Even “set_fact”, when executed within a task include file, doesn’t reliably allow that fact to be seen by ancestors or peers.

Part of the problem is that Ansible mostly seems to be written with a “declarative” philosophy, but many of the modules have “imperative” logic baked into their arguments. It would be nice if Ansible explicitly supported both declarative and imperative logic, because hey, that’s really useful for a huge number of circumstances.

Another problem is that in its desire to be declarative, Ansible seems to lack the most basic of functionality in some of their modules. For example, can you believe that the “service” module only lets you enable or disable services, but not query the state of a service? That’s right, if you wanted to write a task or role that, for example, selectively targets iptables or firewalld based upon which service was active, you’d have to jump through real hoops to do that. In another example, the “firewalld” module uses the “state” argument to determine whether a rule should be allowed (state=“enabled”) or denied (state=“disabled”). However, that flies in the face of firewall configuration in general, because you’d want to be able to “add” and “remove” either type of firewall rule (i.e. “accept” or “deny”). So here, because Ansible wants to appear “declarative” instead of “imperative”, we now have a module that makes little to no sense.

So for composability purposes, it’s really hard to write generic-type tasks or roles that work regardless of various differences between target system. In this sense, Ansible really IS declarative, because it forces you to define, ahead of time, exactly what the system is supposed to look like, NOT how it’s supposed to respond depending upon variable configuration. If you want something more elaborate than that, you have to jump into Python and write your own modules.