Feature Specification: Components telemetry and otel-collector
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code at 093e68b6f (core/components/hosting-provider/telemetry/default.nix, core/components/hosting-provider/telemetry/oidc.nix, core/components/hosting-provider/telemetry/alert-rules.nix, core/components/hosting-provider/telemetry.nix, core/components/hosting-provider/otel-collector/default.nix, core/components/hosting-provider/otel-collector.nix, core/components/hosting-provider/otel-collector-test.nix, core/components/hosting-provider/otel-wiring-test.nix, core/components/hosting-provider/node-logs.nix, core/components/hosting-provider/node-metrics.nix, core/components/hosting-provider/dashboards/, core/components/hosting-provider/monitoring-test.nix, core/components/hosting-provider/monitoring-test.py, core/setups/hosts-common.nix, core/checks/default.nix, core/checks/ci.nix), pull request #1690.
User Scenarios & Testing (mandatory)
User Story 1 - Collect the signals of the fleet (Priority: P1)
The hosting provider wants the metrics and logs of every node in one place.
The otel-collector component declares provides = [ "otel" ]. Each node
of a group with a node that provides otel runs two shippers:
otel-node-metrics and otel-node-logs. They push over OTLP/HTTP to the
collector of the group. The collector exports each signal to its store on
the telemetry node: traces to Tempo, metrics to Mimir, logs to Loki.
Why this priority: without the collection, the dashboards and the alert rules have no data.
Independent Test: test-otel-collector-service posts a synthetic OTLP
trace and finds the span in the journal through the debug exporter.
test-monitoring-storage posts a trace, a metric and a log, and queries
each back from its store.
Acceptance Scenarios:
- Given a group with a node that provides
otel, When the setups evaluate, Then each node importsnode-metrics.nixandnode-logs.nixwithendpoint = "http://<collector>:4318", and the collector node itself useshttp://127.0.0.1:4318. - Given the node that provides
otel, When the setups evaluate, Then the collector listens on0.0.0.0:4317and0.0.0.0:4318, and the firewall opens both ports. - Given
backends, When the collector exports, Then logs go to<endpoint>/otlp/v1/logs, metrics to<endpoint>/otlp/v1/metricsand traces to<endpoint>/v1/traces, each with theAuthorizationheader read fromauthHeaderFile. - Given a node whose roster
network.nameis an IPv4 literal, When the setups evaluate, Then both shippers takeinstanceIdfrommachine.network.name, not the hostnamenixos.
User Story 2 - Store the signals in Garage (Priority: P1)
The telemetry node runs Loki, Tempo and Mimir on loopback, with their
data in three Garage buckets. nginx on domain gates the ingest with basic
auth and routes each OTLP path to its store.
Why this priority: the stores keep the history that the dashboards and the alert rules query.
Independent Test: test-monitoring-storage checks the auth_basic
gate of the ingest vhost and queries each signal back.
Acceptance Scenarios:
- Given the
telemetry-bucketsrequest, When thegarageresource applies it, Then the bucketsloki,tempoandmimirexist with the tenant prefix, and the keytelemetrycan read, write and own them. - Given a request to
domainwithout the ingest password, When nginx checks it, Then nginx refuses it. - Given new S3 key files, When the
systemd.pathsunit sees the change, Thenfediversity-telemetry-s3-envwrites/run/fediversity-telemetry/s3.envagain and runstry-restarton the stores. - Given the default
retentionDays = 90, When the stores run, Then Loki, Tempo and Mimir drop data older than2160h.retentionDayshas no value that keeps data without a limit.
User Story 3 - Read dashboards and get alerts (Priority: P2)
An administrator opens Grafana on uiDomain and logs in with OIDC. Grafana
shows the provisioned datasources, the dashboards and the alert rules. A
rule that fires sends an email.
Why this priority: the alerts tell the administrator about failed backups, expiring certificates and full disks.
Independent Test: test-monitoring-storage checks the datasources, the
dashboards and the rules. It drives the rule of a failing restic-backups-*
unit to Alerting. No test runs the OIDC login.
Acceptance Scenarios:
- Given a user in
fediversity-admins, When the user logs in, Then Grafana gives the roleAdmin. Other users getViewer. - Given a deployment
emailindefault-configuration, When the setups evaluate, ThenalertEmailsholds each;-delimited address of it. - Given no
emailand no group dataalertEmails, When theotelBackendnode evaluates, Then the evaluation stops with an error that namesADMIN_EMAIL. - Given a firing rule, When Grafana evaluates it, Then the contact point
fediversity-emailsends an email through thetelemetry-smtpaccount.
Edge Cases
- An empty
authHeaderFilefails theExecStartPreguard of the collector. The unit retries withRestartSec=10sand no start limit. - Without
backends, the collector exports only todebug. - An empty SMTP password file fails the guard of Grafana. Grafana retries with
RestartSec=30sand no start limit. - A new admin password restarts Grafana. A new
secretKeydoes not restart it. node-certis on only whensecurity.acme.certsis not empty.- The
windmillOTLP exporters are stubs. Grafana is the only span source. - The backup copies the SQLite database of grafana with
.backup, in one read transaction, thus grafana continues to write during the backup. The restore stopsgrafana.service, replaces the database, removes its-waland-shmfiles, and starts the service again. - A restore needs the
secretKeyoftelemetry-grafanathat encrypted the secrets in the database. The key is in OpenBao KV, not in the backup.
Requirements (mandatory)
Functional Requirements
- FR-001: The
otel-collectorcomponent MUST requestspiffe, declareprovides = [ "otel" ]and importotel-collector.nixthroughcode. - FR-002:
otel-collector.nixMUST declareservices.fediversity-otel-collectorwithlistenAddress(default loopback),grpcPort(4317),httpPort(4318),exporters,backends(endpoint,authHeaderFile,caFile) andscrapeTargets. - FR-003: Each signal MUST go only to its own store: traces to
otlphttp/tempo, metrics tootlphttp/mimir, logs tootlphttp/loki. - FR-004: Each entry of
scrapeTargetsMUST become onescrape_configsjob of aprometheusreceiver in the metrics pipeline. - FR-005: The setups MUST give the collector
authHeaderFilethroughcontracts.credentialsfromvars/secret/telemetry-ingest/authHeader, andcaFilefrom the deployment PKI. - FR-006: The setups MUST add the scrape target
incusatincus.<domain>:443/1.0/metricson an Incus deployment. - FR-007:
node-metrics.nixMUST runotel-node-metricswith thehost_metricsreceiver every30s, thesystemdandnode-certexporters on loopback, and diagnostics on port8889. - FR-008:
node-logs.nixMUST runotel-node-logswith thejournaldreceiver at priorityinfo, key each record on_SYSTEMD_UNIT, mapPRIORITYto a severity, and serve diagnostics on port8890. - FR-009: Both shippers MUST set
service.instance.idfrominstanceIdand run asDynamicUser.otel-node-logsMUST be in the groupsystemd-journal. - FR-010: The
telemetrycomponent MUST setcertAudience = "bootstrap", the default subdomaintelemetryanduiDomainwith defaultmonitoring.<apex>, and requestsslfor both names. - FR-011: The
telemetrycomponent MUST generatetelemetry-ingest(htpasswdandauthHeader),telemetry-grafana(secretKey,adminPassword) andtelemetry-s3(accessKey,secretKey). - FR-012: The
telemetry-oidcrequest MUST use the client IDgrafana, the redirecthttps://<uiDomain>/login/generic_oauthand the scopesopenid,profile,emailandgroups. - FR-013:
telemetry.nixMUST provision the datasourcesmimir,lokiandtempo, the dashboards indashboards/in the folderFediversity, and the rule groupfediversityfromtelemetry/alert-rules.nix. - FR-014: The ingest vhost on
domainMUST usebasicAuthFile, route/otlp/v1/logsto Loki,/v1/tracesto Tempo and/otlp/v1/metricsto Mimir, and setclient_max_body_size 0. - FR-015: The
telemetrynode MUST open only port443. Grafana, Loki, Tempo and Mimir MUST listen on loopback. - FR-016: When the group has a node that provides
otel, the setups MUST settracingEndpointto<collector>:4317. - FR-017: The
telemetrycomponent MUST declareprovides = [ "otelBackend" ]. The setups MUST point the collector exporters at the host of theotelBackendprovider, MUST give theotelprovider the trust of its CA, and MUST givetracingEndpointandalertEmailsto theotelBackendprovider node. - FR-018: A group that has no node that provides
oteland setscentralTelemetry = truein its group data MUST ship its telemetry to theotelprovider of the central group (the one group whose roster providesvarsBackend). Thehypervisorgroup setscentralTelemetry. A group that sets neither ships no telemetry. - FR-019: The
telemetrycomponent MUST requestgaragethroughmkBackupsGarageRequestwith the bucketgrafana-backups, andtelemetry/backup.nixMUST add astreamingBackupof the SQLite database of grafana to that bucket with the restic provider, as the usergrafana. - FR-020:
alertEmailsMUST have no default. When the deploymentemailis set, the setups MUST setalertEmailson theotelBackendprovider node to the;-delimited addresses ofemail. Whenemailis empty, the setups MUST set it from the group dataalertEmails, which thealertEmailsargument ofcore/default.nixgives for an in-tree evaluation. When neither gives a value, the evaluation MUST stop with an error that namesADMIN_EMAIL.
Key Entities
- Ingest credential:
telemetry-ingest. Thehtpasswdgates nginx. TheauthHeaderisBasic base64(otel-collector:<password>). - Store buckets:
loki,tempoandmimirin Garage, one per store. - Alert rules:
maddy-failed-dsn,unit-failed,unit-stuck,backup-failed,backup-stale,renewal-failed,cert-expiring,cert-expiring-critical,disk-filling,disk-filling-trend,disk-full,node-absentandingest-silent. - Dashboards:
fediversity-infra-overview,fediversity-logs-tracesandfediversity-node-detail. - Offsite backup: the restic repository
grafana_sqlitein thegrafana-backupsbucket, with the SQLite database of grafana: the users, their preferences, the service accounts, the silences, the annotations and the dashboards that are not provisioned.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
test-otel-collector-service,test-otel-wiringandtest-monitoring-storagepass. - SC-002:
nix-unit-setups-hypervisor-configpasses, so the hypervisor ships its telemetry under an actionableinstance. - SC-003:
nix-unit-setups-vars-reloadpasses, so a newtelemetry-grafana/adminPasswordrestarts Grafana.
Assumptions
- The
garagenode of the group serves the buckets (seegarage). - Authelia serves the OIDC client (see
authelia). - The app nodes reach the collector over the internal network. The collector receivers have no auth.
Known Deviations
None.