Alloy vs OpenTelemetry Collector
- Same engine, different chassis
- Feature comparison
- The same pipeline in both
- Decision table
- Name mapping
- Running both
- Related lessons
Alloy is Grafana’s distribution of the OpenTelemetry Collector with its own configuration language and a native Prometheus/Loki/Pyroscope pipeline next to the OTel one.
In the
otelcol.*layer both run the same upstream Go code. Comparing their CPU or memory for OTLP processing measures noise.
Same engine, different chassis
The real differences are:
- Component catalog — which receivers, processors and exporters exist.
- Lifecycle — how config is loaded, reloaded and managed across a fleet.
- Operations tooling — UI, clustering, remote config.
Feature comparison
| Feature | OpenTelemetry Collector | Grafana Alloy |
|---|---|---|
| Project | CNCF, vendor-neutral | Grafana Labs, Apache 2.0 |
| Config | YAML; wiring in service.pipelines |
Alloy syntax; wiring by references |
| Signals | Traces, metrics, logs (profiles in development) | Traces, metrics, logs, profiles (pyroscope.*) |
| Prometheus scraping | prometheus receiver (converts to OTLP) |
Native prometheus.scrape, ServiceMonitor/PodMonitor CRDs (prometheus.operator.*) |
| Component catalog | Full contrib (hundreds of components); ocb builds a minimal binary |
Curated subset of contrib wrapped as otelcol.*, plus Alloy-only components |
| Stability gating | Per component (feature gates) | Per process (--stability.level) |
| Reload | Restart (or OpAMP Supervisor) | /-/reload; local.file, remote.kubernetes.secret, remote.vault re-evaluate live |
| Remote config | OpAMP + Supervisor | remotecfg block, Grafana Fleet Management |
| Clustering | None built in; shard with the Target Allocator (Operator) | Built in: gossip + consistent hashing of scrape targets |
| Debug UI | None (zPages, logs) | Component graph, health, live debugging on :12345 |
| Reusable modules | — | declare, import.git, import.file |
| Config conversion | — | alloy convert --source-format=... (otelcol, prometheus, promtail) — one way, into Alloy |
Components that exist in contrib but not in Alloy v1.16.1 (checked with alloy validate): otelcol.exporter.elasticsearch, otelcol.connector.routing. Both loadbalancing and tail_sampling are available.
The same pipeline in both
OpenTelemetry Collector (YAML):
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
batch:
timeout: 5s
exporters:
otlp:
endpoint: "tempo:4317"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp]
Grafana Alloy:
otelcol.receiver.otlp "default" {
grpc {
endpoint = "0.0.0.0:4317"
}
output {
traces = [otelcol.processor.memory_limiter.default.input]
}
}
otelcol.processor.memory_limiter "default" {
check_interval = "1s"
limit_percentage = 80
spike_limit_percentage = 20
output {
traces = [otelcol.processor.batch.default.input]
}
}
otelcol.processor.batch "default" {
timeout = "5s"
output {
traces = [otelcol.exporter.otlp.tempo.input]
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo:4317"
tls {
insecure = true
}
}
}
Field names drift between the two: limit_mib in YAML is limit (a size string) in Alloy, and spike_limit_mib is spike_limit.
Decision table
| Situation | Pick | Why |
|---|---|---|
You need a component Alloy does not wrap (Elasticsearch exporter, routing connector, a niche receiver) |
Collector | The only hard technical argument |
| “GA only in production” policy, but one pipeline needs a preview component | Collector | Alloy’s --stability.level lowers the bar for the whole process; the Collector gates per component |
| You patch config with kustomize / yq, or template it from another system | Collector | Plain YAML |
| Minimal image, minimal attack surface | Collector (ocb build) |
Only the components you list are compiled in |
| Grafana backends (Mimir, Loki, Tempo, Pyroscope) | Alloy | Native writers, profiles |
| Prometheus scraping at scale, ServiceMonitors already exist | Alloy | Native scrape, CRD support, built-in target sharding |
| Secrets or config must change without a restart | Alloy | local.file / remote.kubernetes.secret re-evaluate live |
| Central config management for a fleet | Alloy (remotecfg) or Collector + OpAMP Supervisor |
Both manage config, not binaries |
| Logs from pods without hostPath | Alloy | loki.source.kubernetes tails through the API |
| eBPF auto-instrumentation, DB observability | Alloy | beyla.ebpf, database_observability.* |
Name mapping
| OTel Collector | Alloy |
|---|---|
health_check extension |
/-/healthy, /-/ready on the HTTP server |
file_storage extension |
otelcol.storage.file (public preview in v1.16) |
bearertokenauth |
otelcol.auth.bearer |
headers_setter |
otelcol.auth.headers |
pprof extension |
/debug/pprof on the HTTP server |
:8888/metrics (internal telemetry) |
:12345/metrics |
debug exporter |
otelcol.exporter.debug (experimental) |
prometheusremotewrite exporter |
otelcol.exporter.prometheus → prometheus.remote_write |
Running both
A two-tier setup (Alloy agents → Collector gateway, or the other way round) is common and fine. Costs to plan for:
- Two embedded OTel versions. OTTL functions and processor defaults can differ between tiers. A
transformthat works on one may not parse on the other. - Two metric naming conventions for self-monitoring. Alloy exposes
otelcol_*_total; older Collectors expose the same counters without_total(lesson 09). - Two upgrade cadences. Alloy ships a minor release roughly every three weeks and bumps its embedded Collector and Prometheus with it.