Skip to Content
SourcesGrafana Alloy

Grafana Alloy

Grafana Alloy  is Grafana Labs’ OpenTelemetry-native collector distribution. It receives, processes, and forwards logs, metrics, and traces, and is widely deployed as a DaemonSet on Kubernetes or as a host agent on VMs.

Direct Alloy → GlassFlow is supported

Alloy ships the upstream OpenTelemetry Collector’s otelcol.exporter.otlp (gRPC) and otelcol.exporter.otlphttp (HTTP) exporters. Both let you set arbitrary request headers via a headers { … } block, which is everything the GlassFlow OTLP receiver needs to route a request to the right pipeline. Point Alloy at the receiver, route each signal to a per-signal exporter that carries its own x-glassflow-pipeline-id, and your telemetry lands in ClickHouse.

Prerequisites

  • GlassFlow OTLP receiver enabled. See the OTLP Prerequisites.
  • A GlassFlow pipeline per signal with the matching otlp.* source type (otlp.logs, otlp.metrics, or otlp.traces). Note each pipeline ID, Alloy references it in the per-exporter headers block. See the OTLP Examples to create one.
  • Grafana Alloy 1.5+ (validated on the current grafana/alloy:latest).

Forwarding telemetry

Alloy’s exporters are single-signal, so the idiomatic pattern is one exporter per signal, each carrying the right x-glassflow-pipeline-id, wired up behind a single OTLP receiver and a batch processor:

// config.alloy otelcol.receiver.otlp "in" { grpc { endpoint = "0.0.0.0:4317" } http { endpoint = "0.0.0.0:4318" } output { logs = [otelcol.processor.batch.b.input] metrics = [otelcol.processor.batch.b.input] traces = [otelcol.processor.batch.b.input] } } otelcol.processor.batch "b" { timeout = "1s" output { logs = [otelcol.exporter.otlphttp.logs.input] metrics = [otelcol.exporter.otlphttp.metrics.input] traces = [otelcol.exporter.otlphttp.traces.input] } } otelcol.exporter.otlphttp "logs" { client { endpoint = "http://<glassflow-otlp-receiver-host>:4318" compression = "none" tls { insecure = true } headers = { "x-glassflow-pipeline-id" = "<your-logs-pipeline-id>" } } } otelcol.exporter.otlphttp "metrics" { client { endpoint = "http://<glassflow-otlp-receiver-host>:4318" compression = "none" tls { insecure = true } headers = { "x-glassflow-pipeline-id" = "<your-metrics-pipeline-id>" } } } otelcol.exporter.otlphttp "traces" { client { endpoint = "http://<glassflow-otlp-receiver-host>:4318" compression = "none" tls { insecure = true } headers = { "x-glassflow-pipeline-id" = "<your-traces-pipeline-id>" } } }

This same shape works whether the upstream data comes from OTel SDKs, other Alloy instances, sidecar agents, or any other OTLP-compatible client. For host metrics, file tailing, Prometheus scraping, and so on, swap or add the appropriate Alloy components in front of the batch processor. See Alloy components  for the catalog.

Disable gzip compression (compression = "none"). The GlassFlow OTLP receiver does not decompress gzip-encoded payloads and will return 400 Bad Request if you leave the exporter’s default gzip compression enabled.

The output blocks above use tls { insecure = true } and port 4318 for clarity. In production, terminate TLS at the receiver (or an ingress in front of it) and replace the tls block with the appropriate tls { ca_file = … cert_file = … key_file = … } for your environment.

gRPC instead of HTTP

If you’d rather use the gRPC exporter, swap each otelcol.exporter.otlphttp for otelcol.exporter.otlp and point at port 4317. The headers block works identically:

otelcol.exporter.otlp "logs" { client { endpoint = "<glassflow-otlp-receiver-host>:4317" compression = "none" tls { insecure = true } headers = { "x-glassflow-pipeline-id" = "<your-logs-pipeline-id>" } } }

Caveats

  • Misconfigured pipeline IDs fail loudly but quietly. Alloy’s otlphttp exporter treats 4xx responses as permanent and drops the batch with a not retryable error: Permanent error log line. There is no fatal error to alert on. Watch the otelcol_exporter_send_failed_log_records / …_metric_points / …_spans counters; a steadily-climbing value with no successes is the canary.
  • compression = "none" is required (see the warning callout above). Leaving the default gzip will produce a continuous stream of 400 responses on the receiver side.
Last updated on