Watch
0
0
Fork
You've already forked hyperhive
0

forward the attic container's journal to the swarm collector

This commit is contained in:
atlas 2026-10-27 15:22:55 +01:00
commit a572fb6a7f

View file

@ -29,6 +29,9 @@ let
atticCfg = hyperhiveCfg.swarm.attic;
baoCfg = hyperhiveCfg.swarm.bao;
baoDeploy = hyperhiveCfg.deploy.bao;
autheliaCfg = hyperhiveCfg.swarm.authelia;
networkCfg = hyperhiveCfg.network;
gatewayCfg = hyperhiveCfg.gateway;
machine = "swarm-attic";
unit = "swarm-attic";
@ -87,6 +90,33 @@ let
haveClientIdentity = baoDeploy.atticClientCertFile != null && baoDeploy.atticClientKeyFile != null;
# The container's journal forwarder pushes to its own receiver on the swarm
# collector, the route `serviceProducers.swarm-attic` in
# ./swarm-otel-service.nix declares.
forwarderClientId = atticCfg.otel.clientId;
forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}/${machine}";
forwarderUnit = "swarm-journal-forwarder-oidc";
# In the host netns: distinct from every other collector's there.
forwarderTelemetryPort = 8903;
forwarderCredential = id: "/run/credentials/${forwarderUnit}.service/${id}";
# The cache's store identity, handed into the container as system credentials
# for the forwarder's fetch unit. The host units keep reading the same files.
forwarderStoreFiles = lib.filterAttrs (_: source: source != null) {
attic-bao-cert = baoDeploy.atticClientCertFile;
attic-bao-key = baoDeploy.atticClientKeyFile;
attic-bao-server-ca = baoDeploy.serverCaFile;
};
# nixos-container expands `extraFlags` unquoted, so a source path must be
# one word.
oneWord = path: builtins.match ".*[[:space:]].*" path == null;
# The forwarder reaches the swarm collector and authelia by name over TLS; in
# self-signed mode their leaves chain to the hive CA.
caTrust = import ./lib/hive-ca-trust.nix {
inherit lib gatewayCfg;
tlsCfg = hyperhiveCfg.deploy.hive-controller.tls;
};
# Both host units log in as the cache's principal.
storeEnvironment = {
BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}";
@ -171,6 +201,35 @@ in
is set to null.
'';
}
{
assertion = lib.all oneWord (lib.attrValues forwarderStoreFiles);
message = ''
The cache's store identity paths must not contain whitespace:
nspawn's --load-credential receives them through nixos-container's
unquoted extra flags.
'';
}
{
assertion =
(hyperhiveCfg.swarm.otel.serviceProducers.${machine}.clientId or null) == forwarderClientId;
message = ''
services.hyperhive.swarm.otel.serviceProducers has no entry
"${machine}" with clientId "${forwarderClientId}", so the swarm
collector has no receiver for the cache's journal forwarder.
Declare it in swarm-otel-service.nix.
'';
}
];
# nspawn fails to start the container when a credential source is
# missing, so where this host mints the leaf the container waits for it. The
# CA ordering keeps the trust bundle's bind source there first.
systemd.services."container@${machine}" = lib.mkMerge [
caTrust.containerOrdering
{
after = [ "swarm-bao-pki.service" ];
wants = [ "swarm-bao-pki.service" ];
}
];
services.hyperhive.gateway.localNames = [ atticCfg.domain ];
@ -279,19 +338,71 @@ in
containers.${machine} = {
autoStart = true;
ephemeral = false;
# No `--link-journal=host`: the journal stays in the guest, where the
# container's own forwarder reads it (../container-modules/swarm-journal-forwarder.nix).
extraFlags = lib.mapAttrsToList (
id: source: "--load-credential=${id}:${source}"
) forwarderStoreFiles;
privateNetwork = false;
bindMounts.${secretDir} = {
hostPath = secretDir;
isReadOnly = true;
};
bindMounts = {
${secretDir} = {
hostPath = secretDir;
isReadOnly = true;
};
}
// caTrust.bindMount;
config =
{ ... }:
{
imports = [ ../container-modules/swarm-container.nix ];
imports = [
../container-modules/swarm-container.nix
../container-modules/swarm-journal-forwarder.nix
(import ../container-modules/swarm-container-resolver.nix {
inherit (networkCfg) bridgeIp;
# The forwarder and its secret fetch dial the swarm collector,
# authelia and the store by name.
dnsConsumers = [
"opentelemetry-collector.service"
"${forwarderUnit}.service"
];
})
# Go: `SSL_CERT_FILE` replaces its trust store, for the swarm
# collector's and authelia's names.
(caTrust.trustBundle {
inherit pkgs;
name = machine;
consumers = [ "opentelemetry-collector" ];
})
];
services.hyperhive.swarmContainer.privateNetwork = false;
# Authenticated as the forwarder's own OIDC client, with the cache's
# store identity: one identity for the whole container. It is a
# system credential, so the fetch unit loads it.
services.hyperhive.swarmJournalForwarder = {
enable = true;
serviceName = "attic";
endpoint = forwarderEndpoint;
clientId = forwarderClientId;
tokenUrl = "${toString autheliaCfg.url}/api/oidc/token";
telemetryPort = forwarderTelemetryPort;
store = {
package = baoDeploy.package;
addr = "https://${baoCfg.domain}:${toString baoCfg.port}";
secretPath = "secret/swarm/services/${forwarderClientId}/oidc/client";
clientCertFile = forwarderCredential "attic-bao-cert";
clientKeyFile = forwarderCredential "attic-bao-key";
caFile =
if forwarderStoreFiles ? attic-bao-server-ca then
forwarderCredential "attic-bao-server-ca"
else
null;
};
};
systemd.services.${forwarderUnit}.serviceConfig.LoadCredential = lib.attrNames forwarderStoreFiles;
systemd.services.${unit} = {
description = "attic binary cache server";
wantedBy = [ "multi-user.target" ];