Building and deploying a mini charm

1. Introduction

A good number of tutorials and explanations about Charmed Operators (“charms”) exist already. And recently, Erik Lönroth has presented a workshop session about deploying a minimal Charm. This tutorial here is the “written down version” of his session.

:wave: In case you would also like to present a session or share your experience with juju and charms with the community, please do not hesitate to reach out either on Mattermost or Discourse!


2. Overview

The tutorial uses a very simple source code template to build a minimal charm that just performs an “all done” status message at the end of the installation step. The charm will then be deployed using the Charmed Operator Lifecycle Manager.

In order to deploy a Charm, the Charmed Operator Lifecycle Manager needs a deployment environment. For this purpose, in this tutorial we will use LXD, which is a very easy to use container management environment. More concretely, we will build a charm and deploy it using Juju in a container created using LXD.

In summary, the steps we will perform are as follows:

  • Install prerequisites
  • Set up a new charm project
  • Strip down this charm to be minimal
  • Build this charm
  • Deploy this charm
  • Run commands if there are delays or problems
  • Finally, check the result

3. Install prerequisites

Install LXD

In order to deploy a Charm, we first need to install the Charmed Operator Lifecycle Manager (OLM). To install the OLM, we need to install a package named juju (which is why the OLM is often called “juju”). But, as its name indicates, the OLM is a lifecycle manager. That means we also need to configure it with something that it can manage. This can be a public cloud, a private cloud, or a virtualization environment. As mentioned above, in this tutorial we will configure it with LXD.

LXD is a very easy to use container management environment. It runs on many different Linux distributions and it can be used even on Mac and Windows.

The first step in setting up LXD is to install it. On Ubuntu, this can be done in one line, via snap:

$ sudo snap install lxd
…

The second step in setting up LXD is to initialise it. This can be done very easily using the command lxd init. This brings up an interactive mode. For the purpose of this tutorial, for each question you can simply choose the default values:

$ lxd init

Finally, IPv6 needs to be disabled:

lxc network set lxdbr0 ipv6.address none

:zap: Do not execute lxd init on a already set up lxd installation. It will likely reset a number of things including setup networking.

:point_up: Please note that the lxd package also contains lxc:. Lxd is the name for the management software while lxc is the name for the runtime. In fact you will see lxd and lxc in the remainder of the tutorial depending on which of the two is required at a particular step.

Install Juju

The second prerequisite step is to prepare the tool that the charm will be deployed with. This tool is the Charmed Operator Lifecycle Manager (OLM). Preparing the OLM basically entails installing it. More specifically, we will need to install a package named juju (which is why the OLM is often called “Juju” or “juju”).

Just like LXD, juju is available on a number of Linux distributions, and can be even installed on other OSes And, just like LXD, on Ubuntu it can be installed very easily via snap:

$ sudo snap install juju --classic
juju 2.9.19 from Canonical✓ installed

After it has been installed, you can verify the installation by using the juju version command:

$ juju version
Since Juju 2 is being run for the first time, downloaded the latest public cloud information.
2.9.19-ubuntu-amd64

Now you can check what clouds are known out-of-the-box by juju by using the juju clouds command:

$ juju clouds --all
You can bootstrap a new controller using one of these clouds...

Clouds available on the client:
Cloud        Regions  Default        Type        Credentials  Source    Description
aws          21       us-east-1      ec2         0            public    Amazon Web Services
aws-china    2        cn-north-1     ec2         0            public    Amazon China
aws-gov      2        us-gov-west-1  ec2         0            public    Amazon (USA Government)
azure        40       centralus      azure       0            public    Microsoft Azure
azure-china  4        chinaeast      azure       0            public    Microsoft Azure China
equinix      25       px             equinix     0            public    
google       21       us-east1       gce         0            public    Google Cloud Platform
localhost    1        localhost      lxd         0            built-in  LXD Container Hypervisor
oracle       4        us-phoenix-1   oci         0            public    Oracle Compute Cloud Service

For the managed clouds, juju uses a controller and if the default controller initialization for lxd did not perform so far (as it can be seen with the message You can bootstrap...), you can create a new controller with the juju bootstrap command.

As with LXD, this will bring up an interactive mode and, for the purpose of this tutorial, it is fine to simply choose at every step the default value:

$ juju bootstrap
Clouds
aws
aws-china
aws-gov
azure
azure-china
equinix
google
localhost
oracle

Select a cloud [localhost]: 

Enter a name for the Controller [localhost-localhost]: 

Creating Juju controller "localhost-localhost" on localhost/localhost
Looking for packaged Juju agent version 2.9.19 for amd64
Located Juju agent version 2.9.19-ubuntu-amd64 at https://streams.canonical.com/juju/tools/agent/2.9.19/juju-2.9.19-ubuntu-amd64.tgz
To configure your system to better support LXD containers, please see: https://github.com/lxc/lxd/blob/master/doc/production-setup.md
Launching controller instance(s) on localhost/localhost...
 - juju-2be1c8-0 (arch=amd64)          
Installing Juju agent on bootstrap instance
Fetching Juju Dashboard 0.8.1
Waiting for address
Attempting to connect to 10.199.245.184:22
Connected to 10.199.245.184
Running machine configuration script...
Bootstrap agent now started
Contacting Juju controller at 10.199.245.184 to verify accessibility...

Bootstrap complete, controller "localhost-localhost" is now available
Controller machines are in the "controller" model
Initial model "default" added

You can check again, what juju currently supports. Now, a local cloud on localhost with type lxd should be present:

$ juju clouds --all

Clouds available on the controller:
Cloud      Regions  Default    Type
localhost  1        localhost  lxd

Clouds available on the client:
Cloud        Regions  Default        Type        Credentials  Source    Description
aws          21       us-east-1      ec2         0            public    Amazon Web Services
aws-china    2        cn-north-1     ec2         0            public    Amazon China
aws-gov      2        us-gov-west-1  ec2         0            public    Amazon (USA Government)
azure        40       centralus      azure       0            public    Microsoft Azure
azure-china  4        chinaeast      azure       0            public    Microsoft Azure China
cloudsigma   12       dub            cloudsigma  0            public    CloudSigma Cloud
equinix      25       px             equinix     0            public
google       21       us-east1       gce         0            public    Google Cloud Platform
localhost    1        localhost      lxd         1            built-in  LXD Container Hypervisor
oracle       4        us-phoenix-1   oci         0            public    Oracle Compute Cloud Service
rackspace    6        dfw            rackspace   0            public    Rackspace Cloud

As you can see, the cloud of type lxd is present in the output. Juju will coordinate with lxd for deploying charms and its applications.

Install Charmcraft

This brings us to our third prerequisite for this tutorial, namely, a tool for packaging applications as charms. There are multiple ways one can do this. In this tutorial we will do this in the simplest possible manner, using the Charmed Operator Software Development Kit (SDK), specifically, the tool named charmcraft: It is used to build and package Charms. Just like LXD and the OLM, on Ubuntu the SDK’s charmcraft tool can be installed very conveniently via snap:

$ sudo snap install charmcraft --classic
charmcraft 1.2.1 from Canonical✓ installed

Get Erik’s Example Code Base

The last prerequisite is Erik’s git repository with example code. It contains one Charm implementation which will be used further on in this tutorial. For this tutorial it is required to check out this repository. It can be done with using git:

$ git clone https://github.com/erik78se/juju-operators-examples/

Alternatively, you can navigate to the Github repository in the Web browser and download the archive directly from the Web page. Git control is actually not required, thus a plain download works as well.

This is it for prerequisites, all should be set now to start with the tutorial.


4. Set up a New Charm Project

After the prerequisites have been set we can use the charmcraft tool to create an initial Charm with the built-in template. It starts with an creation of an empty folder and the execution the initialisation provided by the charmcraft tool inside this folder:

$ mkdir mini
$ cd  mini
$ charmcraft init
Charm operator package file and directory tree initialized.
TODO:

  CONTRIBUTING.md: 
        README.md: Describe your charm in a few paragraphs of Markdown
        README.md: Provide high-level usage, such as required config or relations
        README.md: Provide any relations which are provided or required by your charm
        README.md: Include a link to the default image your charm uses
     actions.yaml: change this example to suit your needs.
      config.yaml: change this example to suit your needs.
    metadata.yaml: fill out a display name for the Charmcraft store
    metadata.yaml: fill out the charm's description
    metadata.yaml: fill out the charm's summary
    metadata.yaml: replace with containers for your workload (delete for non-k8s)
    metadata.yaml: each container defined above must specify an oci-image resource
     src/charm.py: change this example to suit your needs.
     src/charm.py: change this example to suit your needs.
     src/charm.py: change this example to suit your needs.

If the charmcraft init returns the error Author not given, and nothing in GECOS field, you could set the author information with using charmcraft:

§ charmcraft init --author yourname

5. Strip Down to Mini Size

The result of the previous step should be a collection of files and folders. Remember, this tutorial has the goal to build a minimal Charm. Therefore, let’s consider deleting files which are not necessary for building a minimal Charm: actions.yaml, config.yaml and the folder tests.

:warning: To be very clear: it is basic practise (not good, but basic practise!) to use actions, declare configuration values and write tests. The reason for this step in this tutorial is to make a very minimal setup for experimentation only.

As an overview, consider the following listing:

$ ls -al
-rw-rw-r-- 1 sam sam    99 Nov 12 10:19 .flake8
-rw-rw-r-- 1 sam sam    55 Nov 12 10:19 .gitignore
-rw-rw-r-- 1 sam sam    24 Nov 12 10:19 .jujuignore
-rw-rw-r-- 1 sam sam   910 Nov 12 10:19 CONTRIBUTING.md
-rw-rw-r-- 1 sam sam 11358 Nov 12 10:19 LICENSE
-rw-rw-r-- 1 sam sam   528 Nov 12 10:19 README.md
-rw-rw-r-- 1 sam sam   472 Nov 12 10:19 actions.yaml <-remove-this
-rw-rw-r-- 1 sam sam   233 Nov 12 10:19 charmcraft.yaml
-rw-rw-r-- 1 sam sam   419 Nov 12 10:19 config.yaml <-remove-this
-rw-rw-r-- 1 sam sam   653 Nov 12 10:20 metadata.yaml
-rw-rw-r-- 1 sam sam    36 Nov 12 10:19 requirements-dev.txt
-rw-rw-r-- 1 sam sam    13 Nov 12 10:19 requirements.txt
-rwxrwxr-x 1 sam sam   344 Nov 12 10:19 run_tests
drwxrwxr-x 2 sam sam  4096 Nov 12 10:19 src
drwxrwxr-x 2 sam sam  4096 Nov 12 10:19 tests <-remove-this

In addition, open the file metadata.yaml and delete all fields below summary. This file shall look like the following text:

# Copyright 2021 yourusername
# See LICENSE file for licensing details.

# For a complete list of supported options, see:
# https://discourse.charmhub.io/t/charm-metadata-v2/3674/15
name: mini
display-name: |
  mini
description: |
  this is the tutorial!
summary: |
  some summary here

6. Test Run

Now you are ready to execute the first build of the Charm

$ charmcraft build
Packing charm 'mini_ubuntu-20.04-amd64.charm'...
Created 'mini_ubuntu-20.04-amd64.charm'.

:warning: With 1.5.0 of charmcraft charmcraft build is deprecated in favour of charmcraft pack.

Accordingly, this build will do the minimum of what can be packaged. What should happen now is that juju requests lxc to deploy a container for building the Charm. In order to check what happens on the level of lxc, you can check (with a different shell, of course) listing the containers:

$ lxc list --project charmcraft
+-----------------------------------+---------+-----------------------+------+-----------+-----------+
|               NAME                |  STATE  |         IPV4          | IPV6 |   TYPE    | SNAPSHOTS |
+-----------------------------------+---------+-----------------------+------+-----------+-----------+
| charmcraft-mini-1322311-0-0-amd64 | RUNNING | 10.222.236.175 (eth0) |      | CONTAINER | 0         |
+-----------------------------------+---------+-----------------------+------+-----------+-----------+

After the build has been done, no errors should return. The new Charm should be there, as you can check with a listing for the presence of the new file: mini_ubuntu-20.04-amd64.charm. That is done as a first test, but it will not show anything particular.


7. Charm Implementation and Build

You do not need to edit the main charm implementation file at src/charm.py. Rather, copy from the repository as downloaded in step3 the entire contents from deploy-minimal/src/charm.py and replace the contents of your new Charm implementation in mini/src/charm.py.

Let’s review the implementation for a moment: Note that all charms are inheriting from the charm base as declared at the line 14 class DeployMinimalCharm(CharmBase):.

The next point in the source code we would like to look at is the install hook. Whenever install is called, the charm method at line 27 def _on_install(self, event): is being executed. Take the time to check on a few more hooks, for example you can see the hook for changes to the configuration on line 34 def _on_config_changed(self, event): .

If you consider all the hook methods you will also notice two main things: the logging and the setting of some status value. It is important that at every function the status value is being set.

The mini Charm at mini/src/charm.py would be OK for now, we can go ahead with building the charm again:

$ charmcraft build
Packing charm 'mini_ubuntu-20.04-amd64.charm'...
Created 'mini_ubuntu-20.04-amd64.charm'.

:warning: With 1.5.0 of charmcraft charmcraft build is deprecated in favour of charmcraft pack.


8. Deploy the Mini Charm

After the build has been executed successfully, the charm file mini_ubuntu-20.04-amd64.charm should have been created. As the next step, the Charm can be deployed using juju, note that you point to the charm with the prefix ./ in order to let juju pick that file from your local file system:

$ juju deploy ./mini_ubuntu-20.04-amd64.charm
Located local charm "mini", revision 0
Deploying "mini" from local charm "mini", revision 0

:warning: If you do not use the prefix ./ but just write the plain charm name, juju will try to look for the Charm on Charmhub.io (and likely not find it there).

In order to check if everything went fine, the status of juju can be checked:

$ juju status
Model    Controller  Cloud/Region         Version  SLA          Timestamp
default  overlord    localhost/localhost  2.9.17   unsupported  10:48:41+01:00

App        Version  Status   Scale  Charm      Store     Channel  Rev  OS      Message
cassandra  3.11.11  active       1  cassandra  charmhub  stable    60  ubuntu  Live seed
mini                waiting    0/1  mini       local                0  ubuntu  waiting for machine

Unit          Workload  Agent       Machine  Public address  Ports              Message
cassandra/0*  active    idle        0        10.222.236.232  9042/tcp,9160/tcp  Live seed
mini/0        waiting   allocating  5        10.222.236.117                     waiting for machine

Machine  State    DNS             Inst id        Series  AZ  Message
0        started  10.222.236.232  juju-92a670-0  focal       Running
5        pending  10.222.236.117  juju-92a670-5  focal       Running

Most likely its status is at waiting for a machine, if you execute the commands quickly after each other it is very likely that you see in the status this message first until it is done.


9. Optional: Resolving Hiccups

If the status does not change and this the deployment feels like it failed, there is a number of standard steps you can use:

  • You can always check with the debug log of juju to see some suspicious output or error message. Use juju debug-log.
  • Depending on your machine building and deploying can take minutes, as with many things in server administration check with tools that something is actually working. If you deployed the lxd on your local machine, tools like top or htop for showing the cpu and memory utilisation can be useful to understand what is happening on your machine. If you use a remote cloud with juju, then you must have a look there accordingly.
  • Check source code again if you see deviation from the example or you missed saving the file after editing.
  • Rebuild with a quick charmcraft build to make sure you got everything after you properly saved the file.
  • If you feel, something got really stuck, you can consider to remove the application with executing juju remove-application mini.
  • If there is no progress after trying to remove an application, you could try juju remove-application mini --force.
  • After an application has been removed, you could execute the deployment again juju deploy ./mini_ubuntu-20.04-amd64.charm.
  • check the progress and the status with juju status.

10. Wrapping Up

If everything ran successfully, the status of the juju should look like the following:

$ juju status
Model           Controller  Cloud/Region         Version  SLA          Timestamp
deploy-minimal  overlord    localhost/localhost  2.9.17   unsupported  11:18:18+01:00

App   Version  Status  Scale  Charm  Store  Channel  Rev  OS      Message
mini           active      1  mini   local             2  ubuntu  Step: 3/3

Unit     Workload  Agent  Machine  Public address  Ports  Message
mini/1*  active    idle   1        10.222.236.247         Step: 3/3

Machine  State    DNS             Inst id        Series  AZ  Message
1        started  10.222.236.247  juju-b06cf6-1  focal       Running

Note that the message should be Step: 3/3. If that is there the deployment ran successfully and everything is fine – you have deployed this very simple Charm!

Further readings