docs(#3125): the collector pair, and what an operator sets on which host
observability.md described a single collector holding the upstream credential. It also said endpoint and protocol are what agents are handed; agents get the derived first hop, which has been true since the collector was introduced. The swarm tier is documented beside its sibling swarm services rather than here, and the one line an operator must not miss - swarm.otel.url on a hive that does not run them - is called out in both places, since leaving it unset loses telemetry silently.
This commit is contained in:
parent
094a54e785
commit
80c9118f87
3 changed files with 106 additions and 50 deletions
|
|
@ -177,8 +177,11 @@ namespace.
|
|||
### Reaching host services (`exposeHostPorts`)
|
||||
|
||||
By default agents can only reach the host on 80/443 (+53 DNS), so a
|
||||
host-side service on another port — e.g. a dev OTEL collector for
|
||||
`services.hyperhive.otel.endpoint` (see `docs/observability.md`) — is unreachable.
|
||||
host-side service on another port — e.g. a dev OTLP collector you want
|
||||
agents to reach directly — is unreachable. (hyperhive's own telemetry
|
||||
needs none of this: `otel.enable` opens its collector's port itself, and
|
||||
`otel.endpoint` is the *upstream*, which no agent ever dials. See
|
||||
`docs/observability.md`.)
|
||||
|
||||
`services.hyperhive.network.exposeHostPorts = [ 4318 ];` opens each
|
||||
listed TCP port `P` on the bridge-interface `allowedTCPPorts`, so an
|
||||
|
|
|
|||
|
|
@ -16,18 +16,30 @@ services.hyperhive.otel = {
|
|||
};
|
||||
```
|
||||
|
||||
`enable` is the single gate. `endpoint` is where telemetry goes upstream —
|
||||
required when enabled *unless* this host runs the swarm's own metrics store
|
||||
(`swarm.victoriametrics.enable`), which is a destination in its own right. With
|
||||
both, telemetry goes to both. See
|
||||
`enable` is the single gate. `endpoint` is where telemetry ends up after it
|
||||
leaves the swarm — optional, because the swarm's own metrics store
|
||||
(`swarm.victoriametrics.enable`) is a destination in its own right. With both,
|
||||
telemetry goes to both. See
|
||||
[`swarm/services.md`](swarm/services.md#metrics-victoriametrics--grafana).
|
||||
|
||||
**There is exactly one way telemetry leaves a hive: through the collector that
|
||||
`enable` starts on the host.** Agents never talk to `endpoint` themselves —
|
||||
they export unauthenticated to a bridge address only their own containers can
|
||||
reach, and the collector forwards upstream with the auth header. So the
|
||||
upstream credential exists in one place, on the host, and no agent ever holds
|
||||
a copy.
|
||||
reach. That collector forwards to the swarm's
|
||||
([`swarm/services.md`](swarm/services.md#telemetry-collector-otel)), which is
|
||||
the single process holding the upstream credential and the only writer to the
|
||||
swarm's store. No agent holds a copy, and neither does this hive.
|
||||
|
||||
⚠️ **On a hive that does not run the swarm's services, say where that swarm
|
||||
collector is:**
|
||||
|
||||
```nix
|
||||
services.hyperhive.swarm.otel.url = "http://services-host.example:4319";
|
||||
```
|
||||
|
||||
Left unset it points at this host, where nothing is listening — the collector
|
||||
starts, agents export happily, and the samples go nowhere. The service host
|
||||
itself needs no such line.
|
||||
|
||||
⚠️ **The collector is therefore in the path of all telemetry.** It runs on the
|
||||
same host as the agents and restarts on failure, and telemetry is not the
|
||||
|
|
@ -42,8 +54,8 @@ port on the bridge interface only. So "unauthenticated to a bridge address"
|
|||
means *reachable from an agent container*, not *presents a credential*.
|
||||
|
||||
The consequence, stated because it is a choice rather than an oversight: **any
|
||||
agent can push arbitrary OTLP, and the collector forwards it upstream under the
|
||||
operator's credential.** It cannot tell a container's genuine Claude Code stats
|
||||
agent can push arbitrary OTLP, and it is forwarded on under the operator's
|
||||
credential.** Neither tier can tell a container's genuine Claude Code stats
|
||||
from anything else shaped like OTLP arriving on that port — including data
|
||||
smuggled out in resource attributes on an otherwise-legitimate export.
|
||||
|
||||
|
|
@ -61,33 +73,44 @@ closed by this design, and nothing here should be read as closing it.
|
|||
|
||||
Master switch. When true, all other options below take effect.
|
||||
|
||||
### `services.hyperhive.otel.endpoint` — string, required when enabled unless the swarm store runs here
|
||||
### `services.hyperhive.otel.endpoint` — string, default `""`
|
||||
|
||||
Upstream OTLP endpoint URL. Set as `OTEL_EXPORTER_OTLP_ENDPOINT` for every
|
||||
agent. Example: `"https://collector.example.com/otel"`.
|
||||
Upstream OTLP endpoint URL, read by the swarm's collector. Example:
|
||||
`"https://collector.example.com/otel"`.
|
||||
|
||||
Leave it empty **only** on a host running `swarm.victoriametrics.enable` — the
|
||||
local store is then the destination and the collector writes there instead.
|
||||
With neither, `enable` is refused at eval: telemetry with nowhere to go is a
|
||||
misconfiguration, not a quiet no-op.
|
||||
Leave it empty on a swarm running its own metrics store — that store is then
|
||||
the destination. With neither, the swarm collector is refused at eval:
|
||||
telemetry with nowhere to go is a misconfiguration, not a quiet no-op.
|
||||
|
||||
Not what agents are handed. Their endpoint is this hive's own collector,
|
||||
derived from the bridge address, so setting this changes where telemetry
|
||||
*ends up* and never what a container is told.
|
||||
|
||||
### `services.hyperhive.otel.protocol` — enum, default `"http/protobuf"`
|
||||
|
||||
OTLP wire protocol, passed as `OTEL_EXPORTER_OTLP_PROTOCOL`. Accepted values:
|
||||
Wire protocol for the **upstream** link, honoured by the swarm collector's
|
||||
exporter. Accepted values:
|
||||
- `"http/protobuf"` (default)
|
||||
- `"http/json"`
|
||||
- `"grpc"`
|
||||
|
||||
Agents are not affected: their first hop is this hive's collector, whose
|
||||
OTLP/HTTP receiver takes protobuf whatever the upstream wants.
|
||||
|
||||
### `services.hyperhive.otel.headersCredential` — string or null, default `null`
|
||||
|
||||
Absolute path to a secret file on the host holding the upstream auth header as
|
||||
`NAME=value` (e.g. `Authorization=Bearer <token>`).
|
||||
|
||||
**Only the host collector reads it.** It arrives as an `EnvironmentFile` on the
|
||||
collector's unit, so the value is never read by nix, never copied into the
|
||||
store or the generated config, never passed in argv — and **never forwarded
|
||||
into an agent container**. An agent cannot read the hive's upstream credential
|
||||
because it is never given one.
|
||||
**Only the swarm's collector reads it** — the one tier that talks to the
|
||||
upstream. It arrives as an `EnvironmentFile` on that unit, so the value is
|
||||
never read by nix, never copied into the store or the generated config, never
|
||||
passed in argv — and reaches **neither an agent container nor a hive's own
|
||||
collector**. An agent cannot read the upstream credential because it is never
|
||||
given one.
|
||||
|
||||
Set it on the host running the swarm's services; a hive that only forwards has
|
||||
no use for it.
|
||||
|
||||
Leave `null` if the upstream needs no auth header; the collector then sends
|
||||
none rather than an empty one.
|
||||
|
|
@ -130,46 +153,56 @@ metrics on process exit, so interval tuning is not required for metrics to be
|
|||
exported. A lower value gives more frequent intermediate flushes within
|
||||
long-running turns — cosmetic, not a correctness knob.
|
||||
|
||||
## The host collector
|
||||
## The two collectors
|
||||
|
||||
`enable` starts an OpenTelemetry collector on the host. It is not optional and
|
||||
there is no second path — that is the whole point:
|
||||
Telemetry crosses two collectors, and which one you configure depends on what
|
||||
the host is:
|
||||
|
||||
| | runs where | receives from | does |
|
||||
|---|---|---|---|
|
||||
| **hive tier** — `otel.enable` | every hive with agents | that hive's agents, on the bridge | forwards to the swarm tier. Holds no credential, picks no destination |
|
||||
| **swarm tier** — `swarm.otel.enable` | once per swarm | every hive's collector | writes the swarm's store and exports upstream |
|
||||
|
||||
An all-local host runs both, and needs nothing said about the hop between them.
|
||||
|
||||
```nix
|
||||
services.hyperhive.otel = {
|
||||
enable = true;
|
||||
endpoint = "https://collector.example.com/otel"; # the upstream
|
||||
headersCredential = "/run/secrets/otel-headers"; # only the host reads it
|
||||
headersCredential = "/run/secrets/otel-headers"; # only the swarm tier reads it
|
||||
};
|
||||
```
|
||||
|
||||
**Why it isn't a knob.** Exporting straight to `endpoint` means every agent
|
||||
needs the credential to authenticate — and the harness delivers that token into
|
||||
the agent's own `~/.claude/settings.json`, a file the agent can read. `0600`
|
||||
protects it from other containers, not from the agent itself. As long as the
|
||||
direct path stays *selectable*, that hole stays selectable; an option that can
|
||||
reintroduce it is a hole with extra steps.
|
||||
**Why the hive tier isn't optional.** Exporting straight to `endpoint` means
|
||||
every agent needs the credential to authenticate — and the harness delivers
|
||||
that token into the agent's own `~/.claude/settings.json`, a file the agent can
|
||||
read. `0600` protects it from other containers, not from the agent itself. As
|
||||
long as the direct path stays *selectable*, that hole stays selectable; an
|
||||
option that can reintroduce it is a hole with extra steps.
|
||||
|
||||
**`endpoint` keeps meaning "where telemetry goes upstream."** The collector
|
||||
does not redefine it — the agent-facing value is *derived*
|
||||
**Why the tiers stay separate on one box.** They are not collapsed when
|
||||
co-located: an all-local hive is a statement about *where* processes run, not
|
||||
about the shape of the deployment. A boundary that disappears locally is one
|
||||
the local deployment stops testing.
|
||||
|
||||
**`endpoint` keeps meaning "where telemetry goes upstream."** Neither tier
|
||||
redefines it — the agent-facing value is *derived*
|
||||
(`http://<bridgeIp>:<collector.port>`), so an existing deployment's `endpoint`
|
||||
keeps working unchanged. The bridge port is contributed to `exposeHostPorts`
|
||||
automatically; there is nothing to open by hand.
|
||||
|
||||
What the collector *added* is a second destination: on a host running the
|
||||
swarm's metrics store it writes there too, so `endpoint` is no longer the only
|
||||
place telemetry can land — and no longer the only way to have one.
|
||||
|
||||
### `services.hyperhive.otel.collector.port` — port, default `4318`
|
||||
|
||||
The OTLP/HTTP port the collector listens on, bound to the bridge IP only.
|
||||
The OTLP/HTTP port the hive tier listens on, bound to the bridge IP only. The
|
||||
swarm tier has its own (`swarm.otel.port`, default `4319`) — they share a
|
||||
network namespace when co-located, so the two must differ.
|
||||
|
||||
### `services.hyperhive.otel.collector.upstreamHeaderName` — string, default `"Authorization"`
|
||||
|
||||
Name of the header the collector sends upstream. The **value** comes from the
|
||||
Name of the header the swarm tier sends upstream. The **value** comes from the
|
||||
credential file at runtime (`EnvironmentFile` → `${env:<name>}`), never from
|
||||
nix — so header names are config and header values are secrets, which is the
|
||||
only split the collector's static header map can express.
|
||||
only split a static header map can express.
|
||||
|
||||
⚠️ **`endpoint` must be valid for `protocol`.** The upstream exporter follows
|
||||
`otel.protocol` (`grpc` → the gRPC exporter, otherwise OTLP/HTTP), and the gRPC
|
||||
|
|
|
|||
|
|
@ -93,15 +93,35 @@ login form is switched off whenever SSO is configured. If you enable
|
|||
Grafana on a host with no authelia, the form stays on and Grafana's
|
||||
default `admin`/`admin` applies; change it before exposing that host.
|
||||
|
||||
**Where the data comes from.** With `otel.enable` on, the hive's OTEL
|
||||
collector writes into this store as well as to any upstream endpoint —
|
||||
both, not one or the other, since a local store is for looking at this
|
||||
swarm and an upstream is for whoever aggregates across swarms. That also
|
||||
means `otel.endpoint` is no longer required when the store runs here: a
|
||||
hive with a local store already has somewhere for telemetry to go. See
|
||||
[`../observability.md`](../observability.md).
|
||||
**Where the data comes from.** The swarm's OTEL collector, below.
|
||||
|
||||
Neither container is reachable except through the gateway: both bind
|
||||
loopback, and VictoriaMetrics' write endpoint takes no credential, so
|
||||
the collector is the only intended writer.
|
||||
|
||||
### Telemetry collector (OTEL)
|
||||
|
||||
The swarm's collector receives from every hive's own collector and is the
|
||||
only process that decides where telemetry goes: it writes the store above
|
||||
and exports to `otel.endpoint`, doing both when both are configured. It
|
||||
also holds the upstream credential, which is why no hive and no agent
|
||||
needs one.
|
||||
|
||||
It follows `swarm.enableRequiredServices` like the services above, in a
|
||||
`swarm-otel` container. Its `swarm.otel.port` defaults to `4319` rather
|
||||
than OTLP's usual `4318`, which the hive tier already uses — swarm
|
||||
containers share the host's network namespace, so two collectors on one
|
||||
port is a coin toss at runtime rather than an error at build time.
|
||||
|
||||
| Option | When you'd touch it |
|
||||
|---|---|
|
||||
| `swarm.otel.url` | **On every hive that does not run the swarm's services.** It defaults to this host, so a hive left at the default forwards into nothing and loses its telemetry silently. Point it at the services host: `"http://services-host.example:4319"`. |
|
||||
| `swarm.otel.port` | Only if something else on the services host already claims `4319`. |
|
||||
|
||||
With neither `otel.endpoint` nor the store enabled, this collector is
|
||||
refused at eval — a tier that receives samples and drops them looks
|
||||
healthy while losing data.
|
||||
|
||||
Agent-side configuration, and what a hive's own collector does, are in
|
||||
[`../observability.md`](../observability.md).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue