Watch
0
0
Fork
You've already forked hyperhive
0

glue-controller-env: the controller always exports otlp to the swarm's collector

This commit is contained in:
atlas 2026-10-17 14:50:49 +02:00 • committed by mara
commit 2507f4f713
3 changed files with 45 additions and 17 deletions

View file

@ -198,6 +198,10 @@ and failure series on `collector` appear once something fails.
**Where the data comes from.** The swarm's OTEL collector, below. **Where the data comes from.** The swarm's OTEL collector, below.
The `controller` panels come from the controller itself. It exports OTLP
through the gateway to the swarm's collector from whatever host it runs on,
whether or not that host runs the collector.
Neither container is reachable except through the gateway: each listens Neither container is reachable except through the gateway: each listens
on a unix socket only the gateway's nginx can open, and VictoriaMetrics on a unix socket only the gateway's nginx can open, and VictoriaMetrics
runs in a network namespace of its own with no interface but loopback. runs in a network namespace of its own with no interface but loopback.

View file

@ -1,27 +1,31 @@
# Glue: tell the swarm controller about the authelia and the collector that run # Glue: tell the swarm controller where the swarm's collector is and which
# on this host. # authelia runs on this host.
# #
# ONE PAIRING PER FILE — controller ← the services whose coordinates it reads # ONE PAIRING PER FILE — controller ← the services whose coordinates it reads
# from the environment. Deleting this leaves a controller that exports no # from the environment. Deleting this leaves a controller that exports no
# metrics, and a `swarmctl` that fails `user add` saying the authelia binary and # metrics, and a `swarmctl` that fails `user add` saying the authelia binary and
# users file are unset. # users file are unset.
# #
# ⚠️ Two gates, one per service, each ANDed with the controller being here: # ⚠️ Everything here is gated on the controller being here:
# `systemd.services.swarm-controller.environment` on a host without the # `systemd.services.swarm-controller.environment` on a host without the
# controller would define a unit with no ExecStart. # controller would define a unit with no ExecStart.
# #
# authelia → `swarmctl`'s wrapper, not the daemon's unit. Gated on authelia # authelia → `swarmctl`'s wrapper, not the daemon's unit. Also gated on authelia
# running HERE: the controller can be enabled on a host that is not the swarm's # running HERE: the controller can be enabled on a host that is not the swarm's
# SSO host, and there `swarmctl user add` must fail saying the value is unset — # SSO host, and there `swarmctl user add` must fail saying the value is unset —
# a guessed path would resolve cleanly and write a file nothing reads. The # a guessed path would resolve cleanly and write a file nothing reads. The
# CONFIGURED authelia, not whatever is on PATH: the argon2 parameters baked into # CONFIGURED authelia, not whatever is on PATH: the argon2 parameters baked into
# a hash have to match the verifier's. # a hash have to match the verifier's.
# #
# swarm-otel → the daemon's unit. `vcs_metrics`/`hive_jobq_metrics` read the # collector → the daemon's unit. `vcs_metrics`/`hive_jobq_metrics` read the
# standard `OTEL_EXPORTER_OTLP_ENDPOINT` and are silent no-ops without it. The # standard `OTEL_EXPORTER_OTLP_ENDPOINT` and are silent no-ops without it. No
# endpoint is the collector's swarm-service name through the gateway, never a # gate beyond the controller: the controller always exports to the swarm's
# collector, which the swarm always has (./swarm-bao.nix,
# ./glue-swarm-otel-oidc-client.nix), and `deploy.swarm-otel.enable` only says it
# runs HERE. The endpoint is its swarm-service name through the gateway, never a
# loopback literal, with no trailing slash: the http exporter appends # loopback literal, with no trailing slash: the http exporter appends
# `/v1/metrics` and the gateway route strips `/${producerName}`. # `/v1/metrics` and the gateway route strips `/${producerName}`. The port suffix
# is elided on 443, as in ./swarm-bao.nix.
# `SWARM_CONTROLLER_OTEL_AUDIENCE` is the audience ./glue-controller-oidc-client.nix # `SWARM_CONTROLLER_OTEL_AUDIENCE` is the audience ./glue-controller-oidc-client.nix
# registers and ./swarm-otel.nix's `oidc/${producerName}` authenticator checks. # registers and ./swarm-otel.nix's `oidc/${producerName}` authenticator checks.
{ {
@ -34,6 +38,9 @@ let
deployCfg = hyperhiveCfg.deploy; deployCfg = hyperhiveCfg.deploy;
otelSwarmCfg = hyperhiveCfg.swarm.otel; otelSwarmCfg = hyperhiveCfg.swarm.otel;
queueClientId = hyperhiveCfg.swarm.controller.queueClientId; queueClientId = hyperhiveCfg.swarm.controller.queueClientId;
gatewayPortSuffix = lib.optionalString (
hyperhiveCfg.gateway.httpsPort != 443
) ":${toString hyperhiveCfg.gateway.httpsPort}";
in in
{ {
config = lib.mkIf deployCfg.swarm-controller.enable ( config = lib.mkIf deployCfg.swarm-controller.enable (
@ -44,12 +51,12 @@ in
SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile; SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile;
}; };
}) })
(lib.mkIf deployCfg.swarm-otel.enable { {
systemd.services.swarm-controller.environment = { systemd.services.swarm-controller.environment = {
OTEL_EXPORTER_OTLP_ENDPOINT = "https://${otelSwarmCfg.domain}/${otelSwarmCfg.producerName}"; OTEL_EXPORTER_OTLP_ENDPOINT = "https://${otelSwarmCfg.domain}${gatewayPortSuffix}/${otelSwarmCfg.producerName}";
SWARM_CONTROLLER_OTEL_AUDIENCE = queueClientId; SWARM_CONTROLLER_OTEL_AUDIENCE = queueClientId;
}; };
}) }
] ]
); );
} }

View file

@ -58,6 +58,14 @@ let
otel.debug = true; otel.debug = true;
}; };
# The controller behind a gateway on a non-default TLS port: the only
# fixture whose collector URL carries a port. Only the controller's
# environment is read off it.
controllerAltPort = hive {
deploy.swarm-controller.enable = true;
gateway.httpsPort = 8443;
};
# On the store's host, which mints the runner's store identity. # On the store's host, which mints the runner's store identity.
withCi = hive { withCi = hive {
deploy.bao.enable = true; deploy.bao.enable = true;
@ -149,10 +157,11 @@ let
ok = allLocal.services.hyperhive.deploy.swarm-controller.enable; ok = allLocal.services.hyperhive.deploy.swarm-controller.enable;
} }
{ {
# ../host-modules/glue-controller-env.nix: the collector's coordinates # ../host-modules/glue-controller-env.nix: the controller always exports to
# follow swarm-otel running HERE. `controllerAlone` is the split case — a # the swarm's collector. `controllerAlone` runs no collector of its own
# controller whose collector runs elsewhere. # (`allLocal` does, as the presence control); `centralToggleOff` has no
name = "the controller exports to the collector only where the collector runs here"; # controller, so no unit to set it on.
name = "the controller always exports to the swarm's collector";
ok = ok =
let let
env = m: m.systemd.services.swarm-controller.environment; env = m: m.systemd.services.swarm-controller.environment;
@ -169,8 +178,16 @@ let
&& client != null && client != null
&& lib.elem clientId client.audience && lib.elem clientId client.audience
&& client.accessTokenSignedResponseAlg == "RS256" && client.accessTokenSignedResponseAlg == "RS256"
&& !(env shared.controllerAlone ? OTEL_EXPORTER_OTLP_ENDPOINT) && allLocal.services.hyperhive.deploy.swarm-otel.enable
&& !(env shared.controllerAlone ? SWARM_CONTROLLER_OTEL_AUDIENCE); && !shared.controllerAlone.services.hyperhive.deploy.swarm-otel.enable
&&
(env shared.controllerAlone).OTEL_EXPORTER_OTLP_ENDPOINT or null
== "https://${otel.domain}/${otel.producerName}"
&& (env shared.controllerAlone).SWARM_CONTROLLER_OTEL_AUDIENCE or null == clientId
&&
(env controllerAltPort).OTEL_EXPORTER_OTLP_ENDPOINT or null
== "https://${otel.domain}:8443/${otel.producerName}"
&& !(centralToggleOff.systemd.services ? swarm-controller);
} }
{ {
# Same file: `swarmctl` is told where the configured authelia is only # Same file: `swarmctl` is told where the configured authelia is only