Kubesense

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:

OSArchitectureDownload URL
Linuxamd64https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-linux-amd64
Linuxarm64https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-linux-arm64
macOSIntel (amd64)https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-darwin-amd64
macOSApple Silicon (arm64)https://github.com/kubesense-ai/kubesense.github.io/releases/download/kubesense-otel-go-v1.0.0/otel-darwin-arm64
Windowsamd64https://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/otel

If 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 runsOTEL_EXPORTER_OTLP_ENDPOINTkubesense.env_type
Kubernetes (same cluster as KubeSense)http://kubesense-kubecol.kubesense.svc.cluster.local:33443, or your OpenTelemetry Collectork8s
VMhttp://<KUBESENSE_INGEST_HOST>:33443, or a local OpenTelemetry Collectorlegacy

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 container

The 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
PackagePurpose
otel, otel/sdkOpenTelemetry API and SDK
otlptracehttpSends traces over OTLP/HTTP
contrib/instrumentation/net/http/otelhttpSpans 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

SymptomCheck
permission denied when running otelMake the file executable: chmod +x
exec format errorYou downloaded the wrong OS or architecture. Use the binary for the build machine, not the target
Application runs, but no tracesThe binary was built with plain go build. Rebuild with otel go build and redeploy
Connection errors to :33443Trace ingestion is not enabled on KubeSense, or the port is not reachable from where the application runs
Exporter errors for metrics or logsSet OTEL_METRICS_EXPORTER=none and OTEL_LOGS_EXPORTER=none