A declarative contract for automatic discovery and configuration of
telemetry targets in Grafana Alloy
driven by Docker labels. An application declares how it wants to be
collected through ru.3ops.discovery.* labels on its own container --
Alloy picks them up on its own, with no edit to the collector
configuration.
services:
orders-api:
image: example/orders-api:latest
labels:
ru.3ops.discovery.enabled: "true"
ru.3ops.discovery.metrics.enabled: "true"
ru.3ops.discovery.metrics.type: "prometheus"
ru.3ops.discovery.metrics.port: "8000"
ru.3ops.discovery.metrics.job: "orders-api"The namespace is reverse-DNS derived from the 3ops.ru domain. The
prefix is deliberately not x-: Docker Compose treats an
x--prefixed key inside a labels: mapping as an extension and drops it
silently, while a reverse-DNS key survives both the map and the list
form.
The repository is the contract itself, its reference implementation, and the gates that bind the two together and keep them from drifting apart.
| Path | Purpose |
|---|---|
docs/manifest.ru.md |
The normative contract (Russian). The single source of truth: label names, domains, profiles, the secret contract, security requirements, the Alloy version pin. |
docs/manifest.md |
English translation of the manifest. Provided for readers; not normative and not covered by the gates. |
alloy/*.alloy |
The reference Alloy configuration: discovery, metrics, database, logs, log profiles, outputs. |
tests/static/ |
Static gates: they check the normative facts of the manifest against the actual configuration in both directions. |
tests/e2e/ |
End-to-end stack (docker compose): metrics and logs really do reach Prometheus and Loki; alloy/prometheus/loki monitor themselves through the contract (dogfooding). |
tools/materialize.py |
Assembles the configuration directory (base union optional) for alloy_check and e2e. |
LICENSE |
MIT license. |
The manifest is Russian by design; code, comments and commits are English.
- uv -- the only environment manager.
- Python 3.13+.
- Docker -- for the
alloy_checkande2egates. - System binaries for
docs_lint:rumdl,typos,lychee.
Everything runs through uv run nox.
| Command | What it does | Docker / network |
|---|---|---|
uv run nox |
Default gates: lint + docs_lint + alloy_check + tests. |
yes |
uv run nox -s lint tests |
Fully offline: pre-commit plus the static gates. | no |
uv run nox -s lint |
pre-commit over all files. | no |
uv run nox -s docs_lint |
rumdl + typos + lychee. |
network |
uv run nox -s alloy_check |
alloy fmt -t + validate for every directory combination inside the pinned image. |
Docker |
uv run nox -s tests |
Static manifest-vs-config gates plus mini-app units. | no |
uv run nox -s e2e |
Delivery checks against a live compose stack (minutes). | Docker |
uv run nox -s preflight |
Everything at once. | yes |
The Alloy image is pinned by the manifest (section 14);
noxfile.ALLOY_IMAGE must match -- a static test enforces it.
uv run nox -s release builds dist/3ops-discovery-<version>.tar.gz
and SHA256SUMS: the contract, the reference configuration, the overlay
files and a deployment README. The version comes from the manifest
header, the only place it is written down (pyproject.toml says 0.0.0
on purpose). The contents are enumerated by git ls-files, so untracked
scratch never reaches the archive, and the build is reproducible: a
rebuild yields the same sha256. Publishing is a workflow on a vX.Y.Z
tag, which must match the manifest version.
The static gates extract normative facts from docs/manifest.ru.md
(by anchors: section number plus position) and from alloy/*.alloy (with
a regex scanner), then compare them. Restructuring the manifest breaks
the gates loudly. Deliberately allowed divergences are listed in a single
table in tests/static/asymmetries.py. Contract values (scrape
intervals, label names, defaults) are only ever changed in the manifest
-- never "to make a test pass".
- Domains:
metrics,database,logs,blackbox,otel,snmp,ipmi-- under the keysru.3ops.discovery.<domain>.<key>. - Secrets are passed as a logical identifier rather than a value
(
ru.3ops.discovery.database.secret-id); the secrets themselves reach neither labels nor git. - Deployment env parameters use the
RU_3OPS_DISCOVERY_*prefix (for exampleRU_3OPS_DISCOVERY_REMOTE_WRITE_URL), mirroring the label namespace.
The full description lives in docs/manifest.md
(normative original: docs/manifest.ru.md).
- Conventional Commits, no AI attribution in trailers.
- Versions of external artifacts (packages, images, hook revisions) are pinned from live sources, never from memory.
- No secrets in git, not even fake ones:
*.dsnand the like are generated by fixtures into gitignored temporary directories.