Juju + Ansible = simpler charms

I’ve been working on some more support for ansible in the juju charm-helpers package recently [1], which has effectively transformed my juju charm’s hooks.py to something like:

# Create the hooks helper, passing a list of hooks which will be
# handled by default by running all sections of the playbook
# tagged with the hook name.
hooks = charmhelpers.contrib.ansible.AnsibleHooks(
playbook_path='playbooks/site.yaml',
default_hooks=['start', 'stop', 'config-changed',
'solr-relation-changed'])
@hooks.hook()
def install():
charmhelpers.contrib.ansible.install_ansible_support(from_ppa=True)

And that’s it.

If I need something done outside of ansible, like in the install hook above, I can write a simple hook with the non-ansible setup (in this case, installing ansible), but the decorator will still ensure all the sections of the playbook tagged by the hook-name (in this case, ‘install’) are applied once the custom hook function finishes. All the other hooks (start, stop, config-changed and solr-relation-changed) are registered so that ansible will run the tagged sections automatically on those hooks.

Why am I excited about this? Because it means that practically everything related to ensuring the state of the machine is now handled by ansibles yaml declarations (and I trust those to do what I declare). Of coures those playbooks could themselves get quite large and hard to maintain, but ansible has plenty of ways to break up declarations into includes and roles.

It also means that I need to write and maintain fewer unit-tests – in the above example I need to ensure that when the install() hook is called that ansible is installed, but that’s about it. I no longer need to unit-test the code which creates directories and users, ensures permissions etc., or even calls out to relevant charm-helper functions, as it’s all instead declared as part of the machine state. That said, I’m still just as dependent on integration testing to ensure the started state of the machine is what I need.

I’m pretty sure that ansible + juju has even more possibilities for being able to create extensible charms with plugins (using roles), rather than forcing too much into the charms config.yaml, and other benefits… looking forward to trying it out!

[1] The merge proposal still needs to be reviewed, possibly updated and landed :)

7 Responses

1) What’s the boundary between stuff that should be in a playbook, and stuff in a hook? Is the playbook stuff limited to the install hook, and relation hooks are straight python?

2) Does this approach obsolete some of charm-helpers utilities?

3) This still means rebuilding server setup for every new unit. Docker has a great solution to this, where they pre-build the image offline, cache it, and use that for new “units”. I can’t help but wonder if that’s a better approach in the long run, which would make ansible (as used here) redundant. Thoughts?

So regarding the boundary, I’m not 100%, but I don’t see any reason to keep any idempotent action to the machine state out of the nice declarative yaml, if it’s already supported by ansible. As an example, Ricardo asked earlier about running syncdb for a simple django setup – ansible supports that – so why wouldn’t that be part of the ansible yaml configuration to be run during upgrade-charm (or wherever new code is deployed)? I can’t think of a reason yet.

No – it’s not limited to the install hook. In the above example, all of start, stop, config-changed and solr-relation-changed are registered too, so that on those hooks ansible will apply any section tagged with the hook name. Sorry – that wasn’t clear. I’ll update the example.

Yes, I think using any declarative machine state library (ansible, puppet, saltstack etc.) does obsolete some of the charm-helper utilities (like creating users, creating directories, setting permissions etc) for me. If other people prefer to write (and test) python hooks calling charm-helper code, great, I just find it very brittle to test, so i’d rather declare the state and trust (to some degree) a 3rd party lib that it will fulfil the contract (I’m still keen to have integration tests for the deploy though).

Regarding the approach of pre-building images to cache and deploy for new units, that will be unreal when juju gets to that point, but whether you’re setting the machine state for all units (ie. what this does currently), or setting the machine state to create your image, I still want to be able to do that in a way that is simple to maintain, easy to update. I’m assuming that juju will still be used to create the image, or do you have ideas about how that would happen differently?

Regards building images, docker does it interactively, like version control. So you fire up the image locally at it’s current state, make some changes (install/uninstall/configure), then save that as the new base image. So (some of) your install hook is not needed anymore – system level deps should be already included in the docker image. Maybe also code, not sure. But you wouldn’t need to use something like ansible, as every change to your base image is checked into version control. Ansible makes sense when your base image is the basic one.

A possible downside to this is that I don’t think it’s easy to upgrade a running container to a new version of the image, not sure. Additionally, it’s not as dynamically composable as juju is, especially with reusable cross-cutting subordinate charms.

the thing with docker approach is that there is no upgrade path, although not sure if that is a good thing or a bad thing (still didn’t make up my mind about it)
and docker is still young and hasn’t stood the test of time
it could be integrated as another provider or a –containerized option to the service
I agree with michael, I would rather trust a 3rd party lib that it will fulfil the contract
my first impressions on the charms were that they a half assed (no offense) ansible implementation, so I guess using ansible/puppet/chef/whateverCM would be better