Observability Libs

  • By Jon Seager
Channel Revision Published Runs on
latest/edge 45 10 Jul 2024
Ubuntu 20.04
juju deploy observability-libs --channel edge
Show information

Platform:

charms.observability_libs.v1.kubernetes_service_patch

KubernetesServicePatch Library.

This library is designed to enable developers to more simply patch the Kubernetes Service created by Juju during the deployment of a sidecar charm. When sidecar charms are deployed, Juju creates a service named after the application in the namespace (named after the Juju model). This service by default contains a "placeholder" port, which is 65535/TCP.

When modifying the default set of resources managed by Juju, one must consider the lifecycle of the charm. In this case, any modifications to the default service (created during deployment), will be overwritten during a charm upgrade.

When initialised, this library binds a handler to the parent charm's install and upgrade_charm events which applies the patch to the cluster. This should ensure that the service ports are correct throughout the charm's life.

The constructor simply takes a reference to the parent charm, and a list of lightkube ServicePorts that each define a port for the service. For information regarding the lightkube ServicePort model, please visit the lightkube docs.

Optionally, a name of the service (in case service name needs to be patched as well), labels, selectors, and annotations can be provided as keyword arguments.

Getting Started

To get started using the library, you just need to fetch the library using charmcraft. Note that you also need to add lightkube and lightkube-models to your charm's requirements.txt.

cd some-charm
charmcraft fetch-lib charms.observability_libs.v1.kubernetes_service_patch
cat << EOF >> requirements.txt
lightkube
lightkube-models
EOF

Then, to initialise the library:

For ClusterIP services:

# ...
from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
from lightkube.models.core_v1 import ServicePort

class SomeCharm(CharmBase):
  def __init__(self, *args):
    # ...
    port = ServicePort(443, name=f"{self.app.name}")
    self.service_patcher = KubernetesServicePatch(self, [port])
    # ...

For LoadBalancer/NodePort services:

# ...
from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
from lightkube.models.core_v1 import ServicePort

class SomeCharm(CharmBase):
  def __init__(self, *args):
    # ...
    port = ServicePort(443, name=f"{self.app.name}", targetPort=443, nodePort=30666)
    self.service_patcher = KubernetesServicePatch(
        self, [port], "LoadBalancer"
    )
    # ...

Port protocols can also be specified. Valid protocols are "TCP", "UDP", and "SCTP"

# ...
from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
from lightkube.models.core_v1 import ServicePort

class SomeCharm(CharmBase):
  def __init__(self, *args):
    # ...
    tcp = ServicePort(443, name=f"{self.app.name}-tcp", protocol="TCP")
    udp = ServicePort(443, name=f"{self.app.name}-udp", protocol="UDP")
    sctp = ServicePort(443, name=f"{self.app.name}-sctp", protocol="SCTP")
    self.service_patcher = KubernetesServicePatch(self, [tcp, udp, sctp])
    # ...

Bound with custom events by providing refresh_event argument: For example, you would like to have a configurable port in your charm and want to apply service patch every time charm config is changed.

from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
from lightkube.models.core_v1 import ServicePort

class SomeCharm(CharmBase):
  def __init__(self, *args):
    # ...
    port = ServicePort(int(self.config["charm-config-port"]), name=f"{self.app.name}")
    self.service_patcher = KubernetesServicePatch(
        self,
        [port],
        refresh_event=self.on.config_changed
    )
    # ...

Creating a new k8s lb service instead of patching the one created by juju Service name is optional. If not provided, it defaults to {app_name}-lb. If provided and equal to app_name, it also defaults to {app_name}-lb to prevent conflicts with the Juju default service.

from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
from lightkube.models.core_v1 import ServicePort

class SomeCharm(CharmBase):
  def __init__(self, *args):
    # ...
    port = ServicePort(int(self.config["charm-config-port"]), name=f"{self.app.name}")
    self.service_patcher = KubernetesServicePatch(
        self,
        [port],
        service_type="LoadBalancer",
        service_name="application-lb"
    )
    # ...

Additionally, you may wish to use mocks in your charm's unit testing to ensure that the library does not try to make any API calls, or open any files during testing that are unlikely to be present, and could break your tests. The easiest way to do this is during your test setUp:

# ...

@patch("charm.KubernetesServicePatch", lambda x, y: None)
def setUp(self, *unused):
    self.harness = Harness(SomeCharm)
    # ...

class KubernetesServicePatch

Description

A utility for patching the Kubernetes service set up by Juju. None

Methods

KubernetesServicePatch. __init__( self , charm: CharmBase , ports , service_name , service_type: ServiceType , additional_labels , additional_selectors , additional_annotations )

Constructor for KubernetesServicePatch.

Arguments

charm

the charm that is instantiating the library.

ports

a list of ServicePorts

service_name

allows setting custom name to the patched service. If none given, application name will be used.

service_type

desired type of K8s service. Default value is in line with ServiceSpec's default value.

additional_labels

Labels to be added to the kubernetes service (by default only "app.kubernetes.io/name" is set to the service name)

additional_selectors

Selectors to be added to the kubernetes service (by default only "app.kubernetes.io/name" is set to the service name)

additional_annotations

Annotations to be added to the kubernetes service.

refresh_event

an optional bound event or list of bound events which will be observed to re-apply the patch (e.g. on port change). The install and upgrade-charm events would be observed regardless.

KubernetesServicePatch. is_patched( self )

Reports if the service patch has been applied.

Returns

bool

A boolean indicating if the service patch has been applied.