feat(#3554): let a hive-owned service declare a scrape target

This commit is contained in:
atlas 2026-08-23 19:32:45 +02:00 committed by mara
commit ece21b0f7f

View file

@ -193,6 +193,41 @@ in
''; '';
}; };
scrapeTargets = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
example = lib.literalExpression ''{ hive-forge = "127.0.0.1:3000"; }'';
description = ''
Prometheus exposition endpoints this hive's collector scrapes, as
`<job name> = "<host>:<port>"`.
**A service declares its own entry, from its own module, under its
own `enable`** the same rule as the swarm tier's option of the
same name, and for the same reason: an entry exists only where the
service that named it runs, so the scraper and its target are
co-located by construction rather than by luck.
**This tier, not the swarm one, is where a HIVE-owned target
belongs.** The two are not interchangeable: a swarm service does
not belong to a hive, so its samples must never acquire a `hive`
label which is why that scraper lives one tier up. Putting a
hive-owned target there would either mislabel it or leave it
unattributed.
Scraping is unauthenticated on purpose. This receiver is a
prometheus-to-OTLP converter sitting next to what it reads, in the
same trust position as the agents already pushing to this
collector; the authenticated hop is the collector's own export
onward, which is one hop for the whole hive rather than one per
target.
Empty by default, and that is the shipped case no scrape
receiver is emitted at all, because a `prometheus` receiver with
nothing to scrape is a config that renders, starts and collects
nothing.
'';
};
clientSecretFile = lib.mkOption { clientSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = default =
@ -312,7 +347,20 @@ in
# prove this config STARTS, never mind that a sample arrives. # prove this config STARTS, never mind that a sample arrives.
validateConfigFile = true; validateConfigFile = true;
settings = { settings = {
receivers.otlp.protocols.http.endpoint = listen; receivers = {
otlp.protocols.http.endpoint = listen;
}
# Only when a service has actually declared a target. An enabled
# `prometheus` receiver with an empty `scrape_configs` renders,
# validates and starts perfectly while reading nothing — and the
# empty set is the default, so that inert shape would be what
# most hives deploy.
// lib.optionalAttrs (otel.scrapeTargets != { }) {
prometheus.config.scrape_configs = lib.mapAttrsToList (job: target: {
job_name = job;
static_configs = [ { targets = [ target ]; } ];
}) otel.scrapeTargets;
};
# One destination, and it is the swarm's collector. This tier # One destination, and it is the swarm's collector. This tier
# holds no upstream credential and writes no store: it receives # holds no upstream credential and writes no store: it receives
@ -352,7 +400,14 @@ in
service.extensions = lib.optional senderAuth authName; service.extensions = lib.optional senderAuth authName;
service.pipelines.metrics = { service.pipelines.metrics = {
receivers = [ "otlp" ]; # EXTENDED, not replaced. Assigning here instead of appending
# would drop `otlp` — the hive would stop receiving from its
# own agents while still rendering a config the collector
# starts cleanly on, so nothing would report the loss. The
# `optional` matters too: a pipeline naming a receiver that no
# longer exists is a startup failure, which `validateConfigFile`
# below turns into a build failure rather than a broken deploy.
receivers = [ "otlp" ] ++ lib.optional (otel.scrapeTargets != { }) "prometheus";
exporters = [ swarmName ]; exporters = [ swarmName ];
}; };
} }