Skip to content

Commit 6acae3d

Browse files
docs: add ADR 0001 and storage scale-out stub
ADR records the single-host OTLP-native architecture decision and its alternatives; compose.storage.s3.yml is a deliberately commented stub documenting the concrete Loki/Tempo → S3 path the README mentions.
1 parent 883b9ab commit 6acae3d

4 files changed

Lines changed: 110 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ First tagged release: the stack is runnable, demoable, and validated in CI.
2121
error rate.
2222
- README: architecture diagram, demo quickstart, screenshot, design
2323
decisions.
24+
- ADR 0001 recording the single-host OTLP-native architecture, and a
25+
commented `compose.storage.s3.yml` stub documenting the storage
26+
scale-out path.
2427

2528
### Changed
2629

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ someone.
124124

125125
Everything persists to local Docker volumes (`loki_data`, `tempo_data`,
126126
`prometheus_data`, `grafana_data`). Swap Loki / Tempo storage to S3-compatible
127-
(Backblaze B2, Cloudflare R2, Hetzner, MinIO) when you outgrow local disk.
127+
(Backblaze B2, Cloudflare R2, Hetzner, MinIO) when you outgrow local disk —
128+
`compose.storage.s3.yml` documents the concrete shape of that change, and
129+
`docs/adr/0001-observability-stack.md` records the architecture rationale.
128130

129131
## Design decisions
130132

compose.storage.s3.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# STUB — the storage scale-out path (not wired up; see ADR 0001).
2+
#
3+
# When local volumes stop fitting, Loki and Tempo move their object storage
4+
# to any S3-compatible backend (Cloudflare R2, Backblaze B2, Hetzner, MinIO)
5+
# without touching the collector, Prometheus, or any client project.
6+
#
7+
# This is deliberately a commented stub: it documents the concrete shape of
8+
# the change so the README's scale-out claim is real, but it is not meant to
9+
# be started until credentials and a bucket exist. To activate:
10+
#
11+
# 1. Create s3 variants of the configs, e.g. config/loki.s3.yaml replacing
12+
# `common.storage.filesystem` with:
13+
#
14+
# common:
15+
# storage:
16+
# s3:
17+
# endpoint: ${S3_ENDPOINT} # e.g. <account>.r2.cloudflarestorage.com
18+
# bucketnames: cml-loki
19+
# access_key_id: ${S3_ACCESS_KEY_ID}
20+
# secret_access_key: ${S3_SECRET_ACCESS_KEY}
21+
# s3forcepathstyle: true
22+
#
23+
# and config/tempo.s3.yaml replacing `storage.trace.backend: local` with:
24+
#
25+
# storage:
26+
# trace:
27+
# backend: s3
28+
# s3:
29+
# endpoint: ${S3_ENDPOINT}
30+
# bucket: cml-tempo
31+
# access_key: ${S3_ACCESS_KEY_ID}
32+
# secret_key: ${S3_SECRET_ACCESS_KEY}
33+
#
34+
# 2. Uncomment the overlay below and run:
35+
# docker compose -f compose.yml -f compose.storage.s3.yml up -d
36+
#
37+
# services:
38+
# loki:
39+
# volumes:
40+
# - ./config/loki.s3.yaml:/etc/loki/loki.yaml:ro
41+
# - loki_data:/loki
42+
# environment:
43+
# S3_ENDPOINT: ${S3_ENDPOINT:?set S3_ENDPOINT in .env}
44+
# S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:?set S3_ACCESS_KEY_ID in .env}
45+
# S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:?set S3_SECRET_ACCESS_KEY in .env}
46+
#
47+
# tempo:
48+
# volumes:
49+
# - ./config/tempo.s3.yaml:/etc/tempo/tempo.yaml:ro
50+
# - tempo_data:/var/tempo
51+
# environment:
52+
# S3_ENDPOINT: ${S3_ENDPOINT:?set S3_ENDPOINT in .env}
53+
# S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:?set S3_ACCESS_KEY_ID in .env}
54+
# S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:?set S3_SECRET_ACCESS_KEY in .env}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# ADR 0001: Single-host OTLP-native observability stack
2+
3+
Date: 2026-07-03. Status: accepted (records a decision already in production).
4+
5+
## Context
6+
7+
CML runs several research platforms (RELab and others) that need logs,
8+
traces, and metrics in one place. Telemetry volume is modest — a handful of
9+
services, single-digit requests per second — but the projects are
10+
long-lived, maintained by a very small team, and must stay cheap and
11+
auditable.
12+
13+
## Decision
14+
15+
Run one Docker Compose stack on a single host: an OpenTelemetry Collector as
16+
the only ingestion endpoint, fanning out to Loki (logs), Tempo (traces), and
17+
Prometheus (metrics), with Grafana on top. Expose Grafana and the OTLP
18+
endpoints via Cloudflare Tunnel; bind everything else to `127.0.0.1`.
19+
20+
- **OTLP-native:** projects configure one endpoint and one protocol.
21+
Backends can be swapped (or moved to hosted equivalents) without touching
22+
any application.
23+
- **Single-host:** one compose file is something one person can fully audit
24+
and rebuild. Distributed ingest (Kubernetes, Mimir, multi-tenant Loki)
25+
buys nothing at this volume and costs ongoing operational attention.
26+
- **Traces as the primary signal:** Tempo's metrics generator derives RED
27+
metrics and a service graph from spans, so a service that only sends
28+
traces still gets dashboards and error-rate alerting.
29+
30+
## Alternatives considered
31+
32+
- **Grafana Cloud / hosted SaaS:** less to operate, but recurring cost,
33+
data residency questions for research data, and less useful as
34+
institutional infrastructure knowledge.
35+
- **Per-project stacks:** no shared endpoint to maintain, but duplicates
36+
storage and Grafana per project and makes cross-project correlation
37+
impossible.
38+
- **ELK / OpenSearch:** heavier to run, log-centric; weaker native OTLP and
39+
trace correlation story than the Grafana stack.
40+
41+
## Consequences
42+
43+
- The host is a single point of failure; acceptable because the monitored
44+
platforms degrade gracefully without telemetry (OTLP export is
45+
fire-and-forget) and the stack rebuilds from this repo in minutes.
46+
- Local disk bounds retention (30d logs/metrics, 7d traces). The documented
47+
escape hatch is S3-compatible storage for Loki/Tempo — see
48+
`compose.storage.s3.yml` — before any move to distributed ingest.
49+
- Everything is pinned and validated by `just check` in CI, so the stack
50+
stays reproducible.

0 commit comments

Comments
 (0)