Signal Forge ADR-005: Separate collector configmaps per deployment mode
Status: Accepted
Decision: The Alloy collector configuration is split by mode:
- Cloud:
k8s/monitoring/grafana-helm/values-cloud.yaml.tmpl— Helm values rendered bydeploy-local.shwhenmonitoring.mode: cloud; destinations are Grafana Cloud Tempo/Mimir/Loki. - Local:
k8s/monitoring/grafana/local/configmap.yaml— hand-rolled Alloy configmap applied whenmonitoring.mode: local; destinations are in-cluster Jaeger, Prometheus, and Loki.
./deploy-local.sh selects the correct values file / configmap based on monitoring.mode in
conf.yml.
Rationale:
- A single configmap with conditional blocks or “empty endpoint = no-op” logic obscures intent. Operators reading the deployed configmap should see exactly what is running.
- Cloud and local pipelines have structurally different exporters (
otelcol.exporter.otlp+otelcol.auth.basicvsotelcol.exporter.otlpwithtls.insecure = true+prometheus.remote_write). These are not cosmetic differences. - The split prevents accidental cloud credential exposure in local-only deployments.
Alternative considered: Single configmap with empty-endpoint guards — rejected because it conflates two deployment modes and produces misleading no-op exporter logs.
Addendum (single-sourced trace correlation): The one piece of logic that was genuinely identical
in both configs — the
trace-ID/span-ID → Loki structured-metadata correlation stages
— is now authored once at k8s/monitoring/grafana/shared/trace-correlation-stages.alloy and spliced
into both configmap.yaml.tmpl (raw) and values-cloud.yaml.tmpl (Helm-tpl-escaped) by
deploy-local.sh (render_local_alloy_configmap() / render_helm_values()). This doesn’t change
the rationale above — the two pipelines still have separate, structurally different files; it just
stops one specific snippet from silently drifting when only one side gets edited.
Local graph
Linked from 4 notes
Signal Forge ADR-004: Helm-managed Alloy stack (grafana/k8s-monitoring)
Standardizes on the grafana/k8s-monitoring Helm chart's five-role Alloy topology, keeping the hand-rolled DaemonSet only as a non-deployed reference.
Signal Forge ADR-009: K8s attribute enrichment at collector (not in SDK)
Adds Kubernetes pod/namespace/deployment attributes via Alloy's k8sattributes processor at the collector, keeping application SDKs Kubernetes-agnostic.
Signal Forge ADR-001: Log tailing instead of OTLP log export
Ships logs via node-level Alloy tailing of stdout instead of OTLP SDK log export, to keep log delivery decoupled from application health.
Architecture Overview
Signal Forge's topology, service communication, trace propagation, and per-signal pipeline flow across local and Grafana Cloud deployment modes.
Related notes
Signal Forge ADR-003: Span metrics generated before tail sampling
Places the spanmetrics connector ahead of tail_sampling so RED metrics reflect all traffic instead of only the ~25% of traces that survive sampling.
Signal Forge ADR-002: SpanLink for async RabbitMQ propagation (not parent-child)
Uses a SpanLink, not a parent-child span relationship, to connect RabbitMQ consumer spans back to the producer span across async, retry-prone delivery.
Architecture Overview
Signal Forge's topology, service communication, trace propagation, and per-signal pipeline flow across local and Grafana Cloud deployment modes.
Signal Forge ADR-008: Dead Letter Queue for poison message handling
Routes messages that exceed retry limits or are NACKed without requeue to a RabbitMQ DLQ, preventing infinite retry loops from starving the consumer.