Watch
0
0
Fork
You've already forked hyperhive
0

move links, adminSync.ignoreUsers and scrapeTargets under their service's deploy namespace

This commit is contained in:
atlas 2026-11-01 20:34:50 +01:00
commit 90272ec3c0

View file

@ -18,6 +18,7 @@ let
cfg = config.services.hyperhive.swarm.otel;
deployCfg = config.services.hyperhive.deploy;
upstream = deployCfg.swarm-otel.upstream;
scrapeTargets = deployCfg.swarm-otel.scrapeTargets;
vmCfg = config.services.hyperhive.swarm.victoriametrics;
vlCfg = config.services.hyperhive.swarm.victorialogs;
hyperhiveCfg = config.services.hyperhive;
@ -333,6 +334,58 @@ in
# operator's choice for this deployment, so both hang off the deployment.
# `enable` already lives in ./deploy.nix, which also carries the rename.
options.services.hyperhive.deploy.swarm-otel = {
scrapeTargets = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
example = lib.literalExpression ''{ collector = "127.0.0.1:8889"; }'';
description = ''
Prometheus exposition endpoints this collector scrapes, as
`<job name> = "<host>:<port>"`. The collector runs in a network namespace of
its own, so `127.0.0.1` is the collector container's loopback: a target
here is a listener inside that container, such as its own telemetry.
A path and query may follow the port —
`"127.0.0.1:9100/stats?format=prometheus"` — for an
exporter that does not serve `/metrics`. Both are optional and
omitted when absent, so a bare `host:port` is scraped exactly as
before.
**A service declares its own entry, from its own module, under its
own `enable`.** Do not assemble the list here: an entry then exists
only where the service that named it runs, so a target is never
declared on a host that does not serve it. A swarm service on the
host is not reachable here, since its loopback is not the collector's:
it is a `publishedScrapeTargets` entry instead, which a collector on
any host scrapes.
⚠️ **That constrains the TARGET, not the SCRAPER, and the difference
is a silent gap.** Nothing here puts the collector on the same host.
Services are co-located by *default* — `swarm-required-services.nix`
derives their `enable` flags from one `lib.mkDefault` — not by
construction, and an operator may split them.
When they are split the target is simply never scraped: the
service's host declares an entry no local collector reads, and the
collector's host never enabled that service so has no entry at all.
No error, no eval failure, no warning.
**No assertion can catch this.** Two hosts are separate NixOS
evaluations with no shared context, so neither can see what the
other runs. A service that would be seriously wrong to lose should
say so in its own contribution, because saying it is the only
mechanism available.
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.
'';
};
telemetryPort = lib.mkOption {
type = lib.types.port;
default = 8889;
@ -498,7 +551,7 @@ in
# alive, these counters say whether it is dropping what it receives.
# Loopback is the container's own: this collector scrapes `telemetryPort`
# inside its netns, and nothing else reads it.
services.hyperhive.swarm.otel.scrapeTargets.collector =
services.hyperhive.deploy.swarm-otel.scrapeTargets.collector =
"127.0.0.1:${toString deployCfg.swarm-otel.telemetryPort}";
# OTLP/HTTP, not a browsable UI, but the same reverse-proxy shape as
@ -935,11 +988,11 @@ in
# Same reason the published one is asserted: a value the grammar
# rejects surfaces as `attempt to call elemAt on null` from inside
# the renderer, naming neither the option nor the offending value.
assertion = lib.all (t: parseScrape t != null) (lib.attrValues cfg.scrapeTargets);
assertion = lib.all (t: parseScrape t != null) (lib.attrValues scrapeTargets);
message = ''
services.hyperhive.swarm.otel.scrapeTargets has ${
services.hyperhive.deploy.swarm-otel.scrapeTargets has ${
lib.concatMapStringsSep ", " (kv: "${kv.name} = \"${kv.value}\"") (
lib.filter (kv: parseScrape kv.value == null) (lib.attrsToList cfg.scrapeTargets)
lib.filter (kv: parseScrape kv.value == null) (lib.attrsToList scrapeTargets)
)
}, which is not of the form <host>:<port>[/<path>][?<k>=<v>].
@ -959,12 +1012,11 @@ in
# The message names which side each collision came from, which is
# the part a rendered-config error could not tell an operator.
assertion =
lib.intersectLists (lib.attrNames cfg.scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets)
== [ ];
lib.intersectLists (lib.attrNames scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets) == [ ];
message = ''
services.hyperhive.swarm.otel: ${
lib.concatMapStringsSep ", " (j: "'${j}'") (
lib.intersectLists (lib.attrNames cfg.scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets)
lib.intersectLists (lib.attrNames scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets)
)
} is declared as both a loopback
scrapeTargets job and a publishedScrapeTargets job.
@ -1149,9 +1201,9 @@ in
# 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 != { } || cfg.publishedScrapeTargets != { }) {
// lib.optionalAttrs (scrapeTargets != { } || cfg.publishedScrapeTargets != { }) {
prometheus.config.scrape_configs =
lib.mapAttrsToList loopbackScrapeConfig cfg.scrapeTargets
lib.mapAttrsToList loopbackScrapeConfig scrapeTargets
# ⚠️ `++`, so the two kinds of target land in ONE list —
# which is exactly why a job name may not appear in both
# options. A list concatenation does not resolve a
@ -1410,7 +1462,7 @@ in
# separated FOR is the credential it proves, and that is
# the receiver's job, not the pipeline's.
++ map (n: "otlp/${n}") serviceProducerNames
++ lib.optional (cfg.scrapeTargets != { } || cfg.publishedScrapeTargets != { }) "prometheus";
++ lib.optional (scrapeTargets != { } || cfg.publishedScrapeTargets != { }) "prometheus";
processors = [ "resource/${swarmTierName}" ];
exporters = exporterNames;
};