Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
2 changed files with 41 additions and 129 deletions

View file

@ -181,33 +181,6 @@ in
addresses its siblings by name.
'';
};
scrapeTargets = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
example = lib.literalExpression ''{ forgejo = "127.0.0.1:3000"; }'';
description = ''
Prometheus exposition endpoints this collector scrapes, as
`<job name> = "<host>:<port>"`.
**A service declares its own entry, from its own module, under its
own `enable`.** That is what puts the scraper and the target on the
same host by construction rather than by luck: an entry exists only
where the service that named it runs. Do not assemble the list here.
Every swarm service being co-located is a property of the all-local
deployment, not a guarantee — and that is precisely the case where
the difference is invisible until a swarm splits across hosts.
Samples land in a swarm-level pipeline that stamps `swarm` and
**never** `hive`: a swarm service does not belong to a hive, and
`hive` stays a property of which authenticated receiver accepted a
push, not something a scrape can acquire.
Empty by default, in which case no scrape receiver, processor or
pipeline is emitted at all — an enabled scraper with nothing to
scrape is the inert configuration this option exists to avoid.
'';
};
};
config = lib.mkIf (config.services.hyperhive.enable && cfg.enable) {
@ -414,32 +387,15 @@ in
# an auth claim yields nothing — silently, with a healthy
# startup), and one receiver holding many credentials never
# reveals which one matched.
receivers =
lib.mapAttrs' (
h: p:
lib.nameValuePair "otlp/${h}" {
protocols.http = {
endpoint = "127.0.0.1:${toString p}";
auth.authenticator = "oidc/${h}";
};
}
) hivePorts
# MERGED with the per-hive receivers, never assigned over
# them. A plain assignment here would drop every hive's
# receiver and still render a valid config that starts
# cleanly — the collector has no opinion about how many
# pipelines it was supposed to have.
#
# Only emitted when a service has actually declared a
# target: a `prometheus` receiver with nothing to scrape is
# the shape this whole issue is about, a config that renders
# and deploys perfectly while adding no data.
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
prometheus.config.scrape_configs = lib.mapAttrsToList (job: target: {
job_name = job;
static_configs = [ { targets = [ target ]; } ];
}) cfg.scrapeTargets;
};
receivers = lib.mapAttrs' (
h: p:
lib.nameValuePair "otlp/${h}" {
protocols.http = {
endpoint = "127.0.0.1:${toString p}";
auth.authenticator = "oidc/${h}";
};
}
) hivePorts;
exporters =
lib.optionalAttrs vmCfg.enable {
@ -496,28 +452,14 @@ in
# `exporterNames` is shared by every pipeline — where a
# sample goes is a property of this tier, not of the hive
# that sent it.
service.pipelines =
lib.mapAttrs' (
h: _:
lib.nameValuePair "metrics/${h}" {
receivers = [ "otlp/${h}" ];
processors = [ "resource/${h}" ];
exporters = exporterNames;
}
) hivePorts
# Its OWN pipeline, and that separation is the ruling, not a
# tidiness choice: every `resource/<hive>` above UPSERTS a
# `hive` key, so a scraped swarm sample routed through any of
# them would acquire the one label a swarm-level service must
# not have. Keeping it out of them makes the absence
# structural rather than something to remember to strip.
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
"metrics/swarm" = {
receivers = [ "prometheus" ];
processors = [ "resource/swarm" ];
exporters = exporterNames;
};
};
service.pipelines = lib.mapAttrs' (
h: _:
lib.nameValuePair "metrics/${h}" {
receivers = [ "otlp/${h}" ];
processors = [ "resource/${h}" ];
exporters = exporterNames;
}
) hivePorts;
}
// {
extensions = lib.mapAttrs' (
@ -565,51 +507,36 @@ in
# processor is the whole attribution boundary — the value
# is a constant per receiver, so it says which hive
# authenticated, not which hive claimed to be sending.
processors =
lib.mapAttrs' (
h: _:
lib.nameValuePair "resource/${h}" {
attributes = [
{
key = "hive";
value = h;
action = "upsert";
}
# Stamped here rather than on a separate upstream-only
# pipeline, which would double the pipeline count to
# withhold one constant label from the local store. It
# is redundant there — one VictoriaMetrics per swarm, so
# every series in it already belongs to this swarm — but
# a constant label multiplies no series, and it means
# what LEAVES and what STAYS have the same shape.
#
# Upstream is where it stops being redundant: that is the
# one hop where several swarms can land in one store, and
# samples that cannot name their swarm collide there
# exactly as hives collided here before per-hive
# receivers existed.
{
key = "swarm";
value = swarmDisplayName;
action = "upsert";
}
];
}
) hivePorts
# The swarm tier's own stamp: `swarm` and deliberately NO
# `hive`. A scraped swarm service belongs to the swarm, not to
# any one hive, so there is no honest value to put there — and
# an invented one (a sentinel, the local hive's name) would be
# queried as though it meant something.
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
"resource/swarm".attributes = [
processors = lib.mapAttrs' (
h: _:
lib.nameValuePair "resource/${h}" {
attributes = [
{
key = "hive";
value = h;
action = "upsert";
}
# Stamped here rather than on a separate upstream-only
# pipeline, which would double the pipeline count to
# withhold one constant label from the local store. It
# is redundant there — one VictoriaMetrics per swarm, so
# every series in it already belongs to this swarm — but
# a constant label multiplies no series, and it means
# what LEAVES and what STAYS have the same shape.
#
# Upstream is where it stops being redundant: that is the
# one hop where several swarms can land in one store, and
# samples that cannot name their swarm collide there
# exactly as hives collided here before per-hive
# receivers existed.
{
key = "swarm";
value = swarmDisplayName;
action = "upsert";
}
];
};
}
) hivePorts;
};
};

View file

@ -113,21 +113,6 @@ in
}
];
# This store publishes its own health as prometheus metrics on the same
# listener it serves queries on, so the swarm's collector can scrape it
# with no exporter and no extra port.
#
# Declared here rather than in the collector's module because that is the
# rule the option carries: an entry exists only where the service that
# named it runs, which is what keeps scraper and target on one host by
# construction instead of by luck.
#
# The loopback literal introduces no new assumption — it is the address
# this module already pins the listener to, and the same one the
# collector's `otlphttp/victoriametrics` exporter already writes to. If
# that reach is ever wrong, it is wrong for the write path first.
services.hyperhive.swarm.otel.scrapeTargets.victoriametrics = "127.0.0.1:${toString cfg.port}";
services.nginx.virtualHosts."${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
listen = gatewayCfg.lib.listen;
extraConfig = gatewayCfg.lib.securityHeaders;