What Is OBI?
Originally published in Japanese at https://zenn.dev/ymotongpoo/books/go-ebpf-primer/viewer/35-obi.
OpenTelemetry eBPF Instrumentation (OBI) grew from Grafana Beyla and now provides two paths for collecting traces and metrics. Its processing pipeline and repository layout provide the context for the implementation examined in the hurdle chapters.
Origins in Beyla
OBI grew out of Beyla, an eBPF-based auto-instrumentation tool originally developed by Grafana. Its donation to the OpenTelemetry project was announced in May 2025, and the first release, v0.1.0 (alpha), came out on October 30 of that year. It reached beta at KubeCon EU in March 2026, and releases have continued since at a pace of roughly one per month. Beyla hasn’t disappeared either; it lives on as Grafana’s distribution built on top of OBI. The mainline of development is on the OBI side, and Beyla’s maintainers now work in the OBI repository.
This book references v0.11.0 from August 17, 2026; the latest at the time of writing is v0.12.1 from August 20 of the same year. The repository is open-telemetry/opentelemetry-ebpf-instrumentation.
What OBI does
OBI runs beside the application, either on the same machine or in the same container environment. It finds target processes, attaches eBPF programs, extracts HTTP and gRPC traces and metrics, and sends them to an OpenTelemetry backend without changing the application. The application needs no work on its side. This is the substance of the zero-code instrumentation described in Chapter 1.
From startup to export, it goes through the following stages.
- Discovery: It periodically scans
/procto enumerate running processes and their open ports. It does not instrument every process; it selects only those matching the criteria the user provides in configuration (executable path, port number, Kubernetes metadata, and so on). To reduce polling overhead, there is also an eBPF-based helper that detects new port binds. - ELF analysis: It analyzes the target executable and determines its language and kind. For a Go binary, it derives the addresses of the functions it wants to instrument from the symbol table and
.gopclntab, and the positions of struct fields from DWARF and the offset table. The “information left in the executable” we saw in Chapter 4 all gets put to use here. - Load and attach: It loads the compiled eBPF programs into the kernel, submits them to the verifier, and binds hooks such as uprobes to the addresses obtained by the analysis.
- Event collection: Every time a hook fires, the eBPF program assembles an event on the kernel side and writes it to a ring buffer (a hand-off point for events shared between the kernel and user space). OBI’s user-space side reads from it and assembles request-level spans.
- Export: It attaches Kubernetes and container metadata to the spans and sends them to a trace backend over OTLP (OpenTelemetry’s standard protocol). Metrics can also be exposed in Prometheus format in addition to OTLP.
Figure 1: Solid arrows represent the flow of processing; dashed ones show which stage each note on the left refers to. Hurdles 1 and 3 appear mainly in the analysis and attach stages; Hurdles 2 and 4 appear in the event collection stage.
This figure also shows which parts the rest of this book covers. Hurdle 1 (uretprobe doesn’t work) and Hurdle 3 (field offsets) make it difficult to decide where to place hooks and which memory locations to read during stages 2 and 3. Hurdle 2 (the register ABI) and Hurdle 4 (context propagation) concern what and how to read or write when a hook fires during stage 4.
Trying it out
OBI is Linux-only and requires kernel 5.8 or later (with BTF enabled). Because it loads eBPF programs, it needs root privileges or an equivalent set of capabilities.
The following Docker command observes an app listening on port 8080 and prints captured spans.
docker run --rm \
-e OTEL_EBPF_OPEN_PORT=8080 \
-e OTEL_EBPF_TRACE_PRINTER=text \
--pid=host --privileged \
otel/ebpf-instrument:v0.11.0
--pid=host makes the host’s processes visible; --privileged grants privileges. OBI interacts directly with the kernel, so it requires this kind of privilege.
OBI accepts most configuration through environment variables. The common settings are listed below.
| Environment variable | Meaning |
|---|---|
OTEL_EBPF_OPEN_PORT | Target processes that have this port open |
OTEL_EBPF_AUTO_TARGET_EXE | Select targets by executable path (glob) |
OTEL_SERVICE_NAME | Service name attached to spans |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP destination (the standard OpenTelemetry variable, used as is) |
OTEL_EBPF_TRACE_PRINTER | Print spans to standard output for debugging |
OTEL_EBPF_BPF_CONTEXT_PROPAGATION | Enable the context propagation covered in Hurdle 4 (disabled by default) |
On Kubernetes, the typical deployment is a DaemonSet with one instance per node. With hostPID: true, a single OBI observes every process on the node. A receiver for embedding it in the OpenTelemetry Collector is also available.
This book focuses on why OBI works rather than how to operate it. See the official documentation for setup details.
The two instrumentation paths
OBI’s instrumentation falls into two categories, as laid out in SUPPORT_MATRIX.md.
Network-level protocol instrumentation works independently of the application’s language. It places kprobes and socket filters on in-kernel socket processing such as connection establishment, sends, and receives. It then interprets byte streams as protocols including HTTP/1.1, HTTP/2, gRPC, MySQL, PostgreSQL, Redis, and Kafka. This path can observe applications whose traffic passes through a socket. For TLS traffic, uprobes on OpenSSL functions capture plaintext before encryption and after decryption.
Runtime- and library-level instrumentation requires a separate implementation for each target environment. Go is the only language with library-level function instrumentation. OBI defines support version by version, including net/http and database/sql since Go 1.17 and google.golang.org/grpc since 1.40. All four hurdles arise on this path.
Figure 2: The arrows represent the branching of the classification. Protocol instrumentation is language-independent. Instrumentation that goes down to the function level requires a per-runtime, per-library implementation, and only Go has one.
Protocol-level instrumentation alone can capture latency and status codes. The reason OBI still goes down to the function level for Go is that some information never appears on the socket. For example, the lineage of the goroutine handling a request (Hurdle 4) is invisible from the socket, and if you can read values from the struct before serialization, you get exact values without relying on protocol interpretation.
Repository layout
The hurdle chapters draw from the following areas of the OBI repository.
| Location | Role |
|---|---|
bpf/ | C source for the eBPF programs that run on the kernel side |
bpf/gotracer/ | Function instrumentation of Go binaries (the main focus of this book) |
bpf/generictracer/ | Language-independent protocol instrumentation |
bpf/tpinjector/ | Header injection for context propagation (the second path in Hurdle 4) |
pkg/ | User-space Go code |
pkg/internal/goexec/ | ELF analysis of Go binaries and offset resolution (the main focus of Hurdles 1 and 3) |
pkg/ebpf/ | Loading and attaching eBPF programs, reading the ring buffer |
pkg/export/ | Export to OTLP and Prometheus |
cmd/obi/ | Entry point of the main binary |
The kernel-side C code lives in bpf/, while the user-space Go code lives in pkg/. Nearly every file quoted in the hurdle chapters comes from bpf/gotracer/, pkg/internal/goexec/, or pkg/ebpf/.
Key points for the hurdle chapters
- OBI is a standalone program that runs as a pipeline: discovery → ELF analysis → load and attach → event collection → export.
- There are two instrumentation paths: language-independent protocol instrumentation, and function-level instrumentation that only Go has. All four hurdles arise on the latter.
- The kernel-side C code is in
bpf/, and the user-space Go code is inpkg/.