🐷

Datadog Orchestrionで見るGoの自動軽装ツール

2024/12/23に公開

この記事はGo Advent Calendar 2024 22日目の記事です。

https://qiita.com/advent-calendar/2024/go

はじめに

DatadogのGoアプリケーション用の自動インストルメンテーション(自動軽装)ツールであるDatadog Orchestrionが便利であったので、利用法とどういった仕組みでできているかについて記載しました。

Datadog APMを使用するためには

Datadog APMを実行しようとする際、こちらの記載の通り

  1. Agentのinstall
  2. 対象のソースコード上にTraceに必要なコードを挿入する
    • tracerの開始、終了
    • コンテキスト単位で必要なspan

Datadog Orchestrion for Go Application

DatadogのGoアプリケーションのAPM用の自動インストルメンテーション(自動軽装)ツールです。

Orchestrionの利用方法

こちらに記載の通り、以下の手順で利用できます:

  1. orchestrionのコマンドのinstall
  2. orchestrion pinコマンドにてgo.modに必要なパッケージを入れる
  3. orchestrion go buildにてトレース入のビルドを作成

便利なところ

コード分離とカスタマイズの容易さ

  • トレース対象のソースコード自体に関連するコードを追記しないでも、トレースが可能
    • アプリケーション本体のソースコードとトレース関連(Datadog Instrument)のソースコードを分離できる
  • カスタマイズも楽に書ける
    • 自動インストルメンテーションではパッケージ単位にトレース結果が表示される
    • コメントによるカスタマイズが可能
      • カスタムなspanを設けたい場合は本体ソースに//dd:span custom-tag:valueを追加
      • トレース対象から外したい場合は本体ソースに//orchestrion:ignoreを追加
  • 設定変更が可能
    • YAMLファイルの設定を変更すれば、既存のDatadog Instrumentがカバーできない独自のInstrumentを利用できる

パフォーマンスへの影響

オーバーヘッド

  • コンパイル時インストルメンテーションのため、実行時のオーバーヘッドは最小限
  • 通常のDatadog APMと比較して、パフォーマンスへの影響が少ない

ビルド時間への影響

  • 初回ビルド時は若干の時間増加
  • キャッシュ効果により2回目以降は影響が少ない

自動インストルメンテーションを実現させるための技術

orchestrion go buildにて自動インストルメンテーションがbiuldで生成されたファイル上に挿入されるためには

1. コンパイル時インストルメンテーションの基本

「インストルメンテーション」とは、アプリケーションのパフォーマンスや動作を監視・測定するためのコードを追加することです。
実装方法には主に2つのアプローチがあります:

  1. 実行時インストルメンテーション

// 実行時に動的にコードを追加/変更
func ExecuteWithTracing() {
    // 実行時にトレース用のラッパーが動的に生成される
    // → オーバーヘッドが大きい
}
  1. コンパイル時インストルメンテーション(Orchestrionの方式)
// コンパイル時に以下のようなコードが生成される
func ExecuteWithTracing() {
    span := tracer.StartSpan("Execute")
    defer span.Finish()
    // 元の処理
}

Orchestrionはコンパイル時にトレース用のコードを直接埋め込むため:

  • コードは既にバイナリに組み込まれている
  • 実行時の動的な処理が不要
  • その結果、パフォーマンスへの影響が最小限に

2. 処理フローと使用技術

-toolexecの活用

orchestrion go buildが実行された際に、実際にはtoolexecオプションをつけてプロキシとして実行されます。

プロキシとして実行することで、以下の処理フローを実現:

[go build] → [orchestrion] → [実際のコンパイラツール]
  • Goのビルドプロセスをインターセプト
  • コンパイラやリンカの呼び出しを制御
  • ソースコードの変換処理を挿入

AST変換による解析

  • Goのgo/astパッケージを利用したソースコード解析
  • 関数呼び出しの前後にトレースコードを挿入
  • 元のコードの意味を保持しながらの変換処理
// 変換前
func MyFunction() error {
    // 処理
}

// 変換後
func MyFunction() error {
    span := tracer.StartSpan("MyFunction")
    defer span.Finish()
    // 処理
}

コンパイルとリンク処理

compile.goの主要な処理:

// 簡略化した処理フロー
func (p *Proxy) Compile(args []string) error {
    // 1. ソースコードの解析
    // 2. ASTの変換(トレースコードの挿入)
    // 3. 変換されたコードのコンパイル
}
  • AST(抽象構文木)を使用してソースコードを解析
  • トレース用のコードを適切な位置に挿入
  • 変換後のコードをコンパイル

link.goの役割:

// リンク処理の制御
func (p *Proxy) Link(args []string) error {
    // 1. 必要なライブラリのリンク
    // 2. Datadogトレース用のシンボルの解決
    // 3. 最終的な実行ファイルの生成
}
  • トレース用のライブラリを適切にリンク
  • シンボルの解決を行う

分散処理システム(NATS)

大規模プロジェクトの効率的なビルドを実現するため、NATSを採用しています:

  1. 採用理由

    • 高性能な分散メッセージング(低レイテンシー、高スループット)
    • 軽量なPub/Subモデル
    • 優れた信頼性(自動再接続、エラー処理)
  2. システム構成と利用目的

build_flow

  • ビルドプロセスの分散化
  • 並列処理による最適化
  • 効率的なジョブ管理

このように、-toolexec、AST、compile.go、link.go、NATSが連携することで:

  • コンパイル時のコード変換
  • 効率的な分散処理
  • 適切なリンク処理

以上を実現し、効果的なインストルメンテーションを可能にしています。

Discussion