トレーサーは、マイクロサービス アーキテクチャの実装において重要な役割を果たし、アプリケーション ロジックで実行されているプロセスの「トレース」概要を提供するオブザーバビリティの一部です。
簡単に言うと、Web サービスでは、Tracer は Trace 信号を発行することで、ロジックの実行時間の長さのアイデアを提供します。後で、このトレーサーは、エクスポーターを介してコレクターに信号を送信した後、ネストされたスパンの形式で視覚化され、表示されます。 [OpenTelemetry: トレース]
後でコレクタによって収集できるトレース信号を送信できるようにするために、Web サービスにはライブラリとして OpenTelemetry が必要です。このライブラリは、通常 OpenTelemetry Protocol (OTLP) と呼ばれる標準の可観測性プロトコルとなっています。 [OpenTelemetry: 言語 - 移動]
Web サービスでどのようなプロセスが発生するかの概要を提供するには、トレース信号の視覚化が実際に必要です。 Yeter は、HTTP または gRPC 通信プロトコルを利用して OTLP をサポートするオープンソース プラットフォームです。 [イェーガー]
Golang プログラミング言語での Tracer の実装は、Web サービスが異なる応答期間のデータのみを返す単純なケースを実装します。使用されるライブラリは次のとおりです:
pkg/telemetry/telemetry.go ディレクトリに Telemetry モジュールを実装します。
package telemetry import ( "context" "errors" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" ) // enumeration constant for which protocol used const ( HTTP uint8 = iota GRPC ) // setup client to connect web-service with Jaeger func SetupTraceClient(ctx context.Context, protocol uint8, endpoint string) otlptrace.Client { switch protocol { case GRPC: return otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure(), otlptracegrpc.WithCompressor("gzip")) default: return otlptracehttp.NewClient(otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), otlptracehttp.WithCompression(otlptracehttp.NoCompression)) } } func setupTraceProvider(ctx context.Context, traceClient otlptrace.Client) (*trace.TracerProvider, error) { // set resource res, err := resource.New( ctx, resource.WithFromEnv(), ) if err != nil { return nil, err } // init trace exporter traceExporter, err := otlptrace.New(ctx, traceClient) if err != nil { return nil, err } // init trace exporter traceProvider := trace.NewTracerProvider( trace.WithBatcher( traceExporter, trace.WithBatchTimeout(time.Duration(time.Second*3)), ), trace.WithResource(res), // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables. ) return traceProvider, nil } func SetupTelemetrySDK(ctx context.Context, traceClient otlptrace.Client) (func(context.Context) error, error) { var err error var shutdownFuncs []func(context.Context) error shutdown := func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } handleErr := func(inErr error) { err = errors.Join(inErr, shutdown(ctx)) } traceProvider, err := setupTraceProvider(ctx, traceClient) if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, traceProvider.Shutdown) otel.SetTracerProvider(traceProvider) return shutdown, nil }
次に、メイン関数 main.go でテレメトリ構成をセットアップします。
package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-chi/chi/v5" "github.com/wahyurudiyan/medium/otel-jaeger/config" "github.com/wahyurudiyan/medium/otel-jaeger/pkg/telemetry" "github.com/wahyurudiyan/medium/otel-jaeger/router" ) func SetupTelemetry(ctx context.Context, config *config.Config) (func(context.Context) error, error) { otlpCli := telemetry.SetupTraceClient(ctx, telemetry.GRPC, config.JaegerGRPCEndpoint) shutdownFn, err := telemetry.SetupTelemetrySDK(ctx, otlpCli) return shutdownFn, err } func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cfg := config.Get() shutdownFn, err := SetupTelemetry(ctx, cfg) if err != nil { shutdownFn(ctx) panic(err) } r := chi.NewRouter() r.Route("/api", func(r chi.Router) { router.Router(r) }) srv := http.Server{ Addr: "0.0.0.0:8080", Handler: r, } go func() { fmt.Println("Server running at port:", srv.Addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Printf("listen: %s\n", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <p>router/router.go ファイルのハンドラーで Tracer を使用して、トレース信号を送信します。<br> </p> <pre class="brush:php;toolbar:false">package router import ( "encoding/json" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/wahyurudiyan/medium/otel-jaeger/pkg/random" "go.opentelemetry.io/otel" ) var ( tracer = otel.Tracer("WebServer-Otel-Jaeger") ) func getUserHandler(w http.ResponseWriter, r *http.Request) { _, span := tracer.Start(r.Context(), "GetUser") defer span.End() user := struct { Name string Email string Password string }{ Name: "John Doe", Email: "john@email.com", Password: "Super5ecr3t!", } blob, _ := json.Marshal(&user) sleepDuration := time.Duration(time.Millisecond * time.Duration(random.GenerateRandNum())) time.Sleep(sleepDuration) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(blob) } func Router(router chi.Router) { router.Get("/user", getUserHandler) }
Dockerfile のマルチステージ ビルド イメージ メカニズムを利用した、このビルド Web サービスの Docker 構成:
FROM golang:1.23.4 AS build WORKDIR /src COPY . . RUN go get -v RUN CGO_ENABLED=0 go build -o /bin/service . FROM alpine:latest WORKDIR /app COPY --from=build /bin/service /bin/service CMD ["/bin/service"]
次に、次の構成で docker-compose.yaml ファイルを介してイメージのビルドが実行されます。
services: web-service: container_name: service build: context: . dockerfile: Dockerfile environment: OTEL_SERVICE_NAME: service-otel-jaeger JAEGER_GRPC_ENDPOINT: jaeger:4317 entrypoint: ["service"] ports: - 8080:8080 jaeger: container_name: jaeger image: jaegertracing/all-in-one:latest environment: COLLECTOR_ZIPKIN_HOST_PORT: :9411 ports: - 16686:16686 - 4317:4317 - 4318:4318 - 9411:9411
service.jaeger.ports で公開されているポートは次のポートです:
docker-compose.yaml で定義されているアプリケーションを実行します。次のコマンドを使用できます。
docker compose up --build
アプリケーションの実行後、エンドポイント http://127.0.0.1:8080/api/user でアプリケーションを実行してみてください。Web サービスとアプリケーションが接続されている場合は、図のようにサービス名が表示されます。
プロセスの実行にかかる時間を定義するためのスパンが表示されます。
次に、CLI ツール hey [https://github.com/rakyll/hey] を使用して負荷テストを実行してみましょう。次のコマンドを使用して、簡単な負荷テストを実行できます:
package telemetry import ( "context" "errors" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" ) // enumeration constant for which protocol used const ( HTTP uint8 = iota GRPC ) // setup client to connect web-service with Jaeger func SetupTraceClient(ctx context.Context, protocol uint8, endpoint string) otlptrace.Client { switch protocol { case GRPC: return otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure(), otlptracegrpc.WithCompressor("gzip")) default: return otlptracehttp.NewClient(otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), otlptracehttp.WithCompression(otlptracehttp.NoCompression)) } } func setupTraceProvider(ctx context.Context, traceClient otlptrace.Client) (*trace.TracerProvider, error) { // set resource res, err := resource.New( ctx, resource.WithFromEnv(), ) if err != nil { return nil, err } // init trace exporter traceExporter, err := otlptrace.New(ctx, traceClient) if err != nil { return nil, err } // init trace exporter traceProvider := trace.NewTracerProvider( trace.WithBatcher( traceExporter, trace.WithBatchTimeout(time.Duration(time.Second*3)), ), trace.WithResource(res), // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables. ) return traceProvider, nil } func SetupTelemetrySDK(ctx context.Context, traceClient otlptrace.Client) (func(context.Context) error, error) { var err error var shutdownFuncs []func(context.Context) error shutdown := func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } handleErr := func(inErr error) { err = errors.Join(inErr, shutdown(ctx)) } traceProvider, err := setupTraceProvider(ctx, traceClient) if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, traceProvider.Shutdown) otel.SetTracerProvider(traceProvider) return shutdown, nil }
このコマンドは、1 秒あたり 100 リクエスト (RPS) の負荷テストを 10 分間実行します。 Jaeger UI ページに表示される結果は次のようになります。
負荷テストの実行が完了すると、負荷テストの結果のレポートが表示されます。
package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-chi/chi/v5" "github.com/wahyurudiyan/medium/otel-jaeger/config" "github.com/wahyurudiyan/medium/otel-jaeger/pkg/telemetry" "github.com/wahyurudiyan/medium/otel-jaeger/router" ) func SetupTelemetry(ctx context.Context, config *config.Config) (func(context.Context) error, error) { otlpCli := telemetry.SetupTraceClient(ctx, telemetry.GRPC, config.JaegerGRPCEndpoint) shutdownFn, err := telemetry.SetupTelemetrySDK(ctx, otlpCli) return shutdownFn, err } func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cfg := config.Get() shutdownFn, err := SetupTelemetry(ctx, cfg) if err != nil { shutdownFn(ctx) panic(err) } r := chi.NewRouter() r.Route("/api", func(r chi.Router) { router.Router(r) }) srv := http.Server{ Addr: "0.0.0.0:8080", Handler: r, } go func() { fmt.Println("Server running at port:", srv.Addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Printf("listen: %s\n", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <h2> Github プロジェクト </h2> <p>完全なコードを試したり確認したい場合は、次のリポジトリのクローンを作成できます: https://github.com/wahyurudiyan/otel-jaeger.</p>
以上が[言語] Tracer: Open Telemetry、Golang、Jagger の簡単な実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。