deploy: move grafana's datasources, plugins and socket out of swarm.grafana
`swarm.*` is what a hive needs to be a *client* of the swarm. For Grafana
that is the package, the name it is served under, the port its `/metrics`
is re-served on, and the OIDC client it is registered as. Where its
datasources point, which plugins sit in its store path and the directory
it shares a socket with nginx through are decisions of the machine
running it, so datasourceUrl, logsDatasourceUrl, plugins and socketDir
move to `deploy.grafana.*`.
The two URLs are the interesting half. `swarm-grafana.nix`'s own summary
sentence said what stays is "its package, domain, and wiring" — and both
datasource URLs ARE wiring, so that sentence is rewritten with the move
rather than left asserting the opposite of what the module does. They
move because a URL's scope is the scope of what it ADDRESSES, not the
fact that it is a URL: docs/swarm/services.md already said datasourceUrl
"defaults to the store on this host, which is the only thing it can
reach", because that store binds loopback. The doc argues the move.
`socketDir` was already ruled host-side: the directory is shared between
the host's nginx and the container, and it is the same shape as the
options that moved in earlier slices.
Declared in swarm-grafana.nix under the `deploy.*` path, following
swarm-nats.nix; deploy.nix carries only the renames. One reader outside
the options block is prose: `metricsPort` STAYS and cross-referenced
`{option}services.hyperhive.swarm.grafana.socketDir`, which the split
makes a pointer to another rendered page — nixosOptionsDoc emits
swarm.md and deploy.md separately. Requalified, along with the one line
in docs/swarm/services.md that named a mover.
module-eval configures a hive through all four OLD paths and asserts a
rendered effect: the host tmpfiles rule that creates the socket
directory carries the fixture's custom path. The new paths evaluate fine
without the shims, so dropping them has to read as a clean tree; all
four are defined in the fixture, so removing any single shim fails the
eval rather than only the one the assertion reads.
This commit is contained in:
parent
81b9ddd189
commit
b6df0b4afc
4 changed files with 110 additions and 58 deletions
|
|
@ -116,7 +116,7 @@ See `retentionPeriod` below before leaving it at its default.
|
|||
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `deploy.victoriametrics.retentionPeriod` | Default `5y`. Lower it once you have measured how fast this swarm actually fills a disk — the default is deliberately generous because too-short silently discards history you cannot get back. |
|
||||
| `swarm.grafana.oidc.role` | Default `Admin` for everyone who logs in. Lower to `Viewer`/`Editor` if the swarm grows operators who should not be able to reconfigure Grafana. |
|
||||
| `swarm.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
||||
| `deploy.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
||||
|
||||
**Logging in.** Grafana is behind swarm SSO, so the accounts are the
|
||||
authelia ones — there is no separate Grafana password, and the local
|
||||
|
|
|
|||
|
|
@ -41,6 +41,22 @@ in
|
|||
[ "services" "hyperhive" "swarm" "grafana" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "socketDir" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "socketDir" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "datasourceUrl" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "datasourceUrl" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "logsDatasourceUrl" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "logsDatasourceUrl" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "plugins" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "plugins" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "victoriametrics" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "victoriametrics" "enable" ]
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ let
|
|||
# the OIDC secret above, whose other reader is authelia's container.
|
||||
secretKeyPath = "/var/lib/grafana-secret/secret_key";
|
||||
|
||||
socketPath = "${cfg.socketDir}/grafana.sock";
|
||||
socketPath = "${deployCfg.grafana.socketDir}/grafana.sock";
|
||||
|
||||
# Both static NixOS ids, and both checked rather than assumed: nginx has
|
||||
# `ids.gids.nginx = 60`, Grafana has `ids.uids.grafana = 196` — but there is
|
||||
|
|
@ -111,8 +111,12 @@ in
|
|||
# `enable` moved to `services.hyperhive.deploy.grafana.enable` — see
|
||||
# ./deploy.nix. Whether this host runs the swarm's Grafana is a
|
||||
# deployment decision, and `swarm.*` has to be identical on every host.
|
||||
# What stays here is what the service IS: its package, domain, and
|
||||
# wiring.
|
||||
# What stays here is what the service IS to every hive: its package, the
|
||||
# name it is served under, the port its `/metrics` is re-served on, and
|
||||
# the OIDC client it is registered as. What the host running it decides —
|
||||
# where its datasources point, which plugins are in its store path, and
|
||||
# the socket directory it shares with nginx — is below, under
|
||||
# `deploy.grafana`.
|
||||
options.services.hyperhive.swarm.grafana = {
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
|
|
@ -148,25 +152,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
socketDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/run/swarm-grafana";
|
||||
description = ''
|
||||
Directory holding the unix socket Grafana serves on, shared between
|
||||
the host (where nginx runs) and the container (where Grafana runs).
|
||||
|
||||
⚠️ **Grafana takes no TCP port at all, and that is the point.** Every
|
||||
swarm service container shares the host's network namespace, so a
|
||||
port is a swarm-wide resource that two modules can silently both
|
||||
claim — which is exactly what happened: Grafana defaulted to
|
||||
upstream's 3000, so does the forge, and `grafana.<swarm-domain>`
|
||||
served the forge with no bind error and nothing in any log.
|
||||
|
||||
A socket has a path, and a path collision is a build-time conflict
|
||||
rather than a runtime coin toss.
|
||||
'';
|
||||
};
|
||||
|
||||
metricsPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 9095;
|
||||
|
|
@ -175,7 +160,7 @@ in
|
|||
`/metrics`, and nothing else, so the swarm's collector can scrape it.
|
||||
|
||||
⚠️ **This is nginx's port, not Grafana's.** Grafana still claims none —
|
||||
see {option}`services.hyperhive.swarm.grafana.socketDir` for why that
|
||||
see {option}`services.hyperhive.deploy.grafana.socketDir` for why that
|
||||
matters. A prometheus scrape target is a `host:port`, and it cannot
|
||||
address a unix socket; rather than undo the socket decision, the one
|
||||
endpoint a scraper needs gets a listener of its own.
|
||||
|
|
@ -194,34 +179,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
datasourceUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:${toString vmCfg.port}";
|
||||
defaultText = lib.literalExpression ''"http://127.0.0.1:''${toString services.hyperhive.swarm.victoriametrics.port}"'';
|
||||
description = ''
|
||||
Where the provisioned datasource points. Defaults to the metrics
|
||||
store on this host, which is the only place it can be: that store
|
||||
binds loopback, so a Grafana somewhere else could not reach it
|
||||
anyway. Set explicitly if a deployment fronts VictoriaMetrics with
|
||||
something that does listen wider.
|
||||
'';
|
||||
};
|
||||
|
||||
logsDatasourceUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:${toString vlCfg.port}";
|
||||
defaultText = lib.literalExpression ''"http://127.0.0.1:''${toString services.hyperhive.swarm.victorialogs.port}"'';
|
||||
description = ''
|
||||
Where the provisioned logs datasource points. Same reasoning as
|
||||
{option}`services.hyperhive.swarm.grafana.datasourceUrl`: the store
|
||||
binds loopback, so a Grafana elsewhere could not reach it anyway.
|
||||
|
||||
Provisioned unconditionally, like the metrics datasource — the store
|
||||
being off is a deployment choice rather than a reason to withhold the
|
||||
connection, and an operator whose logs live elsewhere sets this.
|
||||
'';
|
||||
};
|
||||
|
||||
oidc = {
|
||||
clientId = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -257,6 +214,65 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
# What stays above is what the service IS to every hive. What the host
|
||||
# running it decides is here: where its datasources point, which plugins sit
|
||||
# in its store path, and the directory it shares a socket with nginx
|
||||
# through. `enable` already lives in ./deploy.nix, which also carries the
|
||||
# renames.
|
||||
#
|
||||
# ⚠️ Both datasource URLs are wiring and still move. A URL's scope is the
|
||||
# scope of what it ADDRESSES, not the fact that it is a URL: both stores
|
||||
# bind loopback, so these can only ever mean "on this host".
|
||||
options.services.hyperhive.deploy.grafana = {
|
||||
socketDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/run/swarm-grafana";
|
||||
description = ''
|
||||
Directory holding the unix socket Grafana serves on, shared between
|
||||
the host (where nginx runs) and the container (where Grafana runs).
|
||||
|
||||
⚠️ **Grafana takes no TCP port at all, and that is the point.** Every
|
||||
swarm service container shares the host's network namespace, so a
|
||||
port is a swarm-wide resource that two modules can silently both
|
||||
claim — which is exactly what happened: Grafana defaulted to
|
||||
upstream's 3000, so does the forge, and `grafana.<swarm-domain>`
|
||||
served the forge with no bind error and nothing in any log.
|
||||
|
||||
A socket has a path, and a path collision is a build-time conflict
|
||||
rather than a runtime coin toss.
|
||||
'';
|
||||
};
|
||||
|
||||
datasourceUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:${toString vmCfg.port}";
|
||||
defaultText = lib.literalExpression ''"http://127.0.0.1:''${toString services.hyperhive.swarm.victoriametrics.port}"'';
|
||||
description = ''
|
||||
Where the provisioned datasource points. Defaults to the metrics
|
||||
store on this host, which is the only place it can be: that store
|
||||
binds loopback, so a Grafana somewhere else could not reach it
|
||||
anyway. Set explicitly if a deployment fronts VictoriaMetrics with
|
||||
something that does listen wider.
|
||||
'';
|
||||
};
|
||||
|
||||
logsDatasourceUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:${toString vlCfg.port}";
|
||||
defaultText = lib.literalExpression ''"http://127.0.0.1:''${toString services.hyperhive.swarm.victorialogs.port}"'';
|
||||
description = ''
|
||||
Where the provisioned logs datasource points. Same reasoning as
|
||||
{option}`services.hyperhive.deploy.grafana.datasourceUrl`: the store
|
||||
binds loopback, so a Grafana elsewhere could not reach it anyway.
|
||||
|
||||
Provisioned unconditionally, like the metrics datasource — the store
|
||||
being off is a deployment choice rather than a reason to withhold the
|
||||
connection, and an operator whose logs live elsewhere sets this.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
default = [
|
||||
|
|
@ -428,7 +444,7 @@ in
|
|||
# because the host has no `grafana` account — Grafana lives in the
|
||||
# container, and only the number crosses that boundary.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.socketDir} 0750 ${toString grafanaUid} ${toString nginxGid} - -"
|
||||
"d ${deployCfg.grafana.socketDir} 0750 ${toString grafanaUid} ${toString nginxGid} - -"
|
||||
];
|
||||
|
||||
# The secret delivery. It runs on the HOST because that is the only place
|
||||
|
|
@ -513,8 +529,8 @@ in
|
|||
# ownership) and nspawn mounts it in — the same shape the per-agent
|
||||
# `/run/hive-agent/<name>/web.sock` already uses.
|
||||
bindMounts = {
|
||||
${cfg.socketDir} = {
|
||||
hostPath = cfg.socketDir;
|
||||
${deployCfg.grafana.socketDir} = {
|
||||
hostPath = deployCfg.grafana.socketDir;
|
||||
isReadOnly = false;
|
||||
};
|
||||
}
|
||||
|
|
@ -636,7 +652,7 @@ in
|
|||
# hive with no plugins, manual installation is impossible anyway"
|
||||
# — reasoned only about plugins a PERSON installs. Upstream
|
||||
# installs some itself, and those are the ones that went missing.
|
||||
declarativePlugins = cfg.plugins;
|
||||
declarativePlugins = deployCfg.grafana.plugins;
|
||||
|
||||
settings = {
|
||||
server = {
|
||||
|
|
@ -716,7 +732,7 @@ in
|
|||
name = "VictoriaMetrics";
|
||||
type = "prometheus";
|
||||
uid = datasourceUid;
|
||||
url = cfg.datasourceUrl;
|
||||
url = deployCfg.grafana.datasourceUrl;
|
||||
access = "proxy";
|
||||
isDefault = true;
|
||||
}
|
||||
|
|
@ -728,7 +744,7 @@ in
|
|||
# fails at use, far from anything naming this file.
|
||||
type = "victoriametrics-logs-datasource";
|
||||
uid = logsDatasourceUid;
|
||||
url = cfg.logsDatasourceUrl;
|
||||
url = deployCfg.grafana.logsDatasourceUrl;
|
||||
access = "proxy";
|
||||
# Exactly one datasource may claim this, and metrics has it:
|
||||
# two defaults is a coin toss over which one an untyped panel
|
||||
|
|
|
|||
|
|
@ -118,6 +118,14 @@ let
|
|||
swarm.nats.calloutIssuerSeedFile = "/run/secrets/nats-issuer.seed";
|
||||
};
|
||||
|
||||
grafanaOldPath = hive {
|
||||
deploy.grafana.enable = true;
|
||||
swarm.grafana.socketDir = "/run/test-grafana-sock";
|
||||
swarm.grafana.datasourceUrl = "http://127.0.0.1:19999";
|
||||
swarm.grafana.logsDatasourceUrl = "http://127.0.0.1:19998";
|
||||
swarm.grafana.plugins = [ ];
|
||||
};
|
||||
|
||||
baoPkcs11 = hive {
|
||||
deploy.bao.enable = true;
|
||||
deploy.bao.seal = "pkcs11";
|
||||
|
|
@ -293,6 +301,18 @@ let
|
|||
units ? swarm-nats-auth-secrets
|
||||
&& lib.hasInfix "/run/secrets/nats-user.seed" units.swarm-nats-auth-secrets.script;
|
||||
}
|
||||
{
|
||||
# Reads the host's tmpfiles rules, not the options: the socket directory
|
||||
# nginx and the container share is created there, so a rename that
|
||||
# resolved but stopped reaching the module would leave the gateway
|
||||
# proxying to a path nothing creates. All four old paths are defined in
|
||||
# the fixture, so removing any single shim fails the eval rather than
|
||||
# only the one this assertion reads.
|
||||
name = "a config written against the pre-rename grafana paths still creates the socket directory";
|
||||
ok = lib.any (
|
||||
rule: lib.hasInfix "/run/test-grafana-sock" rule
|
||||
) grafanaOldPath.systemd.tmpfiles.rules;
|
||||
}
|
||||
{
|
||||
# The gateway's per-name issuer choice. If this ever collapses to a
|
||||
# constant, every swarm-service vhost serves a certificate its CA
|
||||
|
|
|
|||
Loading…
Reference in a new issue