Copyright @ Lenovo US

    Juju charms are, charming. It promises a selection of blueprints that hold magic to make an application deployment easy. But devils are in the details, as always the case. In this article we will walk in the charm world to learn its design.

    Bundle, charm, service, application

    Juju terms can be confusing. Approximately they can be viewed in a tree model, where a bundle can have multiple charms, a charm can contain multiple services, a service is composed of one or more applications and a group of relations, and a relation, if defined, must be one of the two types:

    1. provide (eg. I am MySQL and I provide a SQL database relation).
    2. require (eg. I'm Mediawiki and I require a SQL database).
    Juju control modeling

    In term of deployment, a service can be deployed to more than one machine so as to achieve HA; a machine can also have more than one services. Here we use the term machine broadly because it can also be a single LXD container, so in this case there can be many containers in a single VM, and many VMs on a single physical machine.

    Charm and layers

    How does a charm describe an application and its relations then? The anwser is: layers. A charm is built from three layers: basic/runtime layer, interface layer, and charm layer. Basic layer is like a library — common parts that many applications can use without conflicts. Thing like DB services, HTTP server, storage should belong to this category. Interface layer is an abstract of what a charm exposes for consumption. Case in point will be any database charm, who should always define an interface to exchange DB login credentials and ports. Charm layer is application specific, and its logic is following the reactive pattern.

    Juju charm layers

    Hooks and Reactive pattern

    Previously, charms are written as a list of hooks responding to events. This is still true because hooks are the actual scripts. Any charm supports the following hooks:

    1. install
    2. config-changed
    3. start
    4. upgrade-charm
    5. stop
    6. update-status
    7. leader-elected
    8. leader-settings-changed

    For each relation defined, there are four relation hooks:

    1. [name]-relation-joined
    2. [name]-relation-changed
    3. [name]-relation-departed
    4. [name]-relation-broken

    Hooks run in a paritcular sequence, and that sequence is hardcoded. This is terrible. So instead of individual hooks, we have now a better way to capture this concept using Statesreactive pattern. A charm can define arbitrary state, say foo. Another layer in the same charm can then uses Python decorator, eg. @when, @when_not, to specify the status of foo, which then determined whether the function will be executed. I suppose the framework itself handles event polling, broadcasting and the like.

    State has a namespace per charm per unit. Therefore, states do not go across unit boundary, nor charm boundary. For states between charms/units, use interface.

    Basic/runtime layer

    There are two ways to construct basic layer: use an existing one, such as the apache-php, or use the basic layer template to write your own. Don't forget to check out these existing runtime layers for reuse. Note: which one can be reused, and which one can not? I don't see any document to distinguish this.

    Interface layer

    Interface layer defines both the provides and the requires. One thing to remember is that relation does not define the actual connection at all! What it does is to pass along configurations — IP address, port, user name, password, and such. For example, if there is a MySQL provides and Wordpress requires, Wordpress will read from the providing side of configurations needed to make the connection.

    Charm relation and interface

    Communication scope

    Interfaces are categorized into three communication scopes: global level, service level, and unit level. Again, they are grouping concept, where unit is the atom (and the default level). Multiple units can belong to a service level, and everything else goes into global level.

    The communication scope is closely related to the relationship lifecycle, where units in the same scope will have data broadcasted to them so they will be aware of each other's state. Therefore, unit level can be used for application that provides no relation and has no peer-to-peer need; service level is certainly providing some type of relation so others can leverage; and global is just a catch-all scope.

    Relation and Interface

    So how does relation and interface come into play? Declaring relation is a strong statement. As we have already mentioned, there are two types of relations: provides and requires. The provides and requires keys defined in metadata.yaml are used to define pairings of charms that are likely to be fruitful. Consider mongodb's metadata:

    name: mongodb
    ...
    provides:
      database:
        interface: mongodb
    

    where in another metadata who is on the receiving end of this relation:

    name: my-node-app
    ...
    requires:
      database:
        interface: mongodb
    provides:
      website:
        interface: http
    

    The word database is the relation name. There is no restriction to it except it can not be juju or jujud. Relation name serves as a grouping because each relation can have more than one interface.

    The interface name, mongodb, is important, since it is being used to look up an existing relation registry for a match. I think this is how charm build can pull in dependencies of other charms/layers.

    Charm layer

    This is where the application logics live.

    Screencast

    See the process in action:

    Screencast showing deploying a Vanilla charm

    — by Feng Xia

    Related:

      2017-10-22
    Juju GUI nginx proxy

    In LXD on localhost we introduced using LXD container to bootstrap a Juju controller. But how to access the Juju GUI? Launching it is easy enough with $ juju gui from juju host;...

      2017-09-06
    Juju local LXD

    Using Juju's LXD provider is the least-hassle way to start an experience of Juju and its charms. However, if you have done charm development for a while, you know making a one line of code...

      2017-07-06
    Charm Ansible integration

    Let's face it. Ansible has the mouth (and market) share these days. For our modeling purpose, we are to utilize its procedural strength to carry out actions, which provides an abstraction instead of coding in charm's Python files.

    Design...