Charm architecture
At its core, Hockeypuck is a Go application that integrates with PostgreSQL and Traefik. The Hockeypuck charm was developed using the 12-factor Go framework. This framework allows us to easily deploy and operate Hockeypuck and its associated infrastructure, such as databases and ingress.
Charm architecture diagram
Below is a diagram of the application architecture of the Hockeypuck charm.
C4Container
System_Boundary(hockeypuckcharm, "Hockeypuck Charm") {
Container_Boundary(hockeypuck_container, "Hockeypuck Workload Container") {
Component(hockeypuck_core, "Hockeypuck", "Go Application", "Observes events, serves web requests")
}
Container_Boundary(charm_container, "Charm Container") {
Component(charm_logic, "Charm Logic", "Juju Operator Framework", "Controls application deployment & config")
}
}
Rel(charm_logic, hockeypuck_core, "Supervises<br>process")
UpdateRelStyle(charm_logic, hockeypuck_core, $offsetX="-30")
The charm design leverages the sidecar pattern to allow multiple containers in each pod with Pebble running as the workload container’s entrypoint.
Pebble is a lightweight, API-driven process supervisor that is responsible for configuring processes to run in a container and controlling those processes throughout the workload lifecycle.
Pebble services
are configured through layers, and the following container represents one layer forming the effective Pebble configuration, or plan
:
- The Hockeypuck container itself, which has a webserver configured in HTTP mode.
As a result, if you run a kubectl get pods
on a namespace named for the Juju model you’ve deployed the Hockeypuck charm into, you’ll see something like the following:
NAME READY STATUS RESTARTS AGE
hockeypuck-k8s-0 2/2 Running 0 6h4m
This shows there are 2 containers - the one named above, as well as a container for the charm code itself.
And if you run kubectl describe pod hockeypuck-k8s-0
, all the containers will have a command /charm/bin/pebble
. That’s because Pebble is responsible for the processes’ startup as explained above.
OCI images
We use Rockcraft to build the OCI image for the Hockeypuck charm. The image is defined in hockeypuck-k8s rock. The rock and the charm are published to Charmhub, the official repository of charms.
See more: How to publish your charm on Charmhub
Metrics
Metrics are provided by the workload container at the /metrics
endpoint at port 9626.
See Metrics for a full list.
Juju events
For this charm, the following Juju events are observed:
-
app_pebble_ready: fired on Kubernetes charms when the requested container is ready. Action: validate the charm configuration, run pending migrations and restart the workload.
-
config_changed: usually fired in response to a configuration change using the CLI. Action: validate the charm configuration, run pending migrations and restart the workload.
-
secret_storage_relation_created: fired when the relation is first created. Action: generate a new secret and store it in the relation data.
-
secret_storage_relation_changed: fired when a new unit joins in an existing relation and whenever the related unit changes its settings. Action: validate the charm configuration, run pending migrations and restart the workload.
-
secret_storage_relation_departed: fired when a unit departs from an existing relation. Action: validate the charm configuration, run pending migrations and restart the workload.
-
update_status: fired at regular intervals. Action: validate the configuration, run pending migrations and restart the workload.
-
secret_changed: fired when the secret owner publishes a new secret revision. Action: validate the configuration, run pending migrations and restart the workload.
-
database_created: fired when a new database is created. Action: validate the charm configuration, run pending migrations and restart the workload.
-
endpoints_changed: fired when the database endpoints change. Action: validate the charm configuration, run pending migrations and restart the workload.
-
database_relation_broken: fired when a unit participating in a non-peer relation is removed. Action: validate the charm configuration, run pending migrations and restart the workload.
-
ingress_ready: fired when the ingress for the app is ready. Action: validate the charm configuration, run pending migrations and restart the workload.
-
ingress_revoked: fired when the ingress for the web app is not ready anymore. Action: validate the charm configuration, run pending migrations and restart the workload.
-
rotate_secret_key: fired when secret-rotate is executed. Action: generate a new secret token for the application.
-
block_keys_action: fired when the block_keys action is run. Action: deletes the required keys from the database and adds them to the blocklisted keys list.
-
rebuild_prefix_tree_action: fired when the rebuild_prefix_tree action is run. Action: Rebuilds the ptree used by Hockeypuck.
-
lookup_key_action: fired when the lookup_key action is run. Action: Searches for the required key in the database.
See more in the Juju docs: Hook
Charm code overview
The src/charm.py
is the default entry point for a charm and has the HockeypuckK8SCharm
Python class that inherits from PaasCharm
, which internally uses CharmBase
. CharmBase
is the base class from which all charms are formed, defined by Ops (Python framework for developing charms).
See more in the Juju docs: Charm
The __init__
method guarantees that the charm observes all events relevant to its operation and handles them.
Take, for example, when a configuration is changed by using the CLI.
- User runs the configuration command:
juju config hockeypuck-k8s app-port=11371
- A
config-changed
event is emitted. - In the
__init__
method is defined how to handle this event like this:
self.framework.observe(self.on.config_changed, self._on_config_changed)
- The method
_on_config_changed
, for its turn, will take the necessary actions such as waiting for all the relations to be ready and then configuring the containers.