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?
- How to you separate your playbooks and your roles?
- How do you properly create roles?
- How do you access roles created by others on your team or on Ansible Galaxy?
- How do you use your own roles along with other people’s roles in the same playbook?
- How do you make sure all the above are available to your other admins?
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.
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 firstname.lastname@example.org: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 email@example.com:dlbewley/foorole.git git push -u origin master cd ~/src git clone firstname.lastname@example.org:dlbewley/foorole.git ansible-foorole
Ansible Galaxy is essentially a framework that makes it super simple to use a stranger’s role in your playbook. Roles are refrenced as
You can reference these strangers' roles in a few ways.
Install them individually:
ansible-galaxy install username.rolename
List them one per line in a
requirements.txtand install all at once:
ansible-galaxy install -r requirements.txt
List them as a role dependency in another role to be install automatically: Add to
# 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
- List them in
requirements.yml. More on this below.
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.
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
.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.
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
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?
- Role repos should be named after the role and have no prefix
- Module repos should have a prefix of
- Plugin repos should have a prefix of
- Playbook repos should have a prefix of
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.