Compare commits
2 changed files with 10 additions and 114 deletions
|
|
@ -239,45 +239,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
monitorPort = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 8222;
|
|
||||||
description = ''
|
|
||||||
Port NATS serves its **monitoring** endpoint on, bound to
|
|
||||||
loopback.
|
|
||||||
|
|
||||||
Not a metrics endpoint: the server has no Prometheus format of its
|
|
||||||
own. This serves `/varz`, `/connz`, `/routez` as JSON, and the
|
|
||||||
exporter below is what translates it — which is why enabling the
|
|
||||||
exporter without this produces a process that starts cleanly and
|
|
||||||
scrapes nothing.
|
|
||||||
|
|
||||||
⚠️ Loopback, and the exporter is the only intended reader. The
|
|
||||||
endpoint is unauthenticated and `/connz` names every connected
|
|
||||||
client, so the address it binds is the whole access control. Do
|
|
||||||
not widen it, and do not put it behind a gateway vhost expecting
|
|
||||||
that to add one.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
metricsPort = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 7777;
|
|
||||||
description = ''
|
|
||||||
Port the Prometheus exporter serves NATS's metrics on, bound to
|
|
||||||
loopback for the swarm collector to scrape.
|
|
||||||
|
|
||||||
⚠️ This and {option}`monitorPort` are two more claims on a port
|
|
||||||
space every swarm container shares — they run in this container but
|
|
||||||
`privateNetwork = false`, so a collision with any other hyperhive
|
|
||||||
service is a runtime coin toss over which process gets the port,
|
|
||||||
with nothing in any log saying so. Both defaults are upstream's
|
|
||||||
own (`nats-server` 8222, `prometheus-nats-exporter` 7777) and
|
|
||||||
neither is claimed elsewhere in this repo, checked when they were
|
|
||||||
added.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clientId = lib.mkOption {
|
clientId = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "swarm-nats";
|
default = "swarm-nats";
|
||||||
|
|
@ -482,31 +443,6 @@ in
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# Declared here rather than in the collector's module: an entry then
|
|
||||||
# exists only where the service that named it runs. Gated on a
|
|
||||||
# collector, because a target nobody reads asserts a collection that is
|
|
||||||
# not happening.
|
|
||||||
#
|
|
||||||
# ⚠️ KNOWN LIMITATION, and it is SILENT. That rule constrains the
|
|
||||||
# target, not the scraper — nothing places the collector on this host.
|
|
||||||
# `swarm-required-services.nix` derives `nats.enable` and `otel.enable`
|
|
||||||
# from one `lib.mkDefault`, so they are co-located by *default*, and an
|
|
||||||
# operator may split them. Split, NATS is never scraped: this host
|
|
||||||
# declares an entry no local collector reads, the collector's host never
|
|
||||||
# enabled this module. No error, no warning — a healthy exporter and an
|
|
||||||
# empty dashboard.
|
|
||||||
#
|
|
||||||
# Not guardable: separate hosts are separate evaluations with no shared
|
|
||||||
# context, so this one cannot see what that one runs. Saying so is the
|
|
||||||
# only mechanism there is.
|
|
||||||
#
|
|
||||||
# ⚠️ `swarm.otel`, not `hyperhive.otel` — two collectors one word apart,
|
|
||||||
# and only this one reads `scrapeTargets`. Written in full so the gate
|
|
||||||
# and the option it gates are visibly the same path.
|
|
||||||
services.hyperhive.swarm.otel.scrapeTargets = lib.mkIf config.services.hyperhive.swarm.otel.enable {
|
|
||||||
nats = "127.0.0.1:${toString cfg.metricsPort}";
|
|
||||||
};
|
|
||||||
|
|
||||||
containers.swarm-nats = {
|
containers.swarm-nats = {
|
||||||
autoStart = true;
|
autoStart = true;
|
||||||
ephemeral = false;
|
ephemeral = false;
|
||||||
|
|
@ -573,36 +509,10 @@ in
|
||||||
# includes other files. The check moves to server start.
|
# includes other files. The check moves to server start.
|
||||||
validateConfig = !cfg.autoGenerateCallout;
|
validateConfig = !cfg.autoGenerateCallout;
|
||||||
|
|
||||||
settings =
|
settings = calloutBlocks {
|
||||||
calloutBlocks {
|
userKey = cfg.calloutUserPublicKey;
|
||||||
userKey = cfg.calloutUserPublicKey;
|
issuerKey = cfg.calloutIssuerPublicKey;
|
||||||
issuerKey = cfg.calloutIssuerPublicKey;
|
};
|
||||||
}
|
|
||||||
// {
|
|
||||||
# The monitoring endpoint, which is what the exporter below
|
|
||||||
# reads. `//` adds a key `calloutBlocks` does not produce
|
|
||||||
# (`accounts`, `authorization`) — checked, because a shallow
|
|
||||||
# merge that collided here would drop the auth config while
|
|
||||||
# rendering a config the server starts on.
|
|
||||||
#
|
|
||||||
# ⚠️ Loopback: unauthenticated, and `/connz` lists every
|
|
||||||
# connected client.
|
|
||||||
http = "127.0.0.1:${toString cfg.monitorPort}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# The translation layer. NATS has no Prometheus format of its
|
|
||||||
# own, so this reads the JSON monitoring endpoint above and
|
|
||||||
# re-serves it in the format the collector scrapes.
|
|
||||||
#
|
|
||||||
# Upstream's exporter module rather than a hand-rolled unit, for
|
|
||||||
# the same reason `services.nats`'s own `settings` is kept: the
|
|
||||||
# reviewed reasoning about flags and hardening lives there.
|
|
||||||
services.prometheus.exporters.nats = {
|
|
||||||
enable = true;
|
|
||||||
listenAddress = "127.0.0.1";
|
|
||||||
port = cfg.metricsPort;
|
|
||||||
url = "http://127.0.0.1:${toString cfg.monitorPort}";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# A wrapper that includes upstream's rendered settings verbatim
|
# A wrapper that includes upstream's rendered settings verbatim
|
||||||
|
|
|
||||||
|
|
@ -243,26 +243,12 @@ in
|
||||||
`<job name> = "<host>:<port>"`.
|
`<job name> = "<host>:<port>"`.
|
||||||
|
|
||||||
**A service declares its own entry, from its own module, under its
|
**A service declares its own entry, from its own module, under its
|
||||||
own `enable`.** Do not assemble the list here: an entry then exists
|
own `enable`.** That is what puts the scraper and the target on the
|
||||||
only where the service that named it runs, so a target is never
|
same host by construction rather than by luck: an entry exists only
|
||||||
declared on a host that does not serve it.
|
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
|
||||||
⚠️ **That constrains the TARGET, not the SCRAPER, and the difference
|
deployment, not a guarantee — and that is precisely the case where
|
||||||
is a silent gap.** Nothing here puts the collector on the same host.
|
the difference is invisible until a swarm splits across hosts.
|
||||||
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
|
Samples land in a swarm-level pipeline that stamps `swarm` and
|
||||||
**never** `hive`: a swarm service does not belong to a hive, and
|
**never** `hive`: a swarm service does not belong to a hive, and
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue