Juju has been around for some time. As a result, charm writing has gone through multiple frameworks and patterns. Here we describe the resulting charm taxonomy.
- Charm types, by substrate
- Charm types, by generation
Firstly, we can differentiate charms by the substrate they are intended to run on; either machines or containers on Kubernetes. For machine charms, we can further draw a line between principal and subordinate charms. In Kubernetes charms, we can identify a few fuzzy but useful categories of charms, depending on the type of the workload they manage.
Finally, we can differentiate charms by the technology they are written with. Several generations of libraries have been used, the latest of which is
There are ‘machine charms’, meant to be deployed on VMs, and ‘Kubernetes charms’, meant to be deployed on Kubernetes.
Juju’s beginnings were centered around simplifying the deployment of complex applications and services in a cloud-first world. At the time, many of those applications were run in virtual machines or on bare-metal servers, and deployments to these environments continue to enjoy first-class support. A machine charm can be deployed to a number of different underlying compute/storage resource providers:
- Bare-metal (using MAAS)
- Virtual machine (using KVM in Openstack, EC2 in AWS, VMware Environment etc.)
- Container (using LXD cluster)
Examples of machine charms include:
A machine charm can have the
subordinate role. When a machine charm is not subordinate, it is said to be
principal. The basic difference is that a subordinate charm is deployed on the same unit as the principal charm it is attached to, while a principal charm is deployed on a new unit (i.e. its own machine).
All charms are principal charms, except those that are subordinate.
Subordinate charms were created to enable the development of charms that could be deployed alongside existing charms – also known as principal charms – to augment them with specific functionality. They are, in many ways, analogous to sidecar charms.
A subordinate charm depends on the creation of a relationship to a principal charm, it is never deployed as a standalone application. This is best explained with an example:
Consider the deployment of a large web application scaled to
n replicas. Each instance of the charm comprises a
unit, in Juju parlance, and an
App is the sum of all units of a given charm with the same name. When a large web application is scaled to
units will be started by Juju.
The administrators of the web application will likely want to collect logs from the application server. The developer of the application charm may not have included a mechanism for forwarding logs from the service that is compatible with the administrator’s specific environment. By using a subordinate charm such as
rsyslog-forwarder, the administrator can ensure that each deployed unit of their web application is automatically configured to forward logs using
rsyslog. To do this, they must deploy the subordinate and
juju integrate their web application to it.
Subordinate charms are written in the same way as other charms, with the addition of an extra
subordinate flag in the charm’s metadata.
Examples of subordinate charms include:
Proxy machine charms (also sometimes called “integrator charms”) are the analogues of workload-less kubernetes charms. Historically they have been developed to act as stand-ins for other applications, hence the name. These are charms that do nothing or little in terms of software installation on the host VM, and whose job consists solely in interacting with Juju constructs.
Examples of proxy charms include:
The first charms were machine charms. More recently, Juju introduced support for Charmed Operators on Kubernetes. Juju can bring the same benefits to applications deployed on Kubernetes, by placing operators alongside workloads to manage them throughout their lifecycle. When a model is created with Juju, a corresponding Kubernetes namespace is created. When a charm is deployed on Kubernetes, it is deployed as a StatefulSet that manages a set of Pods running the specified application containers alongside a sidecar container containing the charm code.
Examples of Kubernetes charms include:
Over the time, some patterns have emerged in Kubernetes charms.
A charm is an operator, so a natural categorization of charms is based on “what is that thing which the charm operates”.
Some charms operate workloads running on a container; some charms operate Kubernetes resources, while some others operate
juju resources. These are the
workload-less charm categories we describe in this section.
This categorization is fuzzy, since a charm can in principle do multiple of these things at once, or even “other” things.
pod is a k8s construct), similar patterns could be identified in the machine charm space.
By definition, the sidecar pattern is designed to allow the augmentation of workloads with additional capabilities and features. The pattern is implemented in this case by a small auxiliary container, located in the same Pod as your application, that provides operations functionality - this is exactly how Juju operates in other environments, and a well-established pattern in the Kubernetes community.
By utilising this pattern, we ensure that there is always an operator right next to every unit of the workload, irrespective of how the application is scaled. The operator will always have direct access to shared memory, the same network namespace and the ability to manipulate the workload as required to keep it running smoothly. This approach simplifies the operation of upstream or third-party application images, enabling administrators to make changes at runtime to suit their environment should they wish, without the requirement to maintain a fleet of bespoke container images.
For charm developers, these benefits are realised by utilising Pebble to manage workloads. Pebble is a lightweight, API-driven process supervisor designed for use with Charmed Operators. Pebble enables charm developers to define how they want workloads to run, and provides an API that enables operations code to manage the workloads throughout their life.
Examples of sidecar charms include:
Charms are processes that exchange data. Sometimes you want to manipulate that data without having to rewrite or reconfigure the charms themselves; in that case you can implement a “charm-in-the-middle” pattern that sits in between and manipulates the relation data acting like a filter/proxy of some kind. Other times you want to write a charm that manages a stack of other charmed applications (danger zone),
Examples of workload-less charms include:
Podspec charms create and manage Kubernetes resources that are used by other charms or applications running on the cloud.
This pattern is discouraged because it is more difficult to implement correctly, as the resources one creates and manages via podspec charms practically avoid juju’s control, and therefore sidestep its model. While one of the core tenets of juju is that juju could suddenly disappear and all of the cloud services should continue to work as they did before (all that juju does is help operating them, not keeping them alive), the idea is that charms “cooperate” with juju by not doing anything without going through juju first, to prevent juju’s model from desyncing.
Examples of podspec charms include:
juju v.3.0, podspec charms are deprecated.
Charm development has been going on for years, so naturally many attempts have been made at making the development easier.
The ‘raw’ API juju exposes can be interacted with directly, but most people will want to use (at least) the bash scripts that come by default with every charm deployment, called
hook tools. If your charm only uses those, then you’re writing a “bare” charm.
If you fancy using a higher-level, object-oriented Python library to interact with the juju model, then you should be using
There exists another python framework that also wraps the hook tools but offers a different (less OOP, less idiomatic) interface, called
reactive. This framework is deprecated and no longer maintained, mentioned here only for historical reasons.
Examples of Ops-based charms include:
These are charms developed using the reactive framework.
Note that this is a framework that has now been superseded by Ops.
Please do not use reactive. Use Ops.
Examples of reactive-based charms include:
These are charms developed without the help of a framework, with all the hook invocations being coded manually.
For this reason these charms are also called ‘hooks-based’, or ‘hooks-only’, charms.
Examples of bare charms include: