Integrating OpenTelemetry with Instana for Distributed Tracing
When working with distributed systems, you need visibility into what's happening across services. OpenTelemetry has emerged as the standard for generating telemetry data, while Instana provides a powerful backend to analyse and visualise that data.
When I started exploring OpenTelemetry with Instana, my primary goal was simple: get traces flowing end‑to‑end with minimal friction, and then refine the setup as complexity increased. I began with the simplest possible approach, exporting traces directly from the application to Instana. Once that was working, I experimented with routing traces through an OpenTelemetry Collector using both HTTP and gRPC exporters. That second setup added more flexibility, especially when testing configuration changes locally without modifying application code.
In this post, I'll walk through both approaches, explain when each makes sense, and highlight the practical differences based on hands‑on experience.
Why Instana with OpenTelemetry?
OpenTelemetry and Instana complement each other well. OpenTelemetry provides a vendor‑neutral way to generate telemetry data, while Instana offers a production‑ready observability backend with deep insights into distributed systems.
Using them together gives several practical advantages:
- Enterprise readiness - Instana provides automatic service mapping, dependency analysis, and backend insights that help teams quickly identify performance bottlenecks in production environments
- Vendor neutrality - instrumentation stays the same even if the backend changes
- Standardised telemetry - consistent tracing across services and languages
- Strong ecosystem - OpenTelemetry libraries are widely available and actively maintained
- Native OTLP support - Instana supports OTLP directly, without custom adapters
This makes it easy to start simple and evolve your observability setup over time.
OTLP: HTTP vs gRPC
The OpenTelemetry Protocol (OTLP) defines how telemetry data is transmitted. It supports two main transport options: OTLP/HTTP - Uses HTTP/1.1 or HTTP/2 with Protocol Buffers OTLP/gRPC - Uses gRPC (HTTP/2) with Protocol Buffers
You can use either HTTP or gRPC exporter in a local setup. Since both function equivalently in this scenario, select the exporter that best aligns with your existing infrastructure and preferred configuration approach.
Security Considerations
When configuring OpenTelemetry exporters, keep these security practices in mind:
- API Key Handling: Store Instana API keys in environment variables or secrets management systems, never hardcode them in source code
- TLS by Default: HTTPS endpoints use TLS automatically. The OpenTelemetry SDK enables TLS by default for secure connections
- Local Testing Only: The
WithInsecure() option disables TLS and should only be used for local development with the collector. Never use it in production or when connecting directly to Instana
Sending traces directly from your application to Instana
This diagram shows the direct export flow where your application sends traces straight to Instana's OTLP endpoint without any intermediate components.
This is the simplest possible setup: your application sends traces directly to Instana's OTLP endpoint. I started with this approach because it provides the fastest way to verify that the instrumentation is correct, traces are being generated, and data is successfully reaching Instana.
Environment Variable Configuration
The easiest setup uses standard OpenTelemetry environment variables:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<instana-otlp-endpoint>"
export OTEL_EXPORTER_OTLP_HEADERS="x-instana-key=<instana-agent-key>"
The OpenTelemetry SDK reads these automatically and configures the exporter. No code changes needed.
Programmatic Configuration
You can also configure the exporter directly in code. Below is an example using OTLP over HTTP in Go:
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
// Create OTLP HTTP exporter
exporter, err := otlptracehttp.New(
ctx,
otlptracehttp.WithEndpoint("instana-otlp-endpoint"),
otlptracehttp.WithHeaders(map[string]string{
"x-instana-key": "instana-agent-key",
}),
// TLS is enabled by default for https:// endpoints
)
if err != nil {
return nil, err
}
// Define resource attributes
res, err := resource.New(
ctx,
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceVersion("1.0.0"),
),
)
if err != nil {
return nil, err
}
// Create tracer provider
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
return tp, nil
}
Routing traces through an OpenTelemetry collector
This diagram illustrates the collector-based flow where traces are routed through an OpenTelemetry Collector before reaching Instana, enabling centralized telemetry processing and configuration.
With an OpenTelemetry Collector, traces pass through an intermediate telemetry pipeline before reaching Instana. While this adds another component, it becomes valuable when:
- multiple services are involved
- you want to transform, filter, or batch telemetry
- configuration changes should not require redeploying applications
- you may want to switch or add backends later
Running the Collector Locally
docker pull otel/opentelemetry-collector:<version>
docker run -d \
--name otel-collector \
-p 4317:4317 \
-p 4318:4318 \
-v $(pwd)/collector-config.yaml:/etc/otelcol/config.yaml \
otel/opentelemetry-collector:<version>
The collector exposes:
- 4317 for OTLP/gRPC
- 4318 for OTLP/HTTP
Collector Configuration
Create collector-config.yaml. Below is a minimal collector configuration that:
- receives traces via gRPC and HTTP
- batches them
- exports them to Instana using OTLP/HTTP
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 10s
exporters:
otlphttp:
endpoint: "https://<instana-otlp-endpoint>:443"
headers:
x-instana-key: "<instana-agent-key>"
tls:
insecure: false
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
If you prefer OTLP/gRPC to Instana, you can change the exporters configuration to:
exporters:
otlp:
endpoint: "<instana-otlp-endpoint>:443"
headers:
x-instana-key: "<instana-agent-key>"
tls:
insecure: false
Application Configuration with Collector
Once the collector is running, point your application to it instead of Instana directly.
Environment Variable Configuration
# For gRPC
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
# For HTTP
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
Programmatic Configuration (gRPC)
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func newExporter(ctx context.Context) (sdktrace.SpanExporter, error) {
return otlptracegrpc.New(
ctx,
otlptracegrpc.WithEndpoint("localhost:4317"),
otlptracegrpc.WithInsecure(), // Collector is local
)
}
Programmatic Configuration (HTTP)
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func newExporter(ctx context.Context) (sdktrace.SpanExporter, error) {
return otlptracehttp.New(
ctx,
otlptracehttp.WithEndpoint("localhost:4318"),
otlptracehttp.WithInsecure(), // Collector is local
)
}
You can use either HTTP or gRPC exporter in a local setup. Since both function equivalently in this scenario, select the exporter that best aligns with your existing infrastructure and preferred configuration approach.
Conclusion
There are two straightforward ways to send OpenTelemetry traces to Instana:
Direct export works well for:
- Development and testing
- Single-service applications
- Simple deployments where you don't need telemetry processing
Collector-based export becomes useful when:
- You have multiple services sending traces
- You need to transform or filter telemetry data
- You want to switch backends without redeploying applications
- You're running in production with multiple environments
Start with direct export to validate instrumentation; introduce the collector once observability becomes a shared, multi-service concern. Direct export is suitable for simple deployments. For environments involving multiple services or requiring greater flexibility, an OpenTelemetry Collector provides a more scalable solution.