Skip to content

Latest commit

 

History

History
129 lines (104 loc) · 7.04 KB

README.md

File metadata and controls

129 lines (104 loc) · 7.04 KB

gRPC Remote Storage Plugins

Update (May 2024): as of v1.58, Jaeger will no longer support grpc-sidecar plugin model. Only gRPC Remote Storage API is suppored.

In order to support an ecosystem of different backend storage implementations, Jaeger supports a gRPC-based Remote Strorage API. The custom backend storage solutions need to implement storage_v1 gRPC interfaces defined in plugin/storage/grpc/proto/.

+----------------------------------+                  +-----------------------------+
|                                  |                  |                             |
|                  +-------------+ |       gRPC       | +-------------+             |
|                  |             | |                  | |             |             |
| jaeger-component | grpc-client +----------------------> grpc-server | custom impl |
|                  |             | |                  | |             |             |
|                  +-------------+ |                  | +-------------+             |
|                                  |                  |                             |
+----------------------------------+                  +-----------------------------+

    Jaeger official components                         Custom backend implementation

Implementing a plugin

Although the instructions below are limited to Go, plugins can be implemented any language. Languages other than Go would implement a gRPC server using the storage_v1 proto interfaces. The proto file can be found in plugin/storage/grpc/proto/. To generate the bindings for your language you would use protoc with the appropriate xx_out= flag. This is detailed in the protobuf documentation and you can see an example of how it is done for Go in the top level Jaeger Makefile.

The easiest way to generate the gRPC storage plugin bindings is to use Docker Protobuf which is a lightweight protoc Docker image containing the dependencies needed to generate code for multiple languages. For example, one can generate bindings for C# on Windows with Docker for Windows using the following steps:

  1. First clone the Jaeger github repo to a folder (e.g. c:\source\repos\jaeger):
$ mkdir c:\source\repos\jaeger
$ cd c:\source\repos\jaeger
$ git clone https://github.com/jaegertracing/jaeger.git c:\source\repos\jaeger
  1. Initialize the Jaeger repo submodules (this pulls in code from other Jaeger repositories that are needed):
$ git submodule update --init --recursive
  1. Then execute the following Docker command which mounts the local directory c:\source\repos\jaeger to the directory /jaeger in the Docker container and then executes the jaegertracing/protobuf:0.2.0 command. This will create a file called Storage.cs in your local Windows folder c:\source\repos\jaeger\code containing the gRPC Storage API bindings.
$ docker run --rm -u 1000 -v/c/source/repos/jaeger:/jaeger -w/jaeger \
    jaegertracing/protobuf:0.2.0 "-I/jaeger -Iidl/proto/api_v2 -I/usr/include/github.com/gogo/protobuf -Iplugin/storage/grpc/proto --csharp_out=/jaeger/code plugin/storage/grpc/proto/storage.proto"

An example of a Go binary that implements Remote Storage API can be found in cmd/remote-storage. That specific binary does not implement a custom backend, instead it supports the same backend implementations as available directly in Jaeger, but it makes them accessible via a Remote Storage API (and is being used in the integration tests).

The API consists of several gRPC services:

  • SpanReaderPlugin - used for querying the data
  • SpanWriterPlugin - used for writing data
  • (optional) StreamingSpanWriterPlugin - allows more efficient transmission
  • (optional) ArchiveSpanWriterPlugin and ArchiveSpanReaderPlugin - to support archiving storage
  • (optional) DependenciesReaderPlugin - for reading service dependencies
  • (optional) PluginCapabilities - can be interrogated to find out which services an implementation supports

The API supports writing spans via gRPC stream, instead of unary messages. Streaming writes can improve throughput and decrease CPU load (see benchmarks in Issue #3636). The backend needs to implement StreamingSpanWriterPlugin service and indicate support via the streamingSpanWriter flag in the Capabilities response.

Note that using the streaming spanWriter may make the collector's save_by_svr metric inaccurate, in which case users will need to pay attention to the metrics provided by the plugin.

Certifying compliance

A plugin implementation shall verify it's correctness with Jaeger storage protocol by running the storage integration tests from integration package.

import (
	jaeger_integration_tests "github.com/jaegertracing/jaeger/plugin/storage/integration"
)

func TestJaegerStorageIntegration(t *testing.T) {
        ...
	si := jaeger_integration_tests.StorageIntegration{
		SpanReader: createSpanReader(),
		SpanWriter: createSpanWriter(),
		CleanUp: func() error { ... },
		Refresh: func() error { ... },
		SkipList: []string {  // Skip any unsupported tests
		},
	}
	// Runs all storage integration tests.
	si.IntegrationTestAll(t)
}

For more details, refer to one of the following implementations.

  1. jaeger-clickhouse
  2. Timescale DB via Promscale

Tracing

Jaeger requests to the backend implementation will include standard OTEL tracing headers. The implementation may choose to participate in those traces to allow end to end visibility of Jaeger's own operations (typically only enabled for read requests, as tracing write requests results in traces for traces and may cause infinite loops).

Bearer token propagation from the UI

When using --query.bearer-token-propagation=true, the bearer token will be properly passed on to the gRPC plugin server. To get access to the bearer token in your plugin, use a method similar to:

import (
    // ... other imports
    "fmt"
    "google.golang.org/grpc/metadata"

    "github.com/jaegertracing/jaeger/plugin/storage/grpc"
)

// ... spanReader type declared here

func (r *spanReader) extractBearerToken(ctx context.Context) (string, bool) {
	if md, ok := metadata.FromIncomingContext(ctx); ok {
		values := md.Get(grpc.BearerTokenKey)
		if len(values) > 0 {
			return values[0], true
		}
	}
	return "", false
}

// ... spanReader interface implementation

func (r *spanReader) GetServices(ctx context.Context) ([]string, error) {
    str, ok := r.extractBearerToken(ctx)
    fmt.Println(fmt.Sprintf("spanReader.GetServices: bearer-token: '%s', wasGiven: '%t'" str, ok))
    // ...
}