🐷
Datadog Orchestrionで見るGoの自動軽装ツール
この記事はGo Advent Calendar 2024 22日目の記事です。
はじめに
DatadogのGoアプリケーション用の自動インストルメンテーション(自動軽装)ツールであるDatadog Orchestrionが便利であったので、利用法とどういった仕組みでできているかについて記載しました。
Datadog APMを使用するためには
Datadog APMを実行しようとする際、こちらの記載の通り
- Agentのinstall
- 対象のソースコード上にTraceに必要なコードを挿入する
- tracerの開始、終了
- コンテキスト単位で必要なspan
Datadog Orchestrion for Go Application
DatadogのGoアプリケーションのAPM用の自動インストルメンテーション(自動軽装)ツールです。
Orchestrionの利用方法
こちらに記載の通り、以下の手順で利用できます:
- orchestrionのコマンドのinstall
- orchestrion pinコマンドにてgo.modに必要なパッケージを入れる
- orchestrion go buildにてトレース入のビルドを作成
便利なところ
コード分離とカスタマイズの容易さ
- トレース対象のソースコード自体に関連するコードを追記しないでも、トレースが可能
- アプリケーション本体のソースコードとトレース関連(Datadog Instrument)のソースコードを分離できる
- カスタマイズも楽に書ける
- 自動インストルメンテーションではパッケージ単位にトレース結果が表示される
- コメントによるカスタマイズが可能
- カスタムなspanを設けたい場合は本体ソースに
//dd:span custom-tag:value
を追加 - トレース対象から外したい場合は本体ソースに
//orchestrion:ignore
を追加
- カスタムなspanを設けたい場合は本体ソースに
- 設定変更が可能
- YAMLファイルの設定を変更すれば、既存のDatadog Instrumentがカバーできない独自のInstrumentを利用できる
パフォーマンスへの影響
オーバーヘッド
- コンパイル時インストルメンテーションのため、実行時のオーバーヘッドは最小限
- 通常のDatadog APMと比較して、パフォーマンスへの影響が少ない
ビルド時間への影響
- 初回ビルド時は若干の時間増加
- キャッシュ効果により2回目以降は影響が少ない
自動インストルメンテーションを実現させるための技術
orchestrion go build
にて自動インストルメンテーションがbiuldで生成されたファイル上に挿入されるためには
1. コンパイル時インストルメンテーションの基本
「インストルメンテーション」とは、アプリケーションのパフォーマンスや動作を監視・測定するためのコードを追加することです。
実装方法には主に2つのアプローチがあります:
- 実行時インストルメンテーション
// 実行時に動的にコードを追加/変更
func ExecuteWithTracing() {
// 実行時にトレース用のラッパーが動的に生成される
// → オーバーヘッドが大きい
}
- コンパイル時インストルメンテーション(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を採用しています:
-
採用理由
- 高性能な分散メッセージング(低レイテンシー、高スループット)
- 軽量なPub/Subモデル
- 優れた信頼性(自動再接続、エラー処理)
-
システム構成と利用目的
- ビルドプロセスの分散化
- 並列処理による最適化
- 効率的なジョブ管理
このように、-toolexec、AST、compile.go、link.go、NATSが連携することで:
- コンパイル時のコード変換
- 効率的な分散処理
- 適切なリンク処理
以上を実現し、効果的なインストルメンテーションを可能にしています。
Discussion