OBI is the OpenTelemetry project donated from Grafana Beyla. If you have used Beyla, the configuration keys are the same with an
OTEL_EBPF_ prefix instead of BEYLA_.When to use eBPF instead of an SDK
eBPF observes the process boundary, so it sees requests between services rather than logic inside them. The two approaches complement each other and land in Bronto through the same OTLP pipeline, with matching trace IDs.
Use eBPF for immediate breadth, for services you cannot rebuild, and to find out where latency and errors actually are. Add an SDK in the services that then warrant domain detail. See Choose your language for SDK setup.
Requirements
Check a host before you start:
Prerequisites
- A Bronto account and API key (how to create one)
- An OpenTelemetry Collector — recommended, and required if you want delta metrics
- A host or cluster meeting the requirements above
Run OBI with Docker Compose
OBI runs as one container alongside your services. It needs the host PID namespace to see other containers’ processes, and elevated privileges to load eBPF programs.docker-compose.yml
obi.yml
Run OBI on Kubernetes
Deploy OBI as a DaemonSet so every node instruments its own pods. The container needshostPID: true and the eBPF capabilities:
obi-daemonset.yaml
obi.yml
Forward to Bronto
Point the Collector’s OTLP receiver at Bronto’s trace and metric endpoints. Traces and metrics use separate endpoints and the same API key header:otel-config.yaml
<REGION> with eu or us to match your account. A mismatched region returns 401, which looks identical to an invalid key.
Name your services
By default OBI names a service after its executable. That is fine for a compiled binary, but interpreted runtimes all end up named after the interpreter —node, python3.13, java.
OBI reads OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES from each target process’s own environment, so you can name services without adding an SDK or touching application code:
docker-compose.yml
Traces are stored in Bronto’s
.traces collection, with one dataset per service.name. service.namespace is carried on every span and is the attribute to filter on to isolate an environment, but it does not determine where traces are stored.What you will see in Bronto
OBI emits standard OpenTelemetry semantic conventions, so no Bronto-specific configuration is required. Traces. Each request produces a server span, client spans for outgoing calls, and — for Go services — anin queue and processing breakdown that separates time spent waiting to be handled from time spent being handled:
span.trace_id, span.parent_span_id, span.kind, span.duration_nano, http.route, http.request.method, http.response.status_code, service.name, service.namespace, and url.path.
Spans produced by OBI carry telemetry.distro.name = opentelemetry-ebpf-instrumentation, which is a convenient way to separate eBPF-derived data from SDK-derived data in queries.
Verify
1
Confirm OBI attached to your processes
OBI logs one line per instrumented process, including the runtime it detected:If nothing appears, your selector matched no processes — check
open_ports against the ports your services actually listen on.2
Print spans without leaving the terminal
Set
trace_printer: text in obi.yml to have OBI print every captured span to stdout as well as exporting it. Send a request through your service and confirm spans appear, then set it back to disabled.3
Check delivery to Bronto
Look for export errors in the Collector’s logs:A
401 means the API key or the region is wrong.4
Find the data in Bronto
Open Explore Traces and filter on your
service.name, or query service.namespace to see the whole environment. Metrics appear in the Metric Explorer under http.server.request.duration.Troubleshooting
- Spans appear but never join into a trace. Context propagation is off by default. Set
context_propagation: alland confirm the kernel is 5.17+. - A service you did not expect is being instrumented. Publishing a container port makes the Docker daemon listen on it too. Add
dockerd,docker-proxy, andcontainerdtoexclude_instrument. - Go services produce fewer details than expected. OBI attaches uprobes to Go runtime symbols. Building with
-ldflags="-s -w"strips the symbol table and silently degrades OBI to generic syscall tracing — leave Go binaries unstripped. creating OTEL namespace in bpffs failed./sys/fs/bpfis not mounted. Core tracing still works; features that rely on pinned maps, such as the log enricher, are disabled. Mount bpffs to restore them.FIONREAD compensation is ineffective. Applications that size reads viaFIONREAD— nginx, Java, and .NET among them — may stall or truncate transfers while context propagation is enabled. Validate against those runtimes, or setcontext_propagation: disabledfor them.- Metrics rejected or missing. Confirm you are not exporting exponential histograms, and that
cumulativetodeltais in the metrics pipeline.
Security considerations
Running withprivileged: true is the documented starting point and the simplest way to prove the setup works, but it grants more than OBI needs. For production, use the least-privilege capability set — CAP_BPF, CAP_PERFMON, CAP_NET_RAW, and CAP_DAC_READ_SEARCH among others, depending on the features you enable.
Set OTEL_EBPF_ENFORCE_SYS_CAPS=true so OBI fails loudly when a required capability is missing instead of degrading quietly.
Host PID namespace access is not optional: OBI must see other processes in order to attach to them.
Go library-level context propagation relies on
bpf_probe_write_user, which is blocked by Secure Boot and kernel lockdown mode. Network-level propagation still works in those environments.Next steps
- Explore Traces — investigate services, latency, and errors
- Send Metrics to Bronto — metric support and limitations
- Choose your language — add SDK instrumentation where you need business context
- Agent Setup — ship logs, which OBI does not produce
- OBI documentation — full configuration reference

