swarm-otel: a journal it cannot read is a config error, not a silent gap

The collector bind-mounts /var/log/journal into its container and points
its journald receiver at that directory. journald only writes there when
it stores persistently: with storage = "volatile" the journal lives in
/run/log/journal, and with "none" there is none at all. Nothing in the
tree sets or asserts services.journald.storage, so a host with either
value deploys a collector that cannot start — nixos-container refuses to
start when a bind source is missing, which is the same trap the
swarm-bao-certs and hostClientSecretDir comments document.

A bindMounts entry never creates its hostPath, and unlike every other
runtime-written bind source in the tree there is no unit to order the
container after: each of those declares before + requiredBy on its
producing unit, and this directory has no producer. journald was told to
store persistently or it does not exist. That leaves an assertion as the
only place to say so.

"auto" is deliberately accepted: it uses the directory when it exists,
and evaluation cannot see whether it does. Certainty is what separates
an assertion from a warning, and only "volatile" and "none" are certain.

Verified by reading config.assertions for all three values rather than
system.build.toplevel, which raises the first failing assertion in the
whole config and so reports a minimal stub's unrelated ones instead:

  persistent -> mine [] others 3
  volatile   -> mine [the message, quoting "volatile"] others 3
  none       -> mine [the message, quoting "none"]     others 3

The others count is identical across the three, so the storage value is
the only thing that differs between them, and a populated list is what
makes the empty one mean "this assertion passed".
This commit is contained in:
atlas 2026-09-02 06:47:13 +02:00 committed by mara
commit 1629eb4fdb

View file

@ -700,6 +700,39 @@ in
leaving both log destinations unset. leaving both log destinations unset.
''; '';
} }
{
# The assertion above covers *which* units to collect; this one
# covers whether there is a journal on disk to collect them from.
# A `bindMounts` entry never creates its `hostPath`, and unlike
# the CA bind source above there is no unit to order after — the
# directory exists because journald was told to store
# persistently, or not at all. `auto` is deliberately not
# rejected: it uses the directory when it exists, and eval cannot
# see whether it does.
assertion =
!collectLogs
|| !(lib.elem config.services.journald.storage [
"volatile"
"none"
]);
message = ''
The swarm collector is configured to ship journal logs, but
services.journald.storage is
"${config.services.journald.storage}" on this host.
journald only writes ${hostJournalDir} when it stores
persistently: with "volatile" the journal lives in
/run/log/journal, and with "none" there is none at all. This
collector's journald receiver reads ${hostJournalDir} and the
container bind-mounts that path, so the collector would not
start at all nixos-container refuses to start when a bind
source is missing.
Set services.journald.storage = "persistent" (the NixOS
default), or turn off log collection by leaving both log
destinations unset.
'';
}
{ {
# Without a roster there are no receivers at all, so this # Without a roster there are no receivers at all, so this
# collector would listen on nothing while looking configured. # collector would listen on nothing while looking configured.