Go
Zero-code OpenTelemetry instrumentation for Go applications with the KubeSense Go build tool.
Go compiles to a single static binary, so instrumentation is added at build time. The KubeSense OpenTelemetry Go tool (otel) wraps the Go compiler. You replace go build with otel go build, and the tool injects OpenTelemetry instrumentation into the binary.
- No code changes
- A drop-in replacement for
go build - Works with existing Go applications on VMs and Kubernetes
Auto-instrumentation on VMs and Kubernetes (no code changes)
Step 1: Download the tool
The tool requires Go 1.23 or later. Download the binary for the machine that builds your application:
| OS | Architecture | Download URL |
|---|---|---|
| Linux | amd64 | https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-linux-amd64 |
| Linux | arm64 | https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-linux-arm64 |
| macOS | Intel (amd64) | https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-darwin-amd64 |
| macOS | Apple Silicon (arm64) | https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-darwin-arm64 |
| Windows | amd64 | https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-windows-amd64.exe |
For example, on Linux amd64:
curl -sSfL -o /usr/local/bin/otel \
https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-linux-amd64
chmod +x /usr/local/bin/otelIf you don't want to put it on your PATH, keep it anywhere and call it by its full path, for example ~/Downloads/otel-darwin-arm64.
Step 2: Build with the tool
In your Go project root, replace your usual build command:
# Before
go build -o app .
# After
otel go build -o app .The tool:
- runs the normal Go compiler with your usual flags
- adds OpenTelemetry instrumentation to the supported libraries your application uses
- produces an instrumented binary that you deploy as usual
On Windows:
.\otel-windows-amd64.exe go build -o app.exe .Container image (multi-stage build, for Kubernetes)
FROM golang:1.23 AS build
WORKDIR /src
ARG KUBESENSE_OTEL_GO_VERSION=v1.0.0
RUN curl -sSfL -o /usr/local/bin/otel \
https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-${KUBESENSE_OTEL_GO_VERSION}/otel-linux-amd64 \
&& chmod +x /usr/local/bin/otel
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 otel go build -o /out/app .
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]On an arm64 build machine, use otel-linux-arm64.
Step 3: Configure the exporter at runtime
The instrumented binary reads the standard OpenTelemetry environment variables. Set them where the application runs:
OTEL_SERVICE_NAME=inventory-service
OTEL_EXPORTER_OTLP_ENDPOINT=http://<KUBESENSE_INGEST_HOST>:33443
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_RESOURCE_ATTRIBUTES=kubesense.cluster=<CLUSTER_NAME>,kubesense.env_type=legacy
OTEL_METRICS_EXPORTER=none
OTEL_LOGS_EXPORTER=none| Where the app runs | OTEL_EXPORTER_OTLP_ENDPOINT | kubesense.env_type |
|---|---|---|
| Kubernetes (same cluster as KubeSense) | http://kubesense-kubecol.kubesense.svc.cluster.local:33443, or your OpenTelemetry Collector | k8s |
| VM | http://<KUBESENSE_INGEST_HOST>:33443, or a local OpenTelemetry Collector | legacy |
Step 4: Verify
Start the application and generate some traffic. Then open APM → Traces in KubeSense and filter by your service name.
Alternative on Kubernetes: eBPF instrumentation with the Operator
If you can't change the build, the OpenTelemetry Operator can instrument a running Go binary with eBPF. Enable --enable-go-instrumentation on the operator (Step 1 of that guide), then annotate the pod template:
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-go: "kubesense/otel-instrumentation"
instrumentation.opentelemetry.io/otel-go-auto-target-exe: "/app" # full path of the binary in the containerThe injected sidecar needs elevated privileges to attach eBPF probes. Some clusters don't allow this, for example those with restricted Pod Security Standards. Build-time instrumentation with otel go build has no such requirement.
Manual instrumentation (SDK)
With manual instrumentation, you add the OpenTelemetry SDK packages to your module and initialize them in code. Use this instead of otel go build.
Step 1: Install the packages
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp| Package | Purpose |
|---|---|
otel, otel/sdk | OpenTelemetry API and SDK |
otlptracehttp | Sends traces over OTLP/HTTP |
contrib/instrumentation/net/http/otelhttp | Spans for incoming and outgoing HTTP requests |
Step 2: Add the setup code
Create telemetry.go:
package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
// setupTelemetry configures tracing from the OTEL_* environment variables
// and returns a function that flushes and shuts it down.
func setupTelemetry(ctx context.Context) (func(context.Context) error, error) {
// Service name and kubesense.* attributes from OTEL_SERVICE_NAME / OTEL_RESOURCE_ATTRIBUTES
res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithHost(), resource.WithTelemetrySDK())
if err != nil {
return nil, err
}
// Reads OTEL_EXPORTER_OTLP_ENDPOINT (base URL)
traceExporter, err := otlptracehttp.New(ctx)
if err != nil {
return nil, err
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(traceExporter),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tracerProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{},
))
return tracerProvider.Shutdown, nil
}Call it at startup and wrap your HTTP handlers:
func main() {
ctx := context.Background()
shutdown, err := setupTelemetry(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
mux := http.NewServeMux()
mux.HandleFunc("/orders", ordersHandler)
log.Fatal(http.ListenAndServe(":8080", otelhttp.NewHandler(mux, "server")))
}Step 3: Configure the endpoints
Set the environment variables from Manual SDK: where to send traces. Other libraries are traced only where you add their instrumentation package, for example otelgrpc for gRPC or otelsql for database/sql. See the OpenTelemetry Go registry.
Troubleshooting
| Symptom | Check |
|---|---|
permission denied when running otel | Make the file executable: chmod +x |
exec format error | You downloaded the wrong OS or architecture. Use the binary for the build machine, not the target |
| Application runs, but no traces | The binary was built with plain go build. Rebuild with otel go build and redeploy |
Connection errors to :33443 | Trace ingestion is not enabled on KubeSense, or the port is not reachable from where the application runs |
| Exporter errors for metrics or logs | Set OTEL_METRICS_EXPORTER=none and OTEL_LOGS_EXPORTER=none |