Charm architecture¶
At its core, Traefik is a Go application that provides both Layer 4 and Layer 7 traffic management, allowing applications to expose themselves outside their network boundary. The traefik-k8s charm deploys and manages Traefik as a reverse proxy on Kubernetes, routing requests from outside the cluster to Juju units and applications with support for TLS termination, path-based and subdomain-based routing, and much more.
Charm architecture diagram¶
C4Container
System_Boundary(traefikcharm, "Traefik Charm") {
Container_Boundary(traefik_container, "Traefik Workload Container") {
Component(traefik_core, "Traefik", "Go Application", "Observes events, handles requests")
}
Container_Boundary(charm_container, "Charm Container") {
Component(charm_logic, "Charm Logic", "Juju Operator Framework", "Controls application deployment & config")
}
}
Rel(charm_logic, traefik_core, "Supervises<br>process")
UpdateRelStyle(charm_logic, traefik_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 layer forms the effective Pebble configuration, or plan:
traefik layer: runs the Traefik binary (
/usr/bin/traefik) and pipes output to both stdout and a log file at/var/log/traefik.log.
As a result, if you run kubectl get pods on a namespace named for the Juju model you’ve deployed the traefik-k8s charm into, you’ll see something like the following:
NAME READY STATUS RESTARTS AGE
traefik-k8s-0 2/2 Running 0 6h4m
This shows there are two containers: the workload container running Traefik (managed by Pebble) and the Juju agent sidecar container.
Containers¶
traefik¶
The traefik container runs the Traefik reverse proxy. It is configured with a configurations storage volume mounted at /opt/traefik/, which holds the dynamic configuration files that Traefik’s file provider watches for changes using fsnotify.
Key paths inside the container:
/etc/traefik/traefik.yaml— the static configuration file (entrypoints, providers, API settings)/opt/traefik/juju/— dynamic configuration directory with per-route YAML files/usr/bin/traefik— the Traefik binary/var/log/traefik.log— log file/usr/local/share/ca-certificates/— trusted CA certificates
OCI images¶
The traefik-k8s charm uses the official Ubuntu Traefik OCI image (docker.io/ubuntu/traefik:2-22.04). This source file of this OCI image is maintained at the Traefik rock repository.
We use Rockcraft to build the OCI image for the Traefik charm.
The image is published to Charmhub as a charm resource.
See more: How to publish your charm on Charmhub
Metrics¶
The charm exposes Prometheus metrics at the /metrics endpoint at the diagnostics port (8082). The metrics-endpoint relation enables Prometheus to scrape Traefik’s built-in metrics.
Juju events¶
For this charm, the following Juju events are observed:
traefik-pebble-ready: fired on Kubernetes charms when the requested container is ready. Action: configure and start the Traefik workload, write static/dynamic configuration, and replan the Pebble service.
config-changed: usually fired in response to a configuration change using the CLI. Action: regenerate Traefik static and dynamic configuration, update the Kubernetes LoadBalancer service, and restart the workload.
start: fired once when the unit is first started. Action: perform initial setup tasks for the charm.
stop: fired when the unit is being stopped. Action: clean up resources.
remove: fired when the unit is being removed. Action: clean up Kubernetes resources such as the LoadBalancer service.
update-status: fired at regular intervals. Action: validate the current state of the workload and update the charm status accordingly.
certificate_available: fired when a TLS certificate becomes available from the certificates provider. Action: store the certificate and reconfigure Traefik with TLS settings.certificates_relation_broken: fired when the TLS certificates relation is removed. Action: clean up TLS configuration and restart Traefik without TLS.
certificate_set_updated: fired when a CA certificate is received via thereceive-ca-certrelation. Action: update the trusted CA certificates in the container and runupdate-ca-certificates.auth_config_changed: fired when forward-auth configuration changes. Action: reconfigure the ForwardAuth middleware for all proxied routes.auth_config_removed: fired when forward-auth configuration is removed. Action: remove the ForwardAuth middleware from proxied routes.ingress data_provided: fired when an ingress requirer provides routing data (applies toingress,ingress-per-unit, andingress-per-apprelations). Action: generate dynamic routing configuration for the requesting application.ingress data_removed: fired when an ingress requirer removes its routing data. Action: delete the corresponding dynamic routing configuration.traefik_route ready: fired when a traefik-route charm provides raw Traefik configuration. Action: write the provided configuration to the dynamic config directory.show-proxied-endpoints: fired when the
show-proxied-endpointsaction is run. Action: display all currently configured ingress routes.
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 TraefikIngressCharm Python class which inherits from 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 traefik-k8s routing_mode=subdomain
A
config-changedevent is emitted.In the
__init__method is defined how to handle this event like this:
self.framework.observe(self.on.config_changed, self._on_change)
The method
_on_change, for its turn, will take the necessary actions such as waiting for all the relations to be ready, regenerating the Traefik static and dynamic configuration, and replanning the Pebble service.