Standard application observability#
luma-observe accepts official OpenTelemetry signals and exposes the standard
VictoriaMetrics, VictoriaLogs and Tempo query APIs through an optional vmauth
gateway. Applications own instrumentation, business state, delivery budgets,
dashboards and alert expressions. Luma supplies a shared transport and storage
service; the stack can be deployed independently of Control.
Data flow#
Official SDK ── OTLP ── Collector ── native Metrics ────── VictoriaMetrics
├───── spanmetrics ───────── VictoriaMetrics
└───── tail sampling ─────── Tempo
Nomad stdout/stderr ── existing log shipper ────────────── VictoriaLogs
Backend / operator ── read credential ── vmauth ───────── standard query APIs
External producer ── ingest credential ── vmauth ──────── Collector OTLP HTTP
Application config ── read-only extension volume ──────── Grafana / vmalert
Native metrics bypass trace sampling. Span metrics are derived before the
storage branch samples successful fast traces at 10%; OTel errors, HTTP 5xx and
traces over one second remain eligible for storage. Metrics describe signals
received by the collector, not proof of complete coverage. SDK sampling,
bounded buffers, memory refusal and exporter failures can still leave gaps.
Collector diagnostics are scraped locally at 127.0.0.1:8888/metrics; vmauth
diagnostics use 127.0.0.1:8429/metrics.
Official auto-instrumentation alone cannot determine business completion. Applications can use official custom metrics and spans. Keep metric attributes bounded (service, task type, outcome), and place individual execution IDs on spans or structured logs. Keep secrets and request contents out of telemetry. Use asynchronous bounded exporters and preserve the application's return, exception and retry behavior when telemetry is unavailable.
Luma-managed producers#
Deploy the shared stack, then redeploy an OTel-capable application. Control
injects the existing OTEL_* endpoint, bearer header and resource attributes.
The manager Tailscale listener :4319 accepts both traces and native metrics.
Application requests and task execution do not pass through the collector.
The injected auto-instrumentation default remains OTEL_METRICS_EXPORTER=none
to avoid enabling automatic process metrics for every existing image. An
application that opts into official metric readers can configure
OTEL_METRICS_EXPORTER=otlp, or initialize its own official MeterProvider and
bounded reader. There is no Luma SDK, business payload format, extra event
database or per-application collector.
Optional machine access#
The default observe-access service uses official vmauth 1.114.0. With empty
credentials it starts with zero authorized users. The startup script only
writes vmauth's configuration; upstream vmauth implements authentication,
routing, concurrency limits and timeouts.
The listener is 127.0.0.1:8427. Every backend store and internal diagnostics
listener remains on loopback. A read credential grants cluster-wide telemetry
read access to its fixed routes; it is not a tenant-level authorization layer.
Use it only from trusted backend services or operator tools, never a browser
bundle. Luma management credentials are separate.
Enable each needed role explicitly in the deployment's
observe/docker-compose.yml, replacing that role's empty environment value:
environment:
LUMA_OBSERVE_READ_TOKEN: ${LUMA_OBSERVE_READ_TOKEN}
LUMA_OBSERVE_INGEST_TOKEN: ${LUMA_OBSERVE_INGEST_TOKEN}
Keep the service's other environment settings. A role that stays disabled keeps
its literal "" value. Luma supports ${NAME} secret references; Compose shell
default syntax is not resolved by Luma. Configure only the enabled roles using
luma secret set LUMA_OBSERVE_READ_TOKEN --scope luma-observe and/or
luma secret set LUMA_OBSERVE_INGEST_TOKEN --scope luma-observe. Generate distinct
random bearer tokens of 32–512 characters. The CLI prompts privately. The safe
variable list is in observe/.env.example.
Rebuild and deploy this independently managed stack from observe/ using the
existing build workflow, for example luma build local . --platform linux/amd64.
A CLI/Control update does not update the stack. The original storage paths,
Tempo 2.10.10 memory settings and 72-hour retention remain in use.
| Role | Allowed path | Upstream API |
|---|---|---|
| Read | /metrics/api/v1/query, /metrics/api/v1/query_range |
Prometheus-compatible instant/range queries |
| Read | /logs/select/logsql/query |
LogsQL query |
| Read | /traces/api/search |
Tempo TraceQL search |
| Read | /traces/api/traces/<32-hex-trace-id> |
Tempo trace lookup |
| Ingest | /otlp/v1/metrics, /otlp/v1/traces |
OTLP/HTTP, JSON or protobuf |
Each role is limited to four concurrent requests, with eight total and a three-second upstream response-header timeout. Application clients still need their own overall timeout and response/row limits. Incoming Authorization is removed before the loopback upstream. Store management, reload, debug and write APIs are outside the read route set. vmauth diagnostics use a separate listener.
For a producer outside the mesh, explicitly install
traefik-access.example.yml in the
manager's existing Traefik file-provider directory after substituting a DNS/TLS
host. Resolve that host through the existing ingress. The example publishes
only the authenticated gateway; it creates no route automatically and changes
no existing /grafana session authentication.
An external official SDK uses:
OTEL_EXPORTER_OTLP_ENDPOINT=https://observe-api.example.com/otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20<ingest-token>
The header follows the official OTel SDK environment-variable format; URL-encode
the token when required by that SDK. Existing Luma-managed applications keep the
automatically injected mesh endpoint and token. A backend query client uses the
same HTTPS host with /metrics, /logs, /traces base paths and its separate
read token. Log ingestion remains the existing Nomad stdout/stderr shipper;
this gateway adds no log write endpoint.
For rotation, replace the appropriate scoped secret and redeploy the stack, then update consumers. The gateway accepts one credential per role, so plan a short read outage or telemetry loss during rotation. Application execution must remain independent of telemetry delivery. To disable a role, restore its literal empty environment value and redeploy. Remove the explicit Traefik file when HTTPS machine access is no longer needed.
Application-owned dashboards and rules#
The observe-extensions volume is a shared read-only config mount in Grafana
and vmalert. Its manager path is
/srv/luma/data/luma-observe/extensions; initial creation accepts an empty
directory. Existing store volumes keep their names and paths.
Install reviewed application configuration through the operator's normal file delivery process. Use unique dashboard UIDs, rule group names and recording metric names across applications:
extensions/
dashboards/<application>.json # standard Grafana dashboard JSON
rules/<application>.yml # standard vmalert/Prometheus rule groups
Grafana's Applications folder discovers dashboard JSON recursively. vmalert
loads rules/*.yml and rules/*.yaml alongside the bundled infrastructure
rules. Both re-scan every 30 seconds. Use an atomic rename from a temporary
file with a different extension, with directories readable/traversable and
files readable by Grafana/vmalert. Treat application rule configuration as
trusted operator input; validate expressions and cardinality before installation.
Validate rule files with the pinned victoriametrics/vmalert:v1.114.0 binary
using -rule=<file> -dryRun. Verify the actual dashboard and /api/v1/rules
after installation; a parse failure keeps the prior valid rules active.
Delete an application's file to remove that configuration on the next reload.
This loads definitions, not a new notification receiver: use the existing
Alertmanager routing, grouping, maintenance silences and authorized receivers.
Verification#
From the repository root with Luma dependencies and Docker installed:
python scripts/check-observe-protocols.py --platform linux/amd64
python scripts/check-observe-tempo.py --platform linux/amd64
CI runs both. The protocol check uses official packaged components and generic synthetic signals. It verifies zero-user startup, independent roles, protected mesh metrics, native counters, span metrics before sampling, retained error and slow traces, LogsQL/TraceQL reads, internal diagnostics, empty extension startup, and live dashboard/rule reload. It disables notification delivery and removes its isolated containers, volumes and temporary image tags. The separate Tempo check retains the fresh-storage and persisted-block restart verification.
After deployment, verify these paths using the actual configured roles, then check a representative instrumented application. Observe exporter failures, receiver refusal, source freshness, CPU/RSS, disk growth and storage retention. These protocol checks are not a throughput benchmark or a disk quota: Tempo still shares the manager disk and has no free-space guard. Existing storage and memory protections remain necessary; application-specific budgets and acceptance criteria belong in each application's deployment runbook.