Closed15

100日チャレンジ day40 (マイクロサービスにおけるOpenTelemetryとGrafana Stackでの可視化)

riddle_tecriddle_tec

昨日
https://zenn.dev/gin_nazo/scraps/e2cb8a9ef4f1eb


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

https://github.com/lirlia/100day_challenge_backend/tree/main/day40_otel_grafana_go

riddle_tecriddle_tec

承知いたしました。それでは、この OpenTelemetry と Grafana Stack を使ったGoマイクロサービス風アプリケーションの学習プロジェクトを Day40 として進めます。

プロジェクト名は day40_otel_grafana_go に変更します。

改訂版プロジェクト概要 (Day40):

  • プロジェクト名: day40_otel_grafana_go
  • UI兼Gateway Service (Go):
    • ポート 8080 で動作。
    • net/httphtml/template を使用。
    • /ui でシンプルな操作ページを提供。ボタンを押すと、後述の各マイクロサービスを呼び出す。
    • Otel で計装。
  • Product Service (Go):
    • ポート 8081 で動作。商品情報の取得や検証を行う架空のサービス。Otel で計装。
  • Inventory Service (Go):
    • ポート 8082 で動作。在庫の確認や引き当てを行う架空のサービス。Otel で計装。
  • Order Service (Go):
    • ポート 8083 で動作。注文の確定処理を行う架空のサービス。Otel で計装。
  • OpenTelemetry (Otel) 計装:
    • トレース: Go SDK を使用し、OTLPエクスポーター経由で Grafana Tempo に送信。
    • メトリクス: Go SDK を使用し、各サービスが /metrics を公開、Prometheus がスクレイプ。
    • ログ: Go標準の log または slog を使用し、JSON形式で標準出力。Promtail が収集し Grafana Loki に送信。
  • Grafana Stack (Docker Compose):
    • Grafana, Loki, Tempo, Prometheus, Promtail。

具体的な作業順序 (Day40):

  1. プロジェクト初期化:
    • template ディレクトリをコピーして day40_otel_grafana_go を作成。
    • Next.js 関連のファイルを削除。
    • package.jsonnameday40_otel_grafana_go に変更。
    • day40_otel_grafana_go 直下に README.md を作成・初期化。
    • 各Goサービス用ディレクトリ (gateway_service, product_service, inventory_service, order_service) を day40_otel_grafana_go/ 配下に作成。
    • 各サービスディレクトリ内で go mod init github.com/lirlia/100day_challenge_backend/day40_otel_grafana_go/xxx_service を実行。
    • day40_otel_grafana_go/ ルートに go.work ファイルを作成し、各サービスモジュールを登録 (go work init ./gateway_service ./product_service ./inventory_service ./order_service)。
  2. Grafana Stack (Docker Compose) のセットアップ:
    • day40_otel_grafana_go/docker-compose.yml を作成 (Grafana, Loki, Tempo, Prometheus, Promtail)。
    • day40_otel_grafana_go/prometheus/prometheus.yml を作成。
    • day40_otel_grafana_go/promtail/promtail-config.yml を作成。
  3. 各Goマイクロサービスの基本的なHTTPサーバー実装 (Otel計装なし):
    • gateway_service:
      • /ui エンドポイント (HTMLフォームを提供)。
      • /execute-order エンドポイント (他のサービスを呼び出すロジック、最初は固定レスポンスでも可)。
    • product_service, inventory_service, order_service:
      • 簡単なHTTPエンドポイントを1つずつ作成 (例: /products, /inventory, /orders)。固定レスポンスを返す。
  4. サービス間連携の実装:
    • gateway_service/execute-order から、他の各サービスのエンドポイントをHTTPで呼び出すように実装。
  5. OpenTelemetry 計装 (トレース):
    • 各GoサービスにOtel SDK (go.opentelemetry.io/otel, go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc 等) を追加。
    • トレースプロバイダーとOTLPエクスポーター (Tempo向け) を初期化する共通関数を作成 (例: common/tracing/tracing.go)。
    • otelhttp を使用して各サービスのHTTPサーバーとクライアントを計装。
    • 手動でカスタムスパンを追加してみる (例: 特定の処理区間)。
  6. OpenTelemetry 計装 (メトリクス):
    • 各Goサービスに Otel SDK (メトリクス関連) を追加。
    • Prometheusエクスポーター (go.opentelemetry.io/otel/exporters/prometheus) をセットアップし、各サービスが /metrics エンドポイントを公開するようにする。
    • 基本的なHTTPメトリクス (リクエスト数、レイテンシ) を収集。カスタムメトリクスも試す。
  7. OpenTelemetry 計装 (ログ):
    • 各Goサービスで、リクエスト情報や処理結果を含むログをJSON形式で標準出力 (slogNewJSONHandler(os.Stdout, ...) など)。
    • (PromtailがDockerログを収集する設定になっていれば、特別なOtelログエクスポーターは不要な場合が多い)
  8. 動作確認とGrafanaでの可視化:
    • 全サービスとGrafana Stackを docker-compose up -d で起動。
    • gateway_service/ui にアクセスし、操作を実行。
    • Grafanaで以下を確認:
      • Tempo: 分散トレースが正しく表示されるか。サービス間の呼び出し関係が追えるか。
      • Prometheus: 各サービスのメトリクスが収集され、クエリできるか。
      • Loki: 各サービスのログが収集され、クエリできるか。
    • Grafana で簡単なダッシュボードを作成。
  9. テストとドキュメント化:
    • curl コマンドなどで各エンドポイントをテスト。
    • README.md にセットアップ手順、アーキテクチャ図、各サービスの説明、Otel設定のポイント、Grafanaでの確認方法などを記述。
    • .cursor/rules/knowledge.mdc を更新。
riddle_tecriddle_tec

いくつか手順についてレビューです、手順に反映してください​

  • Grafana Stack (Docker Compose) のセットアップができたら起動確認を実施して
  • Go のマイクロサービスができたらその都度、API テストを行ってください
  • サービス間の連携も問題なくできるかテストを行ってください
  • トレース、メトリクス、ログを一気に対応していますが、トレース実装→投げているかのテスト→受け取れているかのテスト をし確認できてから次に進むようにしてください
  • 各工程ごとにテストを行い、成功を確認したらコミットし作業を進めてください

上記の内容で改善した手順を出力してください。その後私が ok といったら作業を始めてください

riddle_tecriddle_tec

はい、承知いたしました。ご指摘いただいたレビュー点を反映し、より詳細で段階的な作業手順に改善します。

改訂版・詳細作業順序 (Day40):

  1. プロジェクト初期化:

    • template ディレクトリをコピーして day40_otel_grafana_go を作成。
    • Next.js 関連のファイルを削除。
    • package.jsonnameday40_otel_grafana_go に変更。
    • day40_otel_grafana_go 直下に README.md を作成・初期化。
    • 各Goサービス用ディレクトリ (gateway_service, product_service, inventory_service, order_service) を day40_otel_grafana_go/ 配下に作成。
    • 各サービスディレクトリ内で go mod init github.com/lirlia/100day_challenge_backend/day40_otel_grafana_go/xxx_service を実行。
    • day40_otel_grafana_go/ ルートに go.work ファイルを作成し、各サービスモジュールを登録。
    • テスト: ディレクトリ構造、ファイルが正しく作成されたか確認。
    • コミット: day40: step 1/11 Project initialization
  2. Grafana Stack (Docker Compose) のセットアップ:

    • day40_otel_grafana_go/docker-compose.yml を作成 (Grafana, Loki, Tempo, Prometheus, Promtail)。
    • day40_otel_grafana_go/prometheus/prometheus.yml を作成。
    • day40_otel_grafana_go/promtail/promtail-config.yml を作成。
    • テスト: docker-compose up -d を実行し、各コンテナがエラーなく起動することを確認。Grafana (通常 http://localhost:3000) にアクセスできることを確認。
    • コミット: day40: step 2/11 Grafana Stack setup and basic startup test
  3. Gateway Service 基本実装:

    • gateway_service/main.go を作成。
    • HTTPサーバーを起動 (ポート 8080)。
    • /ui エンドポイント: 簡単なHTMLフォーム(例: 注文ボタン)を html/template で提供。
    • /execute-order エンドポイント: 最初は固定レスポンス(例: "Order processing started")を返す。
    • テスト: go run ./gateway_service で起動。ブラウザで /ui にアクセスし表示を確認。curl localhost:8080/execute-order でレスポンスを確認。
    • コミット: day40: step 3/11 Implement basic Gateway Service
  4. Product Service 基本実装:

    • product_service/main.go を作成。
    • HTTPサーバーを起動 (ポート 8081)。
    • /products エンドポイント: 固定レスポンス(例: {"id": "prod123", "name": "Awesome Product"}) を返す。
    • テスト: go run ./product_service で起動。curl localhost:8081/products でレスポンスを確認。
    • コミット: day40: step 4/11 Implement basic Product Service
  5. Inventory Service 基本実装:

    • inventory_service/main.go を作成。
    • HTTPサーバーを起動 (ポート 8082)。
    • /inventory エンドポイント: 固定レスポンス(例: {"productId": "prod123", "stock": 100}) を返す。
    • テスト: go run ./inventory_service で起動。curl localhost:8082/inventory でレスポンスを確認。
    • コミット: day40: step 5/11 Implement basic Inventory Service
  6. Order Service 基本実装:

    • order_service/main.go を作成。
    • HTTPサーバーを起動 (ポート 8083)。
    • /orders エンドポイント: 固定レスポンス(例: {"orderId": "order789", "status": "created"}) を返す。
    • テスト: go run ./order_service で起動。curl localhost:8083/orders でレスポンスを確認。
    • コミット: day40: step 6/11 Implement basic Order Service
  7. サービス間連携の実装とテスト:

    • gateway_service/execute-order ハンドラを修正。
      • HTTPクライアントを使用し、product_service (http://localhost:8081/products)、inventory_service (http://localhost:8082/inventory)、order_service (http://localhost:8083/orders) を順に呼び出す。
      • 各サービスからのレスポンスをログに出力(または単純に連結してレスポンスとして返す)。
    • テスト: 全サービスを個別のターミナルで起動 (go run ./xxx_service)。gateway_service/ui から操作を実行、または curl localhost:8080/execute-order を実行。gateway_service のログで各サービス呼び出しが成功していることを確認。
    • コミット: day40: step 7/11 Implement and test service-to-service communication
  8. OpenTelemetry 計装 (トレース):

    • Otel SDK (go.opentelemetry.io/otel, OTLP gRPC exporterなど) を各Goプロジェクトの go.mod に追加。
    • 共通のトレース初期化関数 (common/tracing/tracing.go などを作成し、各サービスから利用できるようにする)。
      • TracerProvider の設定、OTLP gRPCエクスポーター (Tempoのtempo:4317 を指す) の設定。
    • 各サービスの main 関数でトレースプロバイダーを初期化。
    • otelhttp を使用して、各サービスのHTTPサーバーと、gateway_service 内のHTTPクライアントを計装。
    • テスト (送信): 全サービスとDocker Composeスタックを起動。gateway_service/ui から操作を実行。エラーが出ないことを確認。
    • テスト (受信): Grafana (Tempoデータソース) でトレースが収集され、サービス間の呼び出しが正しく表示されるか確認。
    • コミット: day40: step 8/11 Implement and test OpenTelemetry Tracing
  9. OpenTelemetry 計装 (メトリクス):

    • Otel SDK (メトリクス関連、Prometheusエクスポーター) を各Goプロジェクトの go.mod に追加。
    • 各サービスの main 関数でPrometheusエクスポーターをセットアップし、/metrics エンドポイントを公開。
    • otelhttp による基本的なHTTPメトリクスが収集されるようにする。
    • テスト (送信): 全サービスとDocker Composeスタックを起動。数回 /ui から操作。各サービスの /metrics エンドポイントに curl でアクセスし、メトリクスが出力されることを確認。
    • テスト (受信): Grafana (Prometheusデータソース) でメトリクス (例: http_server_duration_seconds_count) が収集され、クエリできるか確認。
    • コミット: day40: step 9/11 Implement and test OpenTelemetry Metrics
  10. OpenTelemetry 計装 (ログ):

    • 各Goサービスで log/slog を使用し、構造化ログ (JSON形式) を標準出力に出力するように変更。
      • リクエストID、メソッド、パス、ステータスコード、処理時間などを含める。
      • Otel トレースIDとスパンIDをログに含める (slog.Attrotel.GetTraceMetadata(ctx) を利用)。
    • promtail-config.yml でDockerコンテナの標準出力を収集し、Lokiに送信する設定を確認・調整。
      • LokiがログからトレースIDをパースできるように設定 (derived fields)。
    • テスト (送信): 全サービスとDocker Composeスタックを起動。/ui から操作。各サービスのコンテナログ (例: docker logs <container_name>) でJSON形式のログが出力され、トレースIDが含まれていることを確認。
    • テスト (受信): Grafana (Lokiデータソース) でログが収集され、クエリできるか確認。トレースIDでログをフィルタリングできるか、Trace to Logs連携 (Tempoのトレースから関連ログへジャンプ) が機能するか確認。
    • コミット: day40: step 10/11 Implement and test OpenTelemetry Logging and Trace-to-Logs
  11. ドキュメント作成と最終確認:

    • README.md の更新:
      • アーキテクチャ図 (簡易でOK)。
      • セットアップ手順 (Goモジュール、Docker Compose)。
      • 各サービスの起動方法。
      • Otel設定の主要なポイント。
      • Grafanaでのトレース、メトリクス、ログの確認方法。
      • 学んだこと、詰まった点など。
    • .cursor/rules/knowledge.mdc を更新。
    • 不要なファイルやコードを削除。
    • テスト: 全体を通して動作確認。
    • コミット: day40: step 11/11 Final documentation and cleanup

こちらの改善された手順でよろしいでしょうか?

riddle_tecriddle_tec

o3レビュー後

はい、承知いたしました。さらに詳細なレビューありがとうございます!
いただいたフィードバックを全て反映し、より実践的で学びの多い手順に改善しました。

改訂版・詳細作業順序 (Day40) v3:

  1. プロジェクト初期化:

    • template ディレクトリをコピーして day40_otel_grafana_go を作成。
    • Next.js 関連ファイルを削除。package.jsonname を変更。
    • day40_otel_grafana_go/README.md を作成・初期化。
    • Goサービス用ディレクトリ (gateway_service, product_service, inventory_service, order_service) と、共通処理用ディレクトリ (internal/pkg/otel, internal/pkg/httpclient) を day40_otel_grafana_go/ 配下に作成。
    • 各サービスディレクトリと internal/pkg 内のモジュールで go mod init
    • ルートに go.work ファイルを作成し、全モジュールを登録。
    • テスト: ディレクトリ構造、ファイル確認。
    • コミット: day40: step 1/12 Project initialization and common module structure
  2. Grafana Stack (Docker Compose) セットアップと設定:

    • day40_otel_grafana_go/docker-compose.yml を作成 (Grafana, Loki, Tempo, Prometheus, Promtail)。
    • day40_otel_grafana_go/prometheus/prometheus.yml を作成。
      • scrape_configs セクションに、各Goサービス (gateway_service:8080, product_service:8081 等) の /metrics エンドポイントを static_configs で明示的に指定。
    • day40_otel_grafana_go/promtail/promtail-config.yml を作成。
    • day40_otel_grafana_go/grafana/provisioning/datasources/datasources.yml を作成:
      • Loki, Tempo, Prometheus のデータソースを定義。
      • Loki: derivedFields を設定し、ログメッセージ内の traceID からTempoへのリンクを自動生成。
      • Tempo: tracesToLogs を設定し、Lokiデータソースを指定してトレースからログへのリンクを有効化。
    • docker-compose.yml でGrafanaのプロビジョニングディレクトリをマウント。
    • テスト: docker-compose up -d で全コンテナ起動確認。Grafana UIで各データソースが自動設定され、Loki/Tempoの連携設定が反映されているか確認。
    • コミット: day40: step 2/12 Grafana Stack setup with provisioning and enhanced configs
  3. 共通Otelパッケージ実装 (internal/pkg/otel/otel.go):

    • InitTracerProvider(serviceName, serviceVersion, environment string) (*sdktrace.TracerProvider, error) 関数:
      • resource.NewWithAttributessemconv.ServiceNameKey, semconv.ServiceVersionKey, semconv.DeploymentEnvironmentKey を設定。
      • OTLP gRPCトレースエクスポーター (Tempo宛) を設定。
      • sdktrace.NewTracerProvider を作成して返す。
    • InitMeterProvider(serviceName, serviceVersion, environment string) (*sdkmetric.MeterProvider, error) 関数:
      • 同様にリソースを設定。
      • Prometheusエクスポーター (prometheusexporter.New()) を設定。
      • sdkmetric.NewMeterProvider を作成して返す。
    • GetTracer()GetMeter() (グローバルな TracerProviderMeterProvider から取得、または初期化時に返す)。
    • テスト: 単体テストは難しいが、後のサービス実装時に呼び出してエラーが出ないことを確認。
    • コミット: day40: step 3/12 Implement common OpenTelemetry initialization package
  4. 共通HTTPクライアントパッケージ実装 (internal/pkg/httpclient/client.go):

    • Otel計装済みの http.Client を作成するファクトリ関数。
      • otelhttp.NewTransport(http.DefaultTransport) を使用。
    • (オプション) WithRetry のようなミドルウェア/ラッパーを追加する準備 (今回は詳細実装はスキップ可、ただし考慮した設計に)。
    • テスト: 同上。
    • コミット: day40: step 4/12 Implement common HTTP client package with Otel
  5. Gateway Service 基本実装とOtel計装:

    • gateway_service/main.go:
      • otel.InitTracerProvider, otel.InitMeterProvider を呼び出し初期化。
      • PrometheusエクスポーターのHTTPハンドラを /metrics に登録。
      • HTTPサーバー (net/http) を otelhttp.NewHandler でラップ (ポート 8080)。
      • /ui エンドポイント:
        • シンプルなHTMLフォーム。「通常リクエスト」「エラー発生リクエスト」「長時間リクエスト」など、異なるシナリオを試せるボタンを配置。
      • /execute-order エンドポイント (引数でシナリオを指定できるようにする):
        • context.WithTimeout を使用してリクエスト全体のタイムアウトを設定。
        • 共通HTTPクライアントを使用して他のサービスを呼び出す。
        • レスポンスをHTMLまたはJSONで返す。
    • ログは log/slog を使用し、リクエストスコープでトレースID/スパンIDを含むようにヘルパー関数を検討 (最初は手動でも可)。
    • テスト:
      • go run ./gateway_service で起動。
      • /metrics アクセス確認。
      • /ui ページ表示と各ボタンの動作確認 (他サービスはまだないのでエラーになるが、ゲートウェイ自体のトレース・メトリクスは確認)。
      • Grafana Tempo/Prometheusでゲートウェイのトレース・メトリクス確認。
    • コミット: day40: step 5/12 Implement Gateway Service with Otel and enhanced UI
  6. Product Service 基本実装とOtel計装:

    • product_service/main.go:
      • otel.InitTracerProvider, otel.InitMeterProvider を呼び出し初期化。
      • PrometheusエクスポーターのHTTPハンドラを /metrics に登録。
      • HTTPサーバーを otelhttp.NewHandler でラップ (ポート 8081)。
      • /products エンドポイント: 固定レスポンス。context.Context を引数に取り、ctx.Done() をチェックするような処理を意識。
    • ログは log/slog
    • テスト:
      • go run ./product_service で起動。
      • /metrics アクセス確認。
      • Grafana Tempo/Prometheusでサービスのトレース・メトリクス確認 (単体リクエスト時)。
    • コミット: day40: step 6/12 Implement Product Service with Otel
  7. Inventory Service 基本実装とOtel計装: (Product Serviceと同様の手順)

    • ポート 8082、エンドポイント /inventory
    • テスト: 同様。
    • コミット: day40: step 7/12 Implement Inventory Service with Otel
  8. Order Service 基本実装とOtel計装: (Product Serviceと同様の手順)

    • ポート 8083、エンドポイント /orders
    • テスト: 同様。
    • コミット: day40: step 8/12 Implement Order Service with Otel
  9. サービス間連携の実装とテスト (トレース中心):

    • gateway_service/execute-order で、各サービス (Product, Inventory, Order) を順次HTTP呼び出し。
      • 各呼び出しに ctx を渡す。
      • エラーハンドリングを実装 (最低限、ログ出力)。リトライは今回はスキップしても良いが、失敗するケースもUIから作れるようにする。
    • テスト (複数サービス起動):
      • 全サービスを起動 (Makefile や docker-compose でGoアプリも管理できるようにすると楽になるが、今回は手動でも可。air などのライブリロードツールを使うと便利)。
      • gateway_service/ui から「通常リクエスト」を実行。
      • Grafana Tempo で分散トレースを確認。リクエストが全サービスを通過し、親子関係が正しく表示されるか。各スパンに service.name 等のリソース属性が正しく付与されているか。
    • コミット: day40: step 9/12 Implement and test service-to-service calls with distributed tracing
  10. メトリクス確認とダッシュボード作成準備:

    • テスト:
      • /ui から複数回リクエストを送信。
      • Grafana Prometheus で各サービスのHTTPメトリクス (http_server_duration_seconds_count, http_server_duration_seconds_bucket など) が収集されていることを確認。
      • PromQLで簡単なクエリ (例: サービス毎のリクエスト数、平均レイテンシ) を試す。
    • 簡単なGrafanaダッシュボードを作成 (手動):
      • リクエスト数、エラーレート、レイテンシ (95パーセンタイルなど) をサービス毎に表示。
    • コミット: day40: step 10/12 Verify metrics collection and prepare for dashboarding
  11. ログ収集とTrace-to-Logs連携確認:

    • 各サービスの log/slog ハンドラで、OtelのトレースIDとスパンIDをログフィールドに含めるように実装 (例: slog.HandlerOptions{ AddSource: true, ReplaceAttr: func(...) } を使ってカスタム属性追加)。
      • otel.GetTraceMetadata(ctx) のようなヘルパーを検討するか、otel/baggageotel/propagation を活用してコンテキスト経由で伝播させる。
    • テスト:
      • /ui からリクエストを実行。
      • Grafana Loki で各サービスのログが収集され、トレースID/スパンIDが含まれているか確認。
      • Grafana Tempoのトレース詳細画面から、関連するログへ正しくジャンプできるか確認 (LokiのderivedFields設定が重要)。
      • Lokiから特定のトレースIDでログを検索できるか確認。
    • コミット: day40: step 11/12 Implement structured logging with trace correlation and test Trace-to-Logs
  12. ドキュメント作成、Makefile/起動スクリプト整備、最終確認:

    • README.md 更新 (アーキテクチャ、セットアップ、起動方法、Otel設定、Grafana確認方法など)。
    • 全サービスを一度に起動・停止するための Makefile またはシェルスクリプト (run_all.sh, stop_all.sh) を作成。
      • 例: run_all: gateway product inventory order
        gateway: ; (cd gateway_service && go run .) (バックグラウンド実行など工夫)
    • .cursor/rules/knowledge.mdc 更新。
    • 不要ファイル削除。
    • テスト: 作成したスクリプトで全サービスを起動・停止できるか。全体を通しての動作確認。
    • コミット: day40: step 12/12 Final documentation, startup scripts, and cleanup

こちらの詳細な手順で進めてよろしいでしょうか? OKであれば、ステップ1から開始します。

riddle_tecriddle_tec

Day40 - GoマイクロサービスとGrafana StackによるOpenTelemetry - 引き継ぎプロンプト

背景

Go言語でマイクロサービス群(Gateway, Product, Inventory, Order)を構築し、OpenTelemetry (Otel) を用いて計装(instrumentation)を行っています。目的は、トレース、メトリクス、ログをローカルのGrafana Stack(Docker Composeで起動するTempo, Loki, Prometheus, Grafana)に送信し、可観測性の概念とツールについて学習することです。

ここまでの進捗

  1. プロジェクト設定: day40_otel_grafana_go ディレクトリを作成し、Next.js関連ファイルを削除。Goモジュール(gateway_service, product_service, inventory_service, order_service, internal/pkg/otel, internal/pkg/httpclient)をセットアップし、go.work で管理するようにしました。
  2. Grafana Stack: docker-compose.yml を用いてGrafana, Loki, Tempo, Prometheus, Promtailを設定し、起動しました。PrometheusはGoサービスのメトリクスを(host.docker.internal経由で)スクレイプし、Promtailはログを収集します。Grafanaのデータソース(Prometheus, Loki, Tempo)はプロビジョニングされ、基本的なLokiからTempoへのトレース連携(derivedFields)も設定済みです。途中で発生した設定の問題も修正しました。
  3. 共通パッケージ:
    • internal/pkg/otel: TracerProvider(OTLP gRPCでlocalhost:4317へ送信)とMeterProvider(Prometheusエクスポーター)を初期化する共通関数を実装しました。グローバルなプロバイダーとプロパゲーター(TraceContext + Baggage)を設定します。
    • internal/pkg/httpclient: Otel計装済みのHTTPクライアント(otelhttp.NewTransportを使用)を作成するファクトリ関数を実装しました。
  4. Goマイクロサービス:
    • 4つのサービスすべてについて、基本的なHTTPサーバー(gatewayは8080, productは8081, inventoryは8082, orderは8083)を実装しました。
    • 各サービスは共通パッケージを使用してOtelを初期化します。
    • 各サービスは/metricsエンドポイントをpromhttp.Handler()経由で公開します。
    • 各サービスはotelhttp.NewHandlerを使用してサーバーサイドのリクエストハンドリングをラップしています。
    • Gateway (/) は、異なるシナリオを実行するボタンを持つシンプルなHTML UIを提供します。
    • Gateway (/execute-order) は、Otel計装済みHTTPクライアントを使用して、Product, Inventory, Orderサービスを順次呼び出します。エラー/遅延シナリオも部分的に実装されています。
    • 全サービスのビルドが完了し、個別に実行可能です。

現在の状況と問題点

Goサービス4つすべてが(ホストマシン上でバックグラウンドで)実行中です。Grafana StackはDocker内で実行中です。
Gateway UI (http://localhost:8080/) から「Execute Normal Order」フローを実行すると、リクエストは成功し、UIには後続サービスからの応答が表示されます。
しかし、Grafana Tempoで確認すると、gateway-service, product-service, inventory-service, order-serviceのトレースが、単一のTrace IDの下で繋がらず、それぞれ独立した別のトレースとして表示されてしまいます。 これは、トレースコンテキストがサービス間で正しく伝播されていないことを示しています。

これを解決するために、以下の修正を試みました:

  • Goサービス内のOtelエンドポイント指定を、ホストからDockerへの通信に適したlocalhost:4317に変更しました。
  • gateway_service/callService内の手動クライアントスパン作成を削除し、otelhttp.Transportによる自動的なコンテキスト伝播に完全に依存するようにしました。

これらの変更にもかかわらず、トレースは依然として分断されたままです。

次のステップ(最優先)

ステップ9の再訪: トレースコンテキスト伝播の修正

  1. 調査: なぜotelhttpがGatewayクライアントと後続サービスサーバー間で自動的にtraceparentヘッダー(または同等のもの)を伝播していないのか原因を特定します。
    • 仮説1: internal/pkg/otel/otel.go 内の otel.SetTextMapPropagator(...) 設定は正しく機能しているか?
    • 仮説2: gateway_service 内でコンテキスト(context.Context)はリクエストチェーン(handleExecuteOrder -> callService -> http.NewRequestWithContext)を通じて正しく渡されており、otelhttp.Transportが入力スパンコンテキストにアクセスできているか?
    • 仮説3: サーバー側(後続サービス)の otelhttp.NewHandler がコンテキストを抽出する際に問題があるか?
  2. デバッグ: gateway_service/callServicehttpClient.Do にログを追加し、送信リクエストヘッダー(traceparentがあるか)を出力します。後続サービスのハンドラ(例: product_service/handleGetProduct)内にもログを追加し、受信リクエストヘッダーを出力します。これでヘッダーが送受信されているか確認できます。
  3. 修正: 調査結果に基づきコードを修正します。コンテキストの適切な使用、プロパゲーター設定の確認、あるいは標準的なメカニズムが壊れているように見える場合はライブラリバージョンの互換性などを確認する必要があるかもしれません。
  4. テスト: 全サービスを再起動し、Gateway UIからリクエストを送信します。Tempoでトレースが単一の繋がったグラフとして(4つのサービスすべてを含む形で)表示されることを確認します。

残りのステップ(トレース修正後)

  • ステップ10: Prometheus/Grafanaでのメトリクス収集を確認し、基本的なダッシュボードを作成します。
  • ステップ11: トレース相関(trace_id, span_idを含む)を持つ構造化ログ(slog)を実装し、GrafanaでのTrace-to-Logs連携をテストします。
  • ステップ12: ドキュメント(README.md, .cursor/rules/knowledge.mdc)を完成させ、起動スクリプト(Makefile)を作成し、クリーンアップを行います。

この情報をもとに、まずはトレースコンテキスト伝播の問題解決に焦点を当てて作業を続けてください。

riddle_tecriddle_tec

trace みれた!!
検索するときに trace id をそのまま入れないとみれないというトラップで無限に時間を溶かした

riddle_tecriddle_tec

素晴らしいです!🎉

スクリーンショットで、TraceQL タブにTrace ID (3f55743d75d3cfd090ba2b1b54002a4c) を直接入力した結果、期待通りに gateway-service, product-service, inventory-service, order-service のすべてのスパンが正しく親子関係を保ち、単一のトレースとして表示されているのが確認できました!

問題の原因は、Grafana Explore画面でのTrace IDの検索方法 (UIの操作方法) にありました。

  • Tempoは正しくトレースデータを収集・保存していました。
  • Goの各サービスにおけるOpenTelemetryの計装やコンテキスト伝播も正しく機能していました。
  • TraceQL タブでTrace IDを直接指定することで、意図したトレースを正確に表示させることができました。

これで、当初の目標であった「トレースコンテキスト伝播の修正」は完了し、トレースがGrafana Tempoで正しく繋がって表示されることが確認できました。

残りのステップ:

これで安心して、プロジェクトの残りのステップに進むことができます。

  • ステップ10: Prometheus/Grafanaでのメトリクス収集を確認し、基本的なダッシュボードを作成します。
    • 各Goサービスは既に /metrics エンドポイントを公開しており、Prometheusはこれらをスクレイプする設定になっているはずです。GrafanaでPrometheusデータソースを使い、基本的なメトリクス(例: HTTPリクエスト数、レイテンシなど)を表示するダッシュボードを作成します。
  • ステップ11: トレース相関(trace_id, span_idを含む)を持つ構造化ログ(slog)を実装し、GrafanaでのTrace-to-Logs連携をテストします。
    • 現在の log.Printf をGoの標準ライブラリである log/slog を使用した構造化ロギングに置き換えます。その際、ログエントリに現在のスパンの trace_idspan_id を含めるようにします。
    • PromtailがこれらのログをLokiに送信し、GrafanaのTempoデータソース設定にある「Trace to logs」機能(1つ前のやり取りでスクリーンショットをいただいた箇所)を適切に設定することで、トレーススパンから関連するログエントリへジャンプできるようにします。
  • ステップ12: ドキュメント(README.md, .cursor/rules/knowledge.mdc)を完成させ、起動スクリプト(Makefile は既にありますね)を調整し、クリーンアップを行います。

素晴らしい進捗です!次のステップ(メトリクス収集の確認とダッシュボード作成)に進みますか?
もし進める場合は、まずGrafanaでPrometheusデータソースを使って、各サービスからどのようなメトリクスが収集できているかを確認するところから始めましょう。例えば、gateway_service_http_requests_total (これは otelhttp が自動的に提供するメトリクス名に近いはずです) のようなメトリクスをGrafanaのExploreでクエリしてみるのが良いでしょう。

riddle_tecriddle_tec

学び

  • OTEL のなんとなくの使い方
  • Grafana Stack(Prometheus / Loki / Tempo / Grafana ) の役割
  • Grafana Stack のなんとなくの設定 (複雑だなあ)
このスクラップは4ヶ月前にクローズされました