6 minute read Published: Author: Derek Laventure
Cloud , Infrastructure , Ansible , Drumkit


You are reading our blog series: Pure Ansible Infrastructure


Peering into the cloud

In any Infrastructure-as-Code project, there is a need to have visibility over what’s actually live in the environment we’re managing. There’s also a need for the Code part to have a manifest for what should exist. Any infrastructure provisioning tool’s job is essentially to reconcile the difference between these two things.

In a traditional Ansible setup, these two concepts would be represented by a static inventory (typically a file named hosts). The inventory system within Ansible make target systems available for playbooks to apply their plays to. In this sense, a static hosts file is primarily a manifest. The variables system (which we’ll dive into in more depth in the next post) maps onto the inventory of target hosts (and host groups) so that appropriate values (say the size of the aegir0 droplet) can be associated when running plays against those hosts.

This is nice because it allows for playbooks that target hosts: all that can enforce (or check) for consistency across all systems in the environment. It gives us the means to assess what’s actually live in the environment, but primarily, the hosts file represents a manifest, or a description of what we expect to be live in the environment.

Raindrops keep falling on my head

Ansible’s dynamic inventory plugins turn this all on its head. In this context, the inventory is provided dynamically via some script or API (in our case, the DigitalOcean API) to give us a literal and accurate view of what is live.

Now, instead of our inventory being built from a static file describing what we want, it’s being generated based on what exists. This is pretty powerful and fairly easy to setup. Following the plugin docs, we create the file inventory/digitalocean.yml, which configures the plugin. Then we configure ansible.cfg to use the plugin.

With this setup, we can run an ansible-inventory --graph and see a complete view of the hosts the DigitalOcean is running, in the account for which we’ve provided credentials. We also get some nice features, like a series of variables associated with each host, dynamically populated with attributes like image, id, size, and tags. We can also compose other attributes like public_ip to simplify access to key details about our host.

What’s more, we leverage the keyed_groups to dynamically create host groups based on the tags that we assign when we create our droplets. Thus, when we create the aegir0 and specify “web” and “aegir” tags, and then later add a non-aegir webserver web0 tagged only with “web”, we would end up with an inventory that looks like:

 $ ansible-inventory --graph # Protip: add --vars here to see a full list of vars for each host!
@all:
  |--@ungrouped:
  |  |--localhost
  |--@web:
  |  |--aegir0
  |  |--web0
  |--@aegir:
  |  |--aegir0

Note that localhost is included above. This target is always implicitly available to Ansible, but we specify a second inventory plugin in our ansible.cfg, which is a traditional hosts file containing only localhost. This lets us associate the ansible_connection=localvariable ensuring Ansible doesn't try to SSH tolocalhost. We use localhost` as a target extensively for plays that make remote API calls, such as creating resources at DigitalOcean.

Side quest: getting OAuth token from the Vault

We’ll talk more about the Ansible Vault setup in our project in the next post, but you may notice in our digitalocean.yml file linked above, we specified the oauth_token credentials in a strange way:

# This is a trick to extract our oauth_token from our vault before it's fully available to ansible as a "vaulted" variable.
oauth_token: '{{ lookup("pipe", "cd `git rev-parse --show-toplevel`; ./inventory/get-token.sh") }}'

This was a perplexing part of getting this all set up- the inventory plugin expects us to provide a sensitive value to authenticate itself. Usually, to handle a sensitive value like this, we’d stick this in a vault.yml encrypted variables file, and then use it like so:

oauth_token: '{{ vaulted_oauth_token }}'

What we discovered, however, is that the vault hasn’t yet been opened at the time the inventory plugin is initializing, and so it gets an empty value and fails to authenticate if we do it the usual way.

In fact, the only way we could work out (and kudos to Christopher for this one!) was to wrap a shell script around ansible-vault to “manually” extract the relevant value from the encrypted vault. This get-token.sh script is not complicated, but feels somewhat fragile and out of character in what is otherwise a very robust and thoughtful Ansible ecosystem.

Perhaps we’ve overlooked something here, so if you have a better idea to handle this, please get in touch!

Small problem for small footprints

This dynamic inventory system is very powerful, but there’s a small problem. As described above, we no longer have an inventory that can function as a manifest of what should exist, because it’s always live updating what does exist. In fact, before we’ve created anything, running ansible-inventory would return nothing but localhost!

We’ve traded the simplicity of a static file for the accuracy of a dynamically updated inventory. In a large Ansible project, we would turn to an ERP or other directory system that could be a source of truth for “what should exist”, but in a small project, that’s overkill.

The good news is, for a small project (1-5 hosts), this isn’t really much of a problem at all. As we’ll see in the way we’ve structured our playbooks in the project, it’s pretty manageable to add a new host by copying one file, editing its variables to suit, and then building an application-level playbook to configure what will run on it. The “manifest” essentially becomes the contents of our playbooks/hosts directory, which is probably good enough for most projects with small footprints.

Once you grow beyond a handful of hosts, there probably comes a point where it becomes painful, but a full-blown ERP system still doesn’t make sense. At that point it’s probably worth looking to something like a structured YAML file listing hostnames and a few key parameters (droplet_size, etc) that Ansible can iterate over.

For the time being, this method of leveraging the power of dynamic inventory while essentially “hard coding” our hosts manifest in our playbooks feels like an acceptable trade-off, in the name of keeping the system overall as simple as possible (but no simpler).

With these pieces in places, we’re ready to add the next piece of the puzzle, setting and associating all the required variables our hosts need for our playbooks to configure them correctly. Watch out for our next post about Ansible variables and vault!


The article Dynamic inventory first appeared on the Consensus Enterprises blog.

We've disabled blog comments to prevent spam, but if you have questions or comments about this post, get in touch!