1 of 28

Simple, reusable charms with Ansible

michael.nelson@canonical.com

2 of 28

The next 30 mins...

  • A brief overview of Ansible for charmers
  • How you can use Ansible to declare your charm
  • 5 best things about charming with Ansible
  • Cutting down the cruft: Reusable charm roles

Along the way you can be deploying and investigating an example demo charm. If we’ve time at the end and any questions are exhausted, we can also have a quick discussion:

  • 5 worst things about charming with Ansible

3 of 28

Deploying the example service

Ensure you’ve got a bootstrapped juju environment ready, then:

$ mkdir -p ~/charms/precise && cd ~/charms/precise�$ git clone https://github.com/absoludity/charm-bootstrap-wsgi�$ cd charm-bootstrap-wsgi && make deploy�

You might want to watch the juju-log, or see the README to see how you can do a rolling upgrade, or investigate the playbook.yml, hooks.py or roles.

4 of 28

A brief overview of Ansible for charmers

5 of 28

Ansible - an overview for charmers

From docs.ansible.com:

Ansible’s goals are foremost those of simplicity and maximum ease of use. It also has a strong focus on security and reliability, featuring a minimum of moving parts... and a language that is designed around auditability by humans – even those not familiar with the program.

One thing that stands out and is not mentioned there is in-built support for sharing and reuse of functionality.

6 of 28

A simple task using the ‘apt’ module

- name: Install application dependencies.

apt: pkg={{ item }}

with_items:

- python-oops-wsgi

- python-oops-datedir-repo

- python-oops-amqp

- python-mimeparse

- python-webob

7 of 28

Handling changes to machine state

Ansible modules (like apt, service) are written to be idempotent so that you can notify and handle some tasks only when things change:

handlers:� - name: restart apache� service: name=apache state=restarted

tasks:

- name: template configuration file� template: src=template.j2 dest=/etc/foo.conf� notify:� - restart apache

8 of 28

Other useful ansible features

There are a few other points which are useful when using ansible for Juju charms:

  • Your tasks and handlers are organised into a playbook (which can be a single file, or a combination of your custom files and reusable roles).
  • Tasks can be tagged and ansible can play just the tasks matching certain tags
  • You can specify a bunch of “host variables” which will be available in all your playbook tasks, handlers and templates.

9 of 28

Using Ansible to declare your charm

10 of 28

Symlinking all hooks to hooks.py

Just like the standard charm-helpers python support, all hooks are symlinked to your hooks.py:

$ ls -l hooks

total 8

… charmhelpers

… config-changed -> hooks.py

… hooks.py

… install -> hooks.py

… nrpe-external-master-relation-changed -> hooks.py

… website-relation-changed -> hooks.py

… wsgi-file-relation-changed -> hooks.py

11 of 28

The hooks.py

#!/usr/bin/env python

import sys

import charmhelpers.contrib.ansible

hooks = charmhelpers.contrib.ansible.AnsibleHooks(

playbook_path='playbook.yml')

@hooks.hook('install', 'upgrade-charm')

def install():

"""Install ansible and let it handle the rest of the install/upgrade."""

charmhelpers.contrib.ansible.install_ansible_support(from_ppa=True)

# If you need custom functionality for other hooks which you can’t

# achieve with ansible alone, you can always add them here with the hook

# decorator, but generally there should be no need.

if __name__ == "__main__":

hooks.execute(sys.argv)

12 of 28

What happens when a charm hook is invoked

Whenever one of your hooks is invoked, the following happens for you automatically:

  • As normal, your hooks.py file is run with the hook name as arg 0, which results in
  • The AnsibleHooks() object reading the playbook.yml to get a list of all the tags used within the playbook and registering a default hook function for each.
  • If the hooks.py contains an explicit function for the current hook (like ‘install’ above), it is run first, and then
  • The current juju state is written out to a yaml dict for ansible (at /etc/ansible/host_vars/localhost), with your charm config at the top level and a relations dict etc..
  • All the tasks within your playbook that are tagged with the current hook name are then played out (eg. `ansible-playbook -c local playbook.yml --tags install`)

13 of 28

An example - the elasticsearch charm

The hooks.py for the elasticsearch charm is almost the same as the previous example (it has a litle extra functionality in the install hook). The playbook is as follows:

- hosts: localhost

roles:

- role: nrpe

check_name: check_http

check_params: -H localhost -u /_cluster/health -p 9200 -w 2 -c 3 -s green

service_description: "Verify the cluster health is green."

‘roles’ are a way to reuse existing playbook functionality (more on this later). This role ensures that a nagios check will be configured during the nrpe relation.

14 of 28

An example - the elasticsearch charm

handlers:

- name: Restart ElasticSearch

service: name=elasticsearch state=restarted

tasks:

- include: tasks/install-elasticsearch.yml

- include: tasks/peer-relations.yml

- name: Update configuration

tags:

- config-changed

template: src={{ charm_dir }}/templates/elasticsearch.yml

dest=/etc/elasticsearch/elasticsearch.yml

mode=0644 backup=yes

notify:

- Restart ElasticSearch

15 of 28

An example - the elasticsearch charm

- name: Start ElasticSearch

service: name=elasticsearch state=started

tags:

- start

- name: Stop ElasticSearch

service: name=elasticsearch state=stopped

tags:

- stop

- name: Relate the cluster name and host.

tags:

- client-relation-joined

command: >

relation-set

cluster-name={{ cluster_name }}

host={{ ansible_default_ipv4.address }}

port=9200

16 of 28

5 best things about charming with ansible

17 of 28

5 best things about charming with ansible

5. Simple to use and easy to read

4. Audibility of logs

Take a look at the juju log for the wsgi-example/0 unit of your example deployment.

You’ll see it starts with the default charm-helpers logging, but as soon as ansible takes over, you get a nice clean summary of exactly what changed for each hook. You can also ask ansible to give you a diff of what will change if you apply your playbook (great for auditing any cowboyed changes)

18 of 28

5 best things about charming with ansible

3. It’s declarative and has a simple serialization policy

… and tasks are (generally) idempotent.

2. It has an ever-growing library of built-in modules for doing nearly everything you need.

And if you find that what you need isn’t there, your quick solution is to use the command or shell module (which still provides you with idempotency if you’re careful), or to write and share your own module (it’s Python!)

19 of 28

5 best things about charming with ansible

And finally,

1. A great model for reuse and sharing of common tasks...

20 of 28

Reusable charm roles

21 of 28

Reusable charm roles: nrpe-external-master

An ansible role is just a reusable collection of tasks and handlers (and variables and defaults…) in a standard directory layout.

Open up charm-bootstrap-wsgi/playbook.yaml and you’ll see:

- role: nrpe-external-master

check_name: check_http

check_params: "-I 127.0.0.1 -p 8080 -e ' 200 OK' -s 'It works!'

service_description: "Verify wsgi-example is responding."

which means simply, “include the nrpe-external-master role and pass it the following values”. Note also that we can use the role multiple times with different values too (if we need multiple nagios checks)

22 of 28

Reusable charm roles: nrpe-external-master

The nrpe-external-master role has the following tasks:

- name: Write nagios check command config.

tags:

- nrpe-external-master-relation-changed

template:

src: "check_name.cfg.jinja2"

dest: "/etc/nagios/nrpe.d/{{ check_name }}.cfg"

- name: Write nagios check service definition for export.

tags:

- nrpe-external-master-relation-changed

template:

src: "check_name_service_export.cfg.jinja2"

dest: "/var/lib/nagios/export/service__{{ service_context }}-{{ unit_name }}_{{ check_name }}.cfg"

- name: Trigger nrpe-external-master-relation-changed to restart.

tags:

- nrpe-external-master-relation-changed

command: >

relation-set timestamp={{ ansible_date_time.iso8601_micro }}

23 of 28

Reusable charm roles: nrpe-external-master

We can now reuse that role for any charm that provides nagios checks to an nrpe-external-master subordinate.

That’s a small gain, but one which we already had with the charm-helpers python support.

24 of 28

Reusable charm roles: wsgi-app

We’re currently deploying quite a few python wsgi applications. For each deployment we need to:

  1. Setup specific users
  2. Install the built code into a specific location
  3. Install any package dependencies
  4. Relate to a backend (could be postgresql, could be elasticsearch)
  5. Render the settings
  6. Relate to a wsgi (gunicorn) service (via a subordinate charm)
  7. Setup log rotation
  8. Support updating to a new codebase without upgrading the charm
  9. Support rolling updates to a new codebase

But only 3, 4 and 5 are really specific to the application...

25 of 28

Reusable charm roles: wsgi-app

Open up the charm-bootstrap-wsgi/playbook.yml and you’ll see the role:

- role: wsgi-app

listen_port: 8080

wsgi_application: example_wsgi:application

code_archive: "{{ build_label }}/example-wsgi-app.tar.bzip2"

when: build_label != ''

That is, if the build_label config option is set, the wsgi-app role is included and it just works…. almost...

26 of 28

Reusable charm roles: wsgi-app

The one thing that the wsgi-app role can’t do for you…

- name: Write any custom configuration files

debug: msg="You'd write any custom config files here"

tags:

- config-changed

# Also any backend relation-changed events...

notify:

- Restart wsgi

...is update any custom configuration files that your code uses on config-changed or any backend relation-changed events.

27 of 28

Reusable charm roles: wsgi-app

You can look at the tasks of the wsgi-app role to see the details of everything that you’re getting for free:

  • setup-machine.yml - sets up required users/groups, dependencies, directory layout (based on options you provide), and log rotation.
  • setup-code.yml - handles obtaining your code tarball from either the charm or an external service, extracting your code, extracting to the correct directory and setting permissions.
  • main.yml - symlinking to the correct installed codebase (depending on config options, to support rolling back and rolling updates), wsgi and website relations, and an optional task to support rolling upgrades.

28 of 28

Reusable charm roles: wsgi-app

If you haven’t already, deploy the charm-bootstrap-wsgi service and take a look at the README to see how you can do a rolling upgrade right now.

Let me know if there are any unanswered questions or we can hear about some of the not so great points people have experienced charming with ansible...

Contact details:

michael.nelson@canonical.com

http://micknelson.wordpress.com/

G+ or @michaelanelson