GUI Free Life View on GitHub

How To Scale Up Ansible Playbooks and Roles in a Managable Way

ansible

May 16, 2015

Ansible is Awesome! Ansible is a Mess!

So you found Ansible, and you were all Woah! Ansible is awesome! Ansibilize all the things! Then you created a git repo and started hacking.

Playbooks look in the current directory to find roles, libraries, and inventories, so naturally you put everything in one big git repo, right?

You tried to follow the best practices for writing playbooks, you created roles, and maybe you wrote a filter plugin or a custom module for configuring an application unique to your environment. Eventually you wound up with a big repo of playbooks and inventory files and buried roles and realized there must be a better way to do this.

There Are No Masters

Ansible does not use an agent on the managed host, and it doesn’t have a central server (unless you use Ansible Tower, kinda). Basically anyone on your team can install ansible on their workstation and start adding to your mess or create another one just making things worse and worse.

How do you break this down into managable pieces?

Playbook Repos Provide a Context

Most of my use of Ansible is for provisioning of services rather than deploying a specific application. So, my ansible bits are not inside of an application’s git repo. How should they be tracked then?

How about creating a repo called playbook-task, so for provisioning something large like a multi-server Zimbra install, I create a repo called playbook-zimbra. The playbook repo will of course have at least one playbook yaml file, but it also defines what I call an ansible runtime context: config file, inventory file, group variables, host variables, roles, libraries, etc.

When creating a role within a playbook, the goal should be to make it as general as possible. Once a role is able to define its own reasonable defaults and parameterize all options as variables, it should be split out of the playbook repo into its own repo.

Creating Roles

It is usually better to follow an existing convention, even when it may be overkill, because it makes it easier for others and future-you to figure out what’s going on. Never underestimate the value of standards and conventions! Sometimes coming to a convention isn’t easy though.

Ansible does make it easy to create a skeleton for your role that is consistent with the rest of the world.

Just start by running ansible-galaxy init role_name. Most likely you want to create a git repo out of that role, so do that too.

cd ~/src
ansible-galaxy init foorole
cd foorole
git init
git add .
git commit -m firstsies
git remote add origin git@github.com:dlbewley/foorole.git
git push -u origin master

Now you have a git repo named foorole. You might rename the repo to ansible-foorole, but I prefer to push the repo with the name of foorole and only name my working directory ansible-foorole.

# create a foorole repo on github then push to it
git remote add origin git@github.com:dlbewley/foorole.git
git push -u origin master
cd ~/src
git clone git@github.com:dlbewley/foorole.git ansible-foorole

Ansible Galaxy

Ansible Galaxy is essentially a framework that makes it super simple to use a stranger’s role in your playbook. Roles are refrenced as stranger_name.role_name.

You can reference these strangers’ roles in a few ways.

Role dependencies currently only work with roles hosted on Ansible Galaxy. See fetch_role(), basically it assumes it will be downloading a .tar.gz of a git repo, most likely from github.

    # first grab the file and save it to a temp location
    if '://' in role_name:
        archive_url = role_name
    else: 
        archive_url = 'https://github.com/%s/%s/archive/%s.tar.gz' % (role_data["github_user"], role_data["github_repo"], target)
    print "- downloading role from %s" % archive_url

But maybe you don’t trust these strangers. Then what?

Build Your Own Galaxy

In addition to requirements.txt which assumes Ansible Galaxy as the source of the role, as of v1.8 there is support for a requirements.yml which let’s you point at a .tar.gz or SCM repo somewhere else, like your own local gitlab.

Installing Roles

There are tools or at least a tool, called Ansible Role Manager, which can help you manage roles, but another option is just a Makefile.

Something like this will allow you to type make install to resolve your requirements.yml:

.PHONY: galaxy-install ping

install: galaxy-install

galaxy-install:
	ansible-galaxy install -r requirements.yml --force

ping:
	ansible all -i hosts -m ping

Where do these roles get installed? Ansible-galaxy will install these roles to the first directory found in your roles_path. Remember that. We can take advantage of that.

Roles Path

If you are working on a playbook which may have roles stored along side it, and you want to use roles from Ansible Galaxy, what do you do?

Remember, I just said that that ansible-galaxy install places roles in the first directory in your roles_path? Just create a ansible.cfg in your playbook directory that looks something like this:

[defaults]
remote_user = root
inventory_file = hosts
roles_path = required-roles:roles

Then add required-roles to your .gitignore. Now, you can disentangle your roles and external roles within your playbook. I have to give credit to @command_tab for changing my life with this tip. :)

Organizing Repos When Working with Other Admins

You might run ansible from a laptop, your workstation, a VM. Your colleagues may do the same. How do you make sure each environment is consistent and compatible?

As you scale up use of Ansible on your team, what sort of groundwork can you lay for keeping things organized? I suggest a Ansible name space in a Gitlab instance then try to put all the ansible related bits in there as long as it makes sense. Sometimes a playbook is tightly bound to an application and it seems to make sense to keep it in a ansible sub dir of that repo.

How about some repo naming conventions?

Still To Be Determined

Distributing inventory is a problem I haven’t quite figured out yet. For now, each playbook has it’s own static inventory file and that works just fine for somethings, but it isn’t generally scalable.

A dynamic inventory script which queries a LDAP directory seems like the obvious choice, but with thousands of hosts how can this be done efficiently? Patterns are applied to host lists after the inventory is constructed. Somehow, the limitation needs to be included in the LDAP filter.