Distributed Tracing with OpenTelemetry
Flyte uses the otelutils package in flytestdlib to provide a unified interface for distributed tracing and metrics across its microservices. This infrastructure allows you to track requests as they flow through components like FlyteAdmin, FlytePropeller, and the DataProxy, using standard OpenTelemetry (OTel) protocols.
Configuring OpenTelemetry
You configure tracing and metrics through the otel configuration section. This section defines which exporter to use (e.g., Jaeger or OTLP) and how to sample traces.
The following example shows a typical configuration for exporting traces to a Jaeger collector using a 10% sampling rate:
otel:
type: jaeger
jaeger:
endpoint: http://jaeger-collector.flyte.svc.cluster.local:14268/api/traces
sampler:
parentSampler: traceid
traceIdRatio: 0.1
Supported Exporters
The ExporterType (defined in flytestdlib/otelutils/config.go) determines where telemetry data is sent:
noop: Disables all tracing and metrics (default).file: Writes traces to a local file (configured viaFileConfig.Filename).jaeger: Sends traces to a Jaeger collector endpoint.otlpgrpc: Uses the OTLP protocol over gRPC.otlphttp: Uses the OTLP protocol over HTTP.
Sampling Strategies
Sampling is managed via SamplerConfig. Flyte supports two primary SamplerType values:
always: Every request is traced.traceid: Traces a percentage of requests based on theTraceIDRatio.
Internally, otelutils wraps these in a ParentBased sampler. This ensures that if a parent span is already being sampled, the child spans in Flyte will also be sampled regardless of the local ratio.
Initializing the Providers
To enable tracing in a service, you must register the tracer and meter providers during startup. Use RegisterProvidersWithContext to initialize the global state based on your configuration.
import (
"github.com/flyteorg/flyte/v2/flytestdlib/otelutils"
)
func main() {
ctx := context.Background()
cfg := otelutils.GetConfig()
serviceName := "flyteadmin"
err := otelutils.RegisterProvidersWithContext(ctx, serviceName, cfg)
if err != nil {
// Handle initialization error
}
}
The RegisterProvidersWithContext function in flytestdlib/otelutils/factory.go performs several key tasks:
- Instantiates the appropriate
SpanExporterandMetricExporter. - Creates a
telemetryResourcethat automatically includes service information and versioning. - Sets up a
TracerProviderwith aBatcherfor efficient exporting. - Stores the providers in a global map indexed by the service name.
Manual Instrumentation
When you need to trace specific logic within a function, use the NewSpan helper. This function simplifies span creation by automatically extracting log fields from the context and adding them as span attributes.
func (s *myService) DoWork(ctx context.Context) error {
// Creates a span named "myService.DoWork" for the "flyteadmin" tracer
ctx, span := otelutils.NewSpan(ctx, "flyteadmin", "myService.DoWork")
defer span.End()
// ... implementation logic ...
return nil
}
NewSpan uses contextutils.GetLogFields(ctx) to find metadata (like project, domain, or workflowID) and attaches them to the span, ensuring that your traces are searchable by the same dimensions as your logs.
Automatic Integrations
Flyte provides built-in wrappers to automatically trace common infrastructure components.
Kubernetes Client Tracing
If your service interacts with the Kubernetes API, you can wrap the standard controller-runtime client to get automatic spans for every Get, List, Create, and Update call.
import "github.com/flyteorg/flyte/v2/flytestdlib/otelutils"
// Wrap an existing k8s client
tracedClient := otelutils.WrapK8sClient(k8sClient)
// Every call now generates a span under the "k8s-client" tracer
err := tracedClient.Get(ctx, key, obj)
The K8sClientWrapper in flytestdlib/otelutils/k8s.go intercepts these calls and creates spans with the prefix controller-runtime.pkg.client.
RPC Tracing with ConnectRPC
For services using ConnectRPC (like the Flyte app service), you can inject the OTel providers into interceptors to trace incoming and outgoing RPC calls:
otelInterceptor, err := otelconnect.NewInterceptor(
otelconnect.WithTracerProvider(otelutils.GetTracerProvider("flyte-service")),
otelconnect.WithMeterProvider(otelutils.GetMeterProvider("flyte-service")),
)
This ensures that the trace context is propagated across network boundaries between Flyte services.