ObservabilityL · OpenTelemetry CollectorOTelCollector
Collector Anatomy
What you'll learn
- Explain the OpenTelemetry Collector pipeline model: receivers, processors, exporters, and extensions, and how service.pipelines wires them
- Describe the component lifecycle from factory registration through Start, Running, and Shutdown
- Distinguish agent and gateway deployment topologies and the failure-isolation properties of each
- Identify the collector self-metrics that confirm a pipeline is healthy end to end
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A 200-pod fleet speaks OTLP. Each pod pushes spans, metrics, and log records to a single collector process. The collector is the choke point: every signal the platform sees passes through it, and every loss the platform suffers is decided there. A single misnamed component refuses to start the whole process. A processor placed in the wrong order lets the queue grow until the kernel kills the binary. The cost of a vague mental model is paid at 03:00.
This lesson is the anatomy of the OpenTelemetry Collector: the five component kinds, the pipeline that wires them, the lifecycle each component runs, and the deployment topologies that change the operational shape of the whole system.
What it is
The OpenTelemetry Collector is a single Go binary that accepts telemetry from many sources, transforms it through a pipeline, and ships it to many backends. The configuration is a single YAML document organised into five top-level blocks. The runtime constructs the pipeline as a graph; data flows through the graph as pdata (the in-memory representation shared by all OpenTelemetry SDKs).
+----------+ +-----------+ +----------+
| receivers| --> |processors | --> |exporters |
+----------+ +-----------+ +----------+
\ | /
+----- service.pipelines ----+
+----------+ +-----------+ +----------+
| extension| | receiver | | exporter |
| health_ | | otlp | | loki |
| check | | filelog | | otlp |
| pprof | | journald | | otlphttp |
| file_ | | prometheus| | debug |
| storage | | | | file |
+----------+ +-----------+ +----------+
The five blocks.
- Receivers accept telemetry from the wire or from a local source. The receiver is the inbound edge of the pipeline. It produces pdata; it never consumes from the wire and ships somewhere else.
- Processors transform, batch, filter, or enrich telemetry between a receiver and an exporter. A processor takes pdata in and emits pdata out. It accepts nothing from the wire and writes to no backend.
- Exporters ship telemetry to a backend or to another collector. An exporter consumes pdata and writes it to the outside world.
- Extensions provide capabilities that do not fit the receiver-processor-exporter shape: health endpoints, profiling endpoints, persistent storage for the sending queue, and authentication helpers.
service.pipelinesis the wiring block. Each entry names the signal, the receivers, the processors, and the exporters for one pipeline.
A sixth element, the connector, sits at the boundary between two pipelines. A connector is both an exporter (it ships out of one pipeline) and a receiver (it feeds into the next). Connectors are how a router pipeline and a backend pipeline share data inside a single collector process.
Why a sysadmin cares
The collector is the strategic collector when the fleet is OTel-instrumented. Three properties make it so.
- Vendor neutrality. The collector accepts and emits OTLP, the protocol every OTel-instrumented application already speaks. The configuration is YAML; the schema is open; no single vendor owns it. The cost of a vendor change is the cost of swapping an exporter, not the cost of re-instrumenting the fleet.
- Processor as a first-class concept. A processor is a
named, declared, wired unit.
memory_limiter,attributes,resource,transform,tail_sampling— each is a building block with a documented cost and a documented benefit. The alternative is per-component ad-hoc transformation code in every application. - Two clean deployment topologies. Agent mode runs the collector on every host; gateway mode runs a small number of collectors at the cluster edge. The two compose: agents forward to a gateway; the gateway fans out to the backends.
The cost is verbosity. An OTel Collector configuration for a modest pipeline is roughly twice the line count of the equivalent Alloy configuration. The benefit is that the verbosity lives in a single file per host, in version control, and is owned by one team.
How it works
The factory model
Every component is a Go interface in the collector codebase. The collector binary embeds the factories for the components it ships. The YAML configuration names a factory and supplies its arguments; the runtime builds the component and wires it into the pipeline.
The factory model produces three load-bearing properties.
- A missing factory is a startup failure. If the YAML names
filelogbut the binary does not ship thefilelogfactory, the collector refuses to start withcomponent "filelog" not found in the binary. There is no plugin loader; the binary is what it is. - A wrong argument is a startup failure. If the YAML
passes
start_at: beginnin(typo), the collector refuses to start withunknown value "beginnin" for field start_at. - A pipeline that references an undeclared component is a
startup failure. A receiver named in
service.pipelinesmust exist in thereceiversblock; the same rule applies to processors, exporters, and extensions.
These three rules together mean a misconfigured collector fails fast, loudly, and on the first reload. The cost of debugging is the cost of reading the error.
Component lifecycle
Every component moves through four states.
+------+ +-------+ +---------+ +----------+
| New | --> | Start | --> | Running | --> | Shutdown |
+------+ +-------+ +---------+ +----------+
\ /
\------ Start failure -----/
- New — the factory built the component. The runtime has its configuration; the component has not begun work.
- Start — the component opens its sockets, connects to its backends, and registers with the rest of the pipeline. A Start failure is fatal for the collector.
- Running — the component accepts, transforms, or ships
data. The Running state is observable via the collector
self-metrics on
localhost:8888. - Shutdown — the component drains its queues, closes its
sockets, and releases its resources. Shutdown is invoked by
SIGTERM(clean stop) orSIGHUP(reload).
A failure in Start is a hard failure: the collector exits with non-zero status and systemd (or the container runtime) restarts it. A failure in Running is a soft failure for that component: the receiver or exporter stops accepting data, the pipeline back-pressures, and the operator sees the gap on the dashboard.
Data flow
Each entry in service.pipelines declares a pipeline for one
signal: traces, metrics, or logs. The runtime builds the
pipeline as a chain of Go channels connecting goroutines.
receiver --> processor --> processor --> exporter
| | | |
v v v v
channel_1 --> channel_2 --> channel_3 --> channel_4
|
v
sending_queue
(memory or disk)
|
v
backend
The receivers push pdata into the first channel. Each processor
consumes from its input channel and pushes to its output. The
final processor feeds the exporters in parallel; each exporter
holds its own bounded sending_queue. When the queue fills, the
exporter applies backpressure to its input channel; the
processors slow; the receivers eventually stop accepting data.
The upstream source sees a back-off and reacts (the OTLP SDK
retries with jitter).
Backpressure is the safety valve. Without it, a slow Loki would
fill the collector memory until the kernel OOM-killed the
process. The memory_limiter processor is the gate that
refuses data before the queue fills. Without
memory_limiter, the queue is the only brake.
Extensions
Extensions are components that do not fit the
receiver-processor-exporter shape. They are wired via
service.extensions, separately from pipelines. The most
common ones.
health_check— exposes/statusonlocalhost:13133. The container or systemd unit polls this endpoint to decide whether the collector is ready.pprof— exposes/debug/pprof/for profiling. Bind to localhost; never expose on the cluster network without authentication.zpages— exposes human-readable diagnostics under/debug/. Useful when the GUI dashboard is not available.file_storage— backs thesending_queueof an exporter on disk. Lets a gateway survive a downstream outage without losing buffered data.bearertokenauth/basicauth/oauth2client— client-side authentication helpers used by exporters.
An extension declared in the extensions block but not wired
into service.extensions is loaded but unused. An extension
wired into service.extensions but not declared is a startup
failure.
How to configure it
A minimum viable collector configuration that accepts OTLP, applies a memory limiter and a batch, and exports to Loki. The configuration below is the smallest useful shape; the lessons that follow extend it.
# /etc/otelcol/config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
batch:
timeout: 5s
send_batch_size: 8192
exporters:
loki:
endpoint: https://loki.internal.example.com/loki/api/v1/push
default_labels_enabled: true
headers:
X-Scope-OrgID: prod
Authorization: Basic ${env:LOKI_BASIC_AUTH}
extensions: []
service:
extensions: []
pipelines:
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [loki]
telemetry:
metrics:
address: localhost:8888
logs:
level: info
Three rules to internalise before the next lessons.
memory_limiteris first. The chain order is the discipline. Abatchplaced beforememory_limitergrows unbounded under downstream pressure and the process OOMs.- The collector self-metrics bind to localhost. The default
for the metrics endpoint is
localhost:8888; the default for the health endpoint islocalhost:13133. Bind to the cluster network only with authentication. - Headers carry the tenant. The
lokiexporter routes byX-Scope-OrgID. Without it, the distributor falls back to the default tenant and dashboards return nothing.
How to validate it
Validation is a parse-check against the schema plus a runtime check of the pipeline counters.
# CONFIGURATION: parse-check against the schema.
otelcol validate --config=/etc/otelcol/config.yaml
# (no output on success; non-zero exit on error)
# READ-ONLY: list every component shipped by the binary.
otelcol components
receivers:
- otlp
- filelog
- journald
- prometheus
- hostmetrics
processors:
- batch
- memory_limiter
- attributes
- resource
- transform
- filter
exporters:
- otlp
- otlphttp
- loki
- debug
extensions:
- health_check
- pprof
- file_storage
# READ-ONLY: confirm the receiver is accepting data.
curl -s http://localhost:8888/metrics | grep otelcol_receiver_accepted
otelcol_receiver_accepted_log_records{receiver="otlp"} 1872
# READ-ONLY: confirm the exporter is shipping.
curl -s http://localhost:8888/metrics | grep otelcol_exporter_sent
otelcol_exporter_sent_log_records{exporter="loki"} 1872
If receiver_accepted climbs but exporter_sent does not, the
failure is in the pipeline: a processor is dropping, a memory
limiter is refusing, or the batch has not flushed. If
exporter_sent climbs but lines do not arrive in Loki, the
failure is downstream of the collector.
How it can fail
Six failure modes specific to the collector as a whole.
- The receiver named in a pipeline but not declared. A
service.pipelines.logs.receiversentry namesfilelogbut thereceiversblock has nofilelogentry. Symptom: the collector refuses to start withreceiver "filelog" is not declared. - The component not shipped by the binary. The YAML names
filelog; the binary isotelcol(core), which does not shipfilelog. Symptom: the collector refuses to start withcomponent "filelog" not found in the binary. - The
memory_limiterplaced afterbatch. The chain grows the batch unbounded; the limiter never sees the queue. Symptom:process_runtime_total_alloc_bytesclimbs; the kernel OOM-kills the process; the host loses its metrics agent. - The exporter without the tenant header. The
lokiexporter ships to the default tenant instead of the production tenant. Symptom: lines arrive in Loki but in the wrong tenant; production dashboards return empty. - The SIGHUP that did not reload the pipelines. A collector process that ignores SIGHUP (some container distributions do). Symptom: the on-disk config is new; the running config is old; the agent log shows no reload entry.
- The extension wired but not declared. A
service.extensionsentry namesfile_storagebut theextensionsblock has nofile_storageentry. Symptom: the collector refuses to start withextension "file_storage" is not declared.
How to troubleshoot it
When the collector is not behaving, the order of the diagnostic matters.
- Read the error first. Both parse errors and factory errors are printed with a line number and the offending argument. The first error is usually the only one.
- Confirm the binary matches the YAML.
otelcol componentslists every component shipped by the binary. Cross-reference against the YAML. A missing component is a startup failure. - Validate against the schema.
otelcol validateruns the same parse the runtime runs at start. Run it on the file before sending SIGHUP. - Tail the agent log on first reload. The first batch after a reload fails loudly if anything is misconfigured. Watch for factory errors, parse errors, and rejected entries.
- Compare pipeline counters.
receiver_acceptedminusreceiver_refusedshould approximately equalexporter_sentplusexporter_failed. A persistent gap means the chain is dropping. - Confirm the export path is healthy. A configuration that parses cleanly but ships to a dead endpoint looks identical to a working one until the metrics are inspected.
Security implications
The collector exposes the same surfaces as any telemetry component. The defaults are local-only; production deployments often relax them in ways that need an audit.
- The debug and metrics endpoints. Defaults are
localhost:8888for/metrics,localhost:8889for/debug, andlocalhost:13133for the health check. All bind to localhost. Expose them on the cluster network only with authentication. - The OTLP receiver. The
otlpreceiver accepts data on0.0.0.0:4317(gRPC) and0.0.0.0:4318(HTTP) by default. In agent mode, bind tolocalhostor to a private interface. In gateway mode, restrict the listener with a NetworkPolicy. - TLS to the exporters. The
loki,otlp, andotlphttpexporters accepttlsblocks for CA bundles, client certificates, andinsecure_skip_verify. A stale CA bundle is the most common cause of silent shipping failure. - Filesystem access. The
filelogandjournaldreceivers read whatever the collector process can read. Run the process as a dedicated user with read access to the intended paths and no more.
Performance implications
The on-host cost is roughly 100-150 MiB RAM and 50-100 millicores CPU at modest line rates. The cost grows with batch size, queue depth, and per-record transform work.
- Batching. The
batchprocessor coalesces entries to reduce per-call cost. A largersend_batch_sizewith a longertimeouttrades latency for throughput. The defaulttimeout: 200msis too aggressive for a gateway; five seconds is a more realistic starting point. - Memory limiter. The
memory_limiterprocessor refuses data when the process approaches a memory limit. Thelimit_percentageandspike_limit_percentageare the trade-off knobs. The limit must be the first processor in the chain. - Sending queue. The
sending_queueon exporters is in-memory by default. Thefile_storageextension backs it on disk for durability across restarts; the disk cost is real, and the queue_size should be planned against the worst-case outage.
Production guidance
- Use
otelcol-contribunless the deployment only needscore. The component set incontribmatches the production needs of almost every fleet. The binary is larger; the cost of a missing component at 03:00 is more. - Place
memory_limiterfirst in every pipeline. After the limiter, placebatch, then the rest. The chain order is the discipline. - Pin the collector version. The collector releases monthly. Read the release notes. Breaking changes to component schemas do happen; the migration is usually a one-line edit but the breaking change is the signal to upgrade.
- Smoke test after every config change. Ship a known line with a unique UUID and confirm it arrives in the right backend within ten seconds.
Verification
You should now be able to answer:
- What are the five blocks in
otelcol.yaml, and which block wires the others into a pipeline? - What is the difference between a Start failure and a Running failure, and which one is recoverable?
- Why is the
memory_limiterprocessor placed first in the pipeline, and what happens if it is not? - What do
otelcol_receiver_accepted_*andotelcol_exporter_sent_*tell you about the pipeline? - What is the difference between agent mode and gateway mode, and when does each apply?
Quiz
Knowledge check · 8 questions
Q1. Which block in otelcol.yaml wires receivers, processors, and exporters into a pipeline?
Q2. A service.pipelines.logs.receivers entry names filelog but the receivers block has no filelog entry. The collector will:
Q3. A Start failure in a collector component is recoverable while the collector is running.
Q4. The memory_limiter processor must be placed:
Q5. Name the metric that confirms the loki exporter is shipping log records.
Q6. Which of these are top-level blocks in otelcol.yaml?
Q7. In gateway mode, the collector runs:
Q8. otelcol components is useful because it lists:
Passing score: 75%. Answers are checked in this browser.