When developing charms, we encounter serveral layers of multiplicity, each layer adding complexity to the previous one.
Level 0: Charms
Charm authors cannot limit the number of app instances a juju admin can deploy of the same charm. For example, deploying two prometheus instances is perfectly valid:
juju deploy prometheus-k8s prom1
juju deploy prometheus-k8s prom2
One of the questions you should be asking yourself is if it makes sense to relate two apps of the same type to one another:
$ juju relate prom1 prom2
ERROR ambiguous relation: "prom1 prom2" could refer to
"prom1:metrics-endpoint prom2:self-metrics-endpoint";
"prom2:metrics-endpoint prom1:self-metrics-endpoint"
In prometheus’s case this could mean: prom1
is my main instance, and prom2
provides self-monitoring by scraping prom1’s /metrics
endpoint, in which case we’d
juju relate prom1:metrics-endpoint prom2:self-metrics-endpoint
Level 1: Relations
Charm authors can limit the number of relation a given app has over a given relation. In most cases, a charm can have multiple relations of the same type to other (remote) charms. For example:
- A single prometheus charm can be related to any number of charms over the
metrics-endpoint
relation, to scrape each and every one of them. - A single traefik charm can be related to any number of charms over the
ingress-per-unit
relation, providing different ingress address for each. Conversely, however, an ingress requirer (e.g. prometheus) should only allow a single relation to an ingress provider (e.g. traefik). We do this by specifyinglimit: 1
inmetadata.yaml
.
Level 2: Units
Multiple units of the same charm may offer redundancy / fault-tolerance / reliability / scalability / high-availability. Charm authors cannot limit the number of units a juju admin can scale up an app to (you could “block” all non-leader units in your charm code though).
juju scale-application prom1 3
For prometheus this means:
- All three units of
prom1
need to scrape all the related scrape targets over themetrics-endpoint
relation. - All remote units related to
prom1
overremote-write
must be “told” to push metrics not just to the endpoint of theprom1/0
unit, but also to the endpoints ofprom1/1
andprom1/2
units as well. - The traefik charm needs to reconfigure itself to provider ingress urls to the two newly joined units of
prom1
. -
prom2
needs to become aware of the additionalprom1
units it needs to provide self-monitoring for. - Etc.
Level 3: Charm libraries
When other charm authors use your charm libraries, they may want to create multiple instances of your charm library for the same realtion. For example:
- Forward logs to Loki from multiple containers in the same pod (loki-k8s/186)
- Instantiate multiples instances of
ingress-per-unit
to obtain multiple ingress urls for different ports:IngressPerUnitRequirer(self, relation_name="ingress", port=80)
IngressPerUnitRequirer(self, relation_name="ingress", port=81)
- Or attempt two different relation names but over same relation interface:
IngressPerUnitRequirer(self, relation_name="ingress1", port=80)
IngressPerUnitRequirer(self, relation_name="ingress2", port=81)
Arguably, this is an antipattern and charm libraries thould be singletons, but currently there is no built-in code construct preventing charm lib users from doing this.
Conclusion
In conclusion, charm authors should be mindful about the semantics, usability and scalability of each level of multiplicity. Opinionated design decisions are key.