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. |
|
| `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.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
|
**Logging in.** Grafana is behind swarm SSO, so the accounts are the
|
||||||
authelia ones — there is no separate Grafana password, and the local
|
authelia ones — there is no separate Grafana password, and the local
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,22 @@ in
|
||||||
[ "services" "hyperhive" "swarm" "grafana" "enable" ]
|
[ "services" "hyperhive" "swarm" "grafana" "enable" ]
|
||||||
[ "services" "hyperhive" "deploy" "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
|
(lib.mkRenamedOptionModule
|
||||||
[ "services" "hyperhive" "swarm" "victoriametrics" "enable" ]
|
[ "services" "hyperhive" "swarm" "victoriametrics" "enable" ]
|
||||||
[ "services" "hyperhive" "deploy" "victoriametrics" "enable" ]
|
[ "services" "hyperhive" "deploy" "victoriametrics" "enable" ]
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ let
|
||||||
# the OIDC secret above, whose other reader is authelia's container.
|
# the OIDC secret above, whose other reader is authelia's container.
|
||||||
secretKeyPath = "/var/lib/grafana-secret/secret_key";
|
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
|
# 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
|
# `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
|
# `enable` moved to `services.hyperhive.deploy.grafana.enable` — see
|
||||||
# ./deploy.nix. Whether this host runs the swarm's Grafana is a
|
# ./deploy.nix. Whether this host runs the swarm's Grafana is a
|
||||||
# deployment decision, and `swarm.*` has to be identical on every host.
|
# deployment decision, and `swarm.*` has to be identical on every host.
|
||||||
# What stays here is what the service IS: its package, domain, and
|
# What stays here is what the service IS to every hive: its package, the
|
||||||
# wiring.
|
# 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 = {
|
options.services.hyperhive.swarm.grafana = {
|
||||||
package = lib.mkOption {
|
package = lib.mkOption {
|
||||||
type = lib.types.package;
|
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 {
|
metricsPort = lib.mkOption {
|
||||||
type = lib.types.port;
|
type = lib.types.port;
|
||||||
default = 9095;
|
default = 9095;
|
||||||
|
|
@ -175,7 +160,7 @@ in
|
||||||
`/metrics`, and nothing else, so the swarm's collector can scrape it.
|
`/metrics`, and nothing else, so the swarm's collector can scrape it.
|
||||||
|
|
||||||
⚠️ **This is nginx's port, not Grafana's.** Grafana still claims none —
|
⚠️ **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
|
matters. A prometheus scrape target is a `host:port`, and it cannot
|
||||||
address a unix socket; rather than undo the socket decision, the one
|
address a unix socket; rather than undo the socket decision, the one
|
||||||
endpoint a scraper needs gets a listener of its own.
|
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 = {
|
oidc = {
|
||||||
clientId = lib.mkOption {
|
clientId = lib.mkOption {
|
||||||
type = lib.types.str;
|
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 {
|
plugins = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.package;
|
type = lib.types.listOf lib.types.package;
|
||||||
default = [
|
default = [
|
||||||
|
|
@ -428,7 +444,7 @@ in
|
||||||
# because the host has no `grafana` account — Grafana lives in the
|
# because the host has no `grafana` account — Grafana lives in the
|
||||||
# container, and only the number crosses that boundary.
|
# container, and only the number crosses that boundary.
|
||||||
systemd.tmpfiles.rules = [
|
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
|
# 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
|
# ownership) and nspawn mounts it in — the same shape the per-agent
|
||||||
# `/run/hive-agent/<name>/web.sock` already uses.
|
# `/run/hive-agent/<name>/web.sock` already uses.
|
||||||
bindMounts = {
|
bindMounts = {
|
||||||
${cfg.socketDir} = {
|
${deployCfg.grafana.socketDir} = {
|
||||||
hostPath = cfg.socketDir;
|
hostPath = deployCfg.grafana.socketDir;
|
||||||
isReadOnly = false;
|
isReadOnly = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -636,7 +652,7 @@ in
|
||||||
# hive with no plugins, manual installation is impossible anyway"
|
# hive with no plugins, manual installation is impossible anyway"
|
||||||
# — reasoned only about plugins a PERSON installs. Upstream
|
# — reasoned only about plugins a PERSON installs. Upstream
|
||||||
# installs some itself, and those are the ones that went missing.
|
# installs some itself, and those are the ones that went missing.
|
||||||
declarativePlugins = cfg.plugins;
|
declarativePlugins = deployCfg.grafana.plugins;
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
server = {
|
server = {
|
||||||
|
|
@ -716,7 +732,7 @@ in
|
||||||
name = "VictoriaMetrics";
|
name = "VictoriaMetrics";
|
||||||
type = "prometheus";
|
type = "prometheus";
|
||||||
uid = datasourceUid;
|
uid = datasourceUid;
|
||||||
url = cfg.datasourceUrl;
|
url = deployCfg.grafana.datasourceUrl;
|
||||||
access = "proxy";
|
access = "proxy";
|
||||||
isDefault = true;
|
isDefault = true;
|
||||||
}
|
}
|
||||||
|
|
@ -728,7 +744,7 @@ in
|
||||||
# fails at use, far from anything naming this file.
|
# fails at use, far from anything naming this file.
|
||||||
type = "victoriametrics-logs-datasource";
|
type = "victoriametrics-logs-datasource";
|
||||||
uid = logsDatasourceUid;
|
uid = logsDatasourceUid;
|
||||||
url = cfg.logsDatasourceUrl;
|
url = deployCfg.grafana.logsDatasourceUrl;
|
||||||
access = "proxy";
|
access = "proxy";
|
||||||
# Exactly one datasource may claim this, and metrics has it:
|
# Exactly one datasource may claim this, and metrics has it:
|
||||||
# two defaults is a coin toss over which one an untyped panel
|
# 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";
|
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 {
|
baoPkcs11 = hive {
|
||||||
deploy.bao.enable = true;
|
deploy.bao.enable = true;
|
||||||
deploy.bao.seal = "pkcs11";
|
deploy.bao.seal = "pkcs11";
|
||||||
|
|
@ -293,6 +301,18 @@ let
|
||||||
units ? swarm-nats-auth-secrets
|
units ? swarm-nats-auth-secrets
|
||||||
&& lib.hasInfix "/run/secrets/nats-user.seed" units.swarm-nats-auth-secrets.script;
|
&& 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
|
# The gateway's per-name issuer choice. If this ever collapses to a
|
||||||
# constant, every swarm-service vhost serves a certificate its CA
|
# constant, every swarm-service vhost serves a certificate its CA
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue