otel-collector — the standard Extract stage
The otel-collector is the front door for every observability signal in the lab. It is an
upstream OpenTelemetry Collector (contrib) — config only, no lab-authored code — that
receives signals, enriches the resource, and lands immutable OTLP-JSON artifacts in S3
(s3://semantix/land/<kind>/…). It never emits RDF: landing artifacts is the seam. This is
seam #1 of the one ingestion contract — see the
platform wiki "Runbook — onboard a signal source".
signals ──▶ otel-collector ──▶ s3://semantix/land/<kind>/… ──▶ OTLP→RDF transform ──▶ loader ──▶ qlever
(this repo: Extract) (immutable OTLP-JSON) (triplify, next)
What it does today
| Receiver | k8sobjects (Kubernetes events, mode: watch) |
| Processors | resourcedetection → resource (stamps simon.cluster=lab, simon.signal=k8s-event) → batch |
| Exporter | awss3 — marshaler otlp_json, bucket semantix, prefix land/k8s-events, partitioned year=/month=/day=/hour= |
| Extensions | health_check (:13133) |
| Self-telemetry | Prometheus :8888 (otelcol_* series) |
Each flushed batch becomes one object:
land/k8s-events/year=YYYY/month=MM/day=DD/hour=HH/logs_<n>.json.
Watch mode emits only NEW events. A quiet cluster lands nothing — that is correct, not a fault. To smoke-test, generate an event (
kubectl -n pipelines run evgen --image=busybox --restart=Never --command -- sh -c 'echo hi') and watch an object appear.
S3 credentials (Hetzner)
Uses the lab S3 creds only — AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY come from the
mover-s3 Secret (Hetzner bucket semantix, region fsn1, endpoint
https://fsn1.your-objectstorage.com, path-style). The awss3exporter was verified against
Hetzner — OTLP-JSON lands with no checksum problem. Do not wire business creds here.
Ops (fleet standard — same as the sibling services)
- Health:
http://otel-collector.pipelines.svc:13133/→ Gatus (pipelinegroup) + readiness probe. - Metrics:
http://otel-collector.pipelines.svc:8888/metrics→ Prometheus jobotel-collector. - Dashboard: Grafana "OTel Collector — signal landing (Extract)" (uid
otel-collector). - Catalog: registered in
service-catalog→ Backstage (type: service,system: observability).
Key metrics: otelcol_receiver_accepted_log_records (in), otelcol_exporter_sent_log_records
(landed to S3), otelcol_receiver_refused_log_records + _failed_log_records (errors),
otelcol_processor_batch_batch_send_size_bucket (upload batch size).
Deploy
kubectl -n pipelines create configmap otel-collector-config \
--from-file=config.yaml=config/config.yaml --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deploy/otel-collector.yaml
The image is the pinned upstream contrib digest (Kyverno require-signed-sbom only gates
forgejo.…/* refs, so upstream admits as-is — same precedent as grafana/prometheus).
Onboarding a new signal (the whole point)
New source = a receiver block + an enrichment rule in config/config.yaml, not a new
service. Add a filelog/syslog/otlp/hostmetrics receiver, stamp simon.signal=<kind>,
point a new pipeline at an awss3 exporter with s3_prefix: land/<kind>. Then the shared
OTLP→RDF transform + loader pick it up. Full procedure: platform wiki "Runbook — onboard a
signal source".
Anchor rule (non-negotiable)
Every landed signal must resolve its resource to a stable CMDB IRI downstream (NetBox device,
k8s object, or Backstage service). Enrich enough here (k8sattributes, resourcedetection,
hostname/IP) that the transform can map it. No anchor, no join.