How to manage metrics

Pebble provides metrics for services and health checks in OpenMetrics format. Access to the Pebble metrics endpoint requires HTTP basic authentication with a username and password.

Charms should use Juju secrets to manage sensitive information such as authentication credentials.

This guide demonstrates how to:

  • Create a Juju user secret that stores the username and password. Users who deploy the charm will also need to do this.

  • Add a configuration option to pass the secret ID to the charm.

  • In the charm code, get the username and password from the secret, then create a Pebble identity to allow access to the metrics endpoint using the username and password.

  • Access the metrics endpoint after deploying the charm. Users who deploy the charm will also be able to do this.

Create a Juju user secret

Run juju add-secret to create a Juju user secret:

juju add-secret metrics-user-password username=test password=test

Caution

  • Use a random password generator to create a password. You can use your preferred password manager to generate one and store it securely in the password manager as the single source of truth.

  • Do not use this password for anything else other than metrics, because it will be sent over unencrypted HTTP as basic authentication.

This command returns a secret ID, which you’ll need in the following steps.

Add a configuration option for the secret ID

To pass the secret ID to the charm, define a configuration option named metrics-secret-id in charmcraft.yaml:

config:
  options:
    metrics-secret-id:
      description: Secret ID for the metrics username and password
      type: string

Create a Pebble identity

The charm retrieves the ID of the secret that stores the username and password from the configuration option.

In the handler for the config-changed event, we’ll retrieve the contents of the secret and create a Pebble identity. We’ll also handle the secret-changed event, in case the charm user changes the contents of the secret.

After retrieving the contents of the secret, we’ll use the replace_identities method to create a “basic” type identity in Pebble:

from passlib.hash import sha512_crypt

class MyCharm(ops.CharmBase):
    ...

    def _on_config_changed(self, event: ops.ConfigChangedEvent) -> None:
        # The user must have:
        # - Created a secret with keys 'username' and 'password'
        # - Stored the secret ID in the 'metrics-secret-id' configuration option
        if not self.config.get('metrics-secret-id'):
            return
        secret_id = str(self.config["metrics-secret-id"])
        secret = self.model.get_secret(id=secret_id)
        content = secret.get_content()
        self._replace_identities(content["username"], content["password"])

    def _on_secret_changed(self, event: ops.SecretChangedEvent) -> None:
        if not self.config.get('metrics-secret-id'):
            return
        if event.secret.id == self.config['metrics-secret-id']:
            content = event.secret.peek_content()
            self._replace_identities(content["username"], content["password"])

    def _replace_identities(self, username: str, password: str) -> None:
        identities = {
            username: ops.pebble.Identity(
                access="metrics",
                basic=ops.pebble.BasicIdentity(password=sha512_crypt.hash(password))
            ),
        }
        self.container.pebble.replace_identities(identities)
        logger.debug("New metrics username: %s", username)

    ...

The password of the Pebble identity is stored as a hash, which we generate using sha512_crypt.hash() from passlib.hash.

When Pebble receives a request to access the metrics endpoint, Pebble will verify that the basic authentication credentials in the request match the identity’s username and password.

Deploy the charm and grant access to the user secret

We’ll use a configuration file to set metrics-secret-id to the secret ID.

First, create a configuration file named metrics-config.yaml:

metrics-charm:
  metrics-secret-id: <secret-id-here>

Then, when deploying the charm, use the --config option to pass the configuration file:

juju deploy <charm-name> --config metrics-config.yaml

After deploying the charm, grant access to the user secret:

juju grant-secret metrics-user-password <charm-name>

Access the metrics endpoint

Within the same Kubernetes cluster

Deploying the charm causes Juju to create a Kubernetes service named <charm-name>-endpoints within the Kubernetes cluster. Use this service to connect to Pebble within each workload container. Pebble’s HTTP port for the first workload container is 38813.

For example, if you deploy a charm named my-charm in the test namespace, access the metrics endpoint at:

my-charm-endpoints.test.svc.cluster.local:38813/v1/metrics

You’ll need to use HTTP basic authentication with the username and password that you specified in the juju add-secret command.

Through an Ingress

To access the metrics endpoint from outside the Kubernetes cluster, use an Ingress.

Use the service <charm-name> service in the Ingress (which is also created by Juju) instead of the <charm-name>-endpoints service, as the latter is a headless service and doesn’t have a ClusterIP.

  • To expose the Pebble HTTP port, use set_ports in your charm code:

    self.unit.set_ports(38813)
  • To create an Ingress, use the following Ingress resource example (assuming the charm and service name is my-charm):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: metrics
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
  - http:
      paths:
      - path: /my-charm/(.*)
        pathType: Prefix
        backend:
          service:
            name: my-charm
            port:
              number: 38813

Access the metrics endpoint with HTTP basic authentication at HOSTNAME/my-charm/v1/metrics.