Repository navigation
feat(observability): deploy Tempo eval on R2 for the bake-off - #1764
Conversation
The owner cut the #1280 tracing bake-off to a single candidate, Tempo, on a budget of zero new nodes and zero PVCs. This opens the observability-eval sandbox (teardown 2026-10-24) with Tempo 2.10.8 in single-binary mode from the maintained community chart, storing blocks in a bucket-scoped R2 bucket and keeping the WAL on a 2Gi emptyDir, so the soak measures object storage rather than a disk we would then pay to keep. No workload emits OTLP today, so turning the collector fan-out on forwards only the synthetic fixture spans an operator submits; real producers stay a separate, recorded gate. The ADR and runbook declare the soak window and volume before any measurement and list the WAL and R2-outage losses as accepted for the bake-off only. Alerts go to Telegram only: every remedy is an operator's, and the runbook scales Tempo to zero on purpose. Each rule is silent once the sandbox is gone, so the file cannot page after teardown. Refs #1280 Claude-Session: https://claude.ai/code/session_013zqBqP9sSbVTd1kXRLA6AU
|
Claude finished @mashkovd's task in 4m 8s —— View job Review complete ✅
No P1/P2 findings. Checked against
Could not independently verify the community |
Antigravity (agy) review — informationalModel: gemini-3.8-flash-medium · commit FindingsP1: Duplicate volume and volume mount in Tempo candidate values breaks StatefulSet admission
Concrete failure scenario: Because extraVolumeMounts:
- name: tempo-data
mountPath: /var/tempo
persistence:
enabled: false
extraVolumes:
- name: tempo-data
emptyDir:
sizeLimit: 2Githe rendered StatefulSet ends up with two volumes named The Kubernetes API server strictly rejects duplicate volume names and duplicate volume mounts at admission with validation errors ( Proposed fix: |
There was a problem hiding this comment.
No P1/P2 findings (0 P3). Tempo values (single binary, S3/R2 storage, WAL emptyDir, GOMEMLIMIT), quota/LimitRange sizing, NetworkPolicy Cloudflare egress CIDRs (verified byte-identical to infrastructure/k3s-preview's allowlist), ExternalSecret/Vault wiring, alert routing, and render tests all check out. Good to merge.
|
Re the agy P1 ("duplicate
volumeMounts:
{{- if .Values.tempo.extraVolumeMounts -}}
{{ toYaml .Values.tempo.extraVolumeMounts | nindent 8 }}
{{- end }}
- mountPath: /conf
name: tempo-conf
{{- if .Values.persistence.enabled }}
- mountPath: /var/tempo
name: storage
{{- end }}
...
volumes:
{{- if .Values.extraVolumes -}}
{{ toYaml .Values.extraVolumes | nindent 6 }}
{{- end }}
...
- configMap:
name: {{ template "tempo.name" . }}
name: tempo-conf
volumes:
- emptyDir:
sizeLimit: 2Gi
name: tempo-data
- configMap:
name: tempo
name: tempo-conf
# container volumeMounts: tempo-data -> /var/tempo, tempo-conf -> /conf
# volumeClaimTemplates: noneRemoving I also ran the same rendered config locally under Tempo 2.10.8 with a read-only root filesystem and the WAL directory mounted separately. It became ready, ingested OTLP, returned the traces from search and flushed a block to an S3 emulator. |
Summary
Opens the
observability-evalsandbox for the #1280 tracing bake-off with one candidate, Grafana Tempo (owner decision, 2026-10-10). Teardown date: 2026-10-24.Tempo 2.10.8, single binary, 1 replica, from the community chart
tempo2.4.0(https://grafana-community.github.io/helm-charts; chart to appVersion mapping checked against that repo'sindex.yaml, generated 2026-10-09). The oldgrafana/tempo-distributedpin is gone.Storage: Cloudflare R2 bucket
tempo-tracesthrough a bucket-scoped token. An ExternalSecrettempo-r2reads Vault KVplatform/r2-tempothrough the existingvault-backendClusterSecretStore. That store already readssecret/data/platform/*, so no Vault change is needed. Tempo expands${TEMPO_S3_*}from the Secret (-config.expand-env=true), so the bucket, endpoint and keys never appear in git, and a render test fails if a literal shows up.WAL on an
emptyDirwithsizeLimit: 2Gi, no PVC. The quota now allowspersistentvolumeclaims: "0"andrequests.storage: "0". Retention is336h(block_retention, enforced by the compactor), with the bucket's 30-day lifecycle rule as a backstop.max_block_durationis 5m so blocks reach R2 within minutes.Resources: requests 100m / 512Mi, limits 1 CPU / 1Gi,
GOMEMLIMIT=800MiB. The chart's default 1 GiB memory ballast is turned off, because with it the GC would run continuously underGOMEMLIMIT. The quota is cut from 4 CPU / 12Gi requests to 1 CPU / 1536Mi.Collector fan-out:
debugplusotlp/eval-tempo. Receivers, processors and the other pipelines are unchanged, and a test asserts it.Grafana datasource "Tempo (eval)" (uid
tempo-eval). It is rendered only while the sandbox is open and the tempo candidate is enabled.NetworkPolicies for Tempo:
:3200frommonitoring(Grafana, vmagent), and egress:443to Cloudflare's published IPv4 ranges only. OTLP4317/4318frommonitoringwas already allowed by the namespace template.Scrape: a VMServiceScrape (
job="tempo") on the non-headless Service only. The chart's ServiceMonitor stays off, which avoids the prometheus-pushgateway is scraped twice, so every pushed metric is duplicated and its alerts fire twice #1159 double scrape.Alerts (
vm-rules/tempo-eval-alerts.yaml, promtool unit tests invm-rules/tests/), routed to Telegram only by aTempoEval.*route:TempoEvalNotScrapedTempoEvalMemoryNearLimit(RSS > 85% of the limit)TempoEvalOOMKilledTempoEvalRestartingTempoEvalIngestRefusedTempoEvalDiscardedSpansTempoEvalCollectorExportFailuresTempoEvalCollectorDroppedSpansTempoEvalFlushFailuresTempoEvalObjectStorageErrors(non-2xx/404 from the bucket)TempoEvalCompactionOrRetentionErrorsNo rule uses
absent(). Each one is silent with the sandbox gone, and the tests prove this, including the scaled-to-zero case.Docs:
docs/runbooks/tracing-bake-off.mdis rewritten for the Tempo-only round: capacity, gated rollout, accepted risks and teardown, including emptying the bucket.docs/runbooks/otel-collector.mddescribes the fan-out's current state.yamllint.ymlignores the new datasource template. It starts with a template comment, so the eval-off render emits no document, and yamllint cannot parse a file that starts that way. Same reason as the existing eval templates.Are real producers forwarded? No, there are none yet.
The fan-out is ON as merged, and that is safe because no workload emits OTLP to the collector today. Checked on 2026-10-10:
otelcol_receiver_*series in 30 days.OTEL_*ENDPOINTenv.otel.enabled.So the only spans that reach Tempo are the ones an operator submits through
otel-trace-fixture. Instrumenting a real producer is a separate change, recorded on #1280 as an amendment to the declared volume first.Resources (measured read-only 2026-10-10)
Allocatable per worker is 7700m CPU and about 14.66GiB memory. The control plane is tainted
NoSchedule. Tempo adds +100m / +512Mi requests and +1 CPU / +1Gi limits to whichever worker it lands on.Tempo's expected actual use at the declared synthetic volume is about 200–400MiB RSS and well under 0.1 core. The WAL
emptyDiris capped at 2Gi of node disk, and each worker has about 116–122GiB free.Risks (accepted for the bake-off only, listed in the ADR and the runbook)
emptyDirand the WAL is replayed. A pod move loses up to about one block window (5 min) of unflushed spans.sizeLimit.TempoEvalFlushFailuresandTempoEvalObjectStorageErrorsfire long before that.TempoEvalCollectorExportFailuresmay fire once during the first sync, while the collector rolls before Tempo is Ready.Rollout
Merge. This merge is the sandbox opening. Nothing is applied by hand.
Argo CD sync. Check that
tempo-r2isSecretSynced,tempo-0is Ready and the quotastatus.usedis within limits. The collector rolls with the new exporter.Synthetic span proof: collector → Tempo → Grafana. Run
argo submit -n argo-workflows --from clusterworkflowtemplate/otel-trace-fixture, then check:otelcol_exporter_sent_spans{exporter="otlp/eval-tempo"}goes up;tempo_ingester_blocks_flushed_total> 0 withfailed_flushesflat.Record the timestamp on spike(observability): run the live tracing backend bake-off and decide #1280. It starts the declared soak window.
Real producers later, each as its own reviewed change, recorded on spike(observability): run the live tracing backend bake-off and decide #1280 before it lands.
A permanent backend and prod-wide tracing are separate owner gates after the ADR verdict.
Rollback
git revertthis PR. Reverting templates while Argo CD holds the liveotel-eval-tempoApplication orphans it.docs/runbooks/tracing-bake-off.md§9:otelCollector.eval.enabled: false, the tempo candidateenabled: false,teardownAfter: "". Prune cascades through the Application finalizer and removes the namespace, Secret, policies, scrape, exporter and datasource ConfigMap.tempo-tracesbucket if Tempo is not selected.enabled: false. This removes the Application, the exporter and the datasource, and leaves the namespace.Verification done locally
helm lintpassed for all charts.Full
validate-manifests.ymlvalidatejob, run step by step: 47/48 steps passed, including:eval/tempo/);check-vm-rules.sh(--selftestplus the real run, with the new promtool tests);validate-eval-teardown-date.py(--selftestplus the real run).The one failure is
test_otel_collector_redaction_e2e.py. It needs a working local Docker daemon, which was broken in this environment, and this PR does not touch it. CI runs it.yamllint 1.35.1with the workflow's relaxed config passed.Off-state check:
--set otelCollector.eval.enabled=falseonorigin/mainand on this branch differs only by theTempoEval.*Alertmanager route.infra-componentsis always deployed but is silent without the sandbox, and the tests prove it.tests/test_otel_collector_backends_render.pywas extended:otlp/eval-tempo;${...}placeholders only, ServiceMonitor off);otlpEndpoint, and there is no datasource when the sandbox is off or tempo is disabled;"0".Assertions were mutation-checked: revision, literal endpoint, sizeLimit and GOMEMLIMIT each turn the test red.
Local e2e with the exact rendered Tempo config (Tempo 2.10.8, a local S3 emulator instead of R2, a read-only root filesystem):
expand-envworks;blocks_flushed_total 1, PUT 200);failed_flushes_totalandtempodb_backend_request_duration_seconds_count{status_code="500"}, which are the series the storage alerts use.Not verified (needs the live cluster after merge)
otel-trace-fixture→ collector path.Refs #1280
🤖 Generated with Claude Code
https://claude.ai/code/session_013zqBqP9sSbVTd1kXRLA6AU