チュートリアル - Datadog Agent と同じホスト上の Go アプリケーションのトレースを有効にする

概要

このチュートリアルでは、ホスト上にインストールされたサンプル Go アプリケーションでトレースを有効にするための手順を説明します。このシナリオでは、アプリケーションと同じホスト上に Datadog Agent をインストールします。

コンテナ内またはクラウドインフラストラクチャーのアプリケーション、コンテナ内の Agent、異なる言語で書かれたアプリケーションなど、その他のシナリオについては、その他のトレース有効化のチュートリアルを参照してください。

Go の一般的なトレース設定ドキュメントについては、Go アプリケーションのトレースを参照してください。

前提条件

  • Datadog のアカウントと組織の API キー
  • sudo を使用する際にルート権限を持つ物理または仮想の Linux ホスト。このホストには以下の要件があります。
    • Git
    • Curl
    • Go バージョン 1.18+
    • Make と GCC

Agent のインストール

Datadog Agent をマシンにインストールしていない場合は、Integrations > Agent にアクセスし、お使いの OS を選択してください。例えば、ほとんどの Linux プラットフォームでは、<YOUR_API_KEY>Datadog API キーに置き換えて、以下のスクリプトを実行することで Agent をインストールすることができます。

DD_AGENT_MAJOR_VERSION=7 DD_API_KEY=<YOUR_API_KEY> DD_SITE="datadoghq.com" bash -c "$(curl -L https://install.datadoghq.com/scripts/install_script.sh)"

datadoghq.com 以外の Datadog サイトにデータを送信するには、DD_SITE 環境変数を Datadog サイトに置き換えてください。

Events > Explorer を開き、オプションで Datadog ソースファセットでフィルタリングし、ホストへの Agent インストールを確認するイベントを探して、Agent が実行されており、Datadog にデータを送信していることを確認します。

Agent がホストにインストールされたことを示す Datadog からのメッセージを表示するイベントエクスプローラー。
数分後、Datadog にホストが表示されない場合 (Infrastructure > Host map)、Organization Settings > API Keys にある組織の正しい API キーを使用したことを確認してください。

サンプル Go アプリケーションのインストールと実行

次に、トレースするためのサンプルアプリケーションをインストールします。このチュートリアルのコードサンプルは github.com/DataDog/apm-tutorial-golang.git で見ることができます。以下を実行することで git リポジトリの複製を行います。

git clone https://github.com/DataDog/apm-tutorial-golang.git

次のコマンドを使用して、サンプルアプリケーションをビルドします。初めて実行するときは、このコマンドに時間がかかるかもしれません。

make runNotes

サンプルの notes アプリケーションは、インメモリデータベースにデータを保存する基本的な REST API です。curl を使っていくつかの API リクエストを送信します。

curl localhost:8080/notes
まだデータベースに何もないので [] を返します
curl -X POST 'localhost:8080/notes?desc=hello'
ノートに hello という説明と 1 という ID 値を追加します。{"id":1,"description":"hello"} を返します。
curl localhost:8080/notes/1
id の値が 1 であるノートを返します: {"id":1,"description":"hello"}
curl -X POST 'localhost:8080/notes?desc=otherNote'
ノートに otherNote という説明と 2 という ID 値を追加します。{"id":2,"description":"otherNote"} を返します。
curl localhost:8080/notes
データベースの内容を返します: [{"id":1,"description":"hello"},{"id";2,"description":"otherNote"}]

さらに API コールを実行し、アプリケーションのアクションを確認します。終了したら、以下のコマンドを実行して、アプリケーションを終了します。

make exitNotes

Datadog トレーシングのインストール

次に、Go トレーサーをインストールします。apm-tutorial-golang ディレクトリから、以下を実行します。

go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace

トレーシングライブラリが go.mod に追加されたので、トレーシングのサポートを有効にします。

apm-tutorial-golang/cmd/notes/main.go の以下のインポートのコメントを解除します。

cmd/notes/main.go

  sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql"
  chitrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi"
  httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
  "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
  "fmt"

インポートを変更します。

_ "github.com/mattn/go-sqlite3"

to:

"github.com/mattn/go-sqlite3"

main() 関数で、以下の行のコメントを解除します。

cmd/notes/main.go

tracer.Start()
defer tracer.Stop()

cmd/notes/main.go

client = httptrace.WrapClient(client, httptrace.RTWithResourceNamer(func(req *http.Request) string {
        return fmt.Sprintf("%s %s", req.Method, req.URL.Path)
    }))

cmd/notes/main.go

r.Use(chitrace.Middleware(chitrace.WithServiceName("notes")))

setupDB() で、以下の行のコメントを解除します。

cmd/notes/main.go

sqltrace.Register("sqlite3", &sqlite3.SQLiteDriver{}, sqltrace.WithServiceName("db"))
db, err := sqltrace.Open("sqlite3", "file::memory:?cache=shared")

次の行をコメントアウトします。

cmd/notes/main.go

db, err := sql.Open("sqlite3", "file::memory:?cache=shared")

これらの変更を行ったら、以下を実行します。

go mod tidy

Go アプリケーションを起動して自動インスツルメンテーションを探る

トレースの生成と収集を開始するには、make runNotes で再度アプリケーションを起動します。

再びアプリケーションにリクエストを送るには、curl を使用します。

curl localhost:8080/notes
[]
curl -X POST 'localhost:8080/notes?desc=hello'
{"id":1,"description":"hello"}
curl localhost:8080/notes/1
{"id":1,"description":"hello"}
curl localhost:8080/notes
[{"id":1,"description":"hello"}]

しばらく待って、Datadog の UI を見てみてください。APM > Traces に移動します。Traces リストには、次のように表示されます。

Traces ビューには、ホストから入ってくるトレースデータが表示されます。

データベース (db) と notes アプリのエントリがあります。トレースリストには、すべてのスパン、いつ開始したか、どのリソースがスパンで追跡されたか、どれくらいの時間がかかったか、が表示されます。

もし、トレースが表示されない場合は、Traces Search フィールドのフィルターをクリアしてください (使用していない ENV などの環境変数にフィルターをかけている場合があります)。

トレースの検証

Traces ページで、POST /notes トレースをクリックすると、各スパンにかかった時間や、あるスパンが完了する前に他のスパンが発生したことを示すフレームグラフが表示されます。グラフの上部にあるバーは、前の画面で選択したスパンです (この場合、ノートアプリケーションへの最初のエントリポイントです)。

バーの幅は、それが完了するまでにかかった時間を示します。低い深さのバーは、高い深さのバーの寿命の間に完了するスパンを表します。

POST トレースのフレームグラフは次のようになります。

POST トレースのフレームグラフ。

GET /notes トレースは次のようになります。

GET トレースのフレームグラフ。

トレーシングのコンフィギュレーション

トレーシングライブラリは、Datadog に送信するテレメトリーにタグを追加するように構成することができます。タグは、データをグループ化し、フィルターをかけ、ダッシュボードやグラフで有意義に表示するのに役立ちます。タグを追加するためには、アプリケーションを実行する際に環境変数を指定します。プロジェクトの Makefile には、環境変数 DD_ENVDD_SERVICEDD_VERSION が含まれており、これらは統合サービスタグ付けを有効にするように設定されています。

Makefile

run: build
  DD_TRACE_SAMPLE_RATE=1 DD_SERVICE=notes DD_ENV=dev DD_VERSION=0.0.1 ./cmd/notes/notes &
また、Makefile では DD_TRACE_SAMPLE_RATE 環境変数に 1 を設定し、これは 100% のサンプルレートを表しています。100% のサンプルレートは、このチュートリアルの目的のために、ノートサービスに対するすべてのリクエストが Datadog バックエンドに送信され、分析および表示されることを保証します。実際の本番環境や大量生産環境では、これほど高いレートを指定することはないでしょう。アプリケーションのこの変数で高いサンプルレートを設定すると、Agent の構成がオーバーライドされ、非常に大量のデータが Datadog に送信されることになります。ほとんどのユースケースでは、Agent が自動的にサンプリングレートを決定するようにします。

利用可能な構成オプションの詳細については、Go トレーシングライブラリの構成を参照してください。

自動トレーシングライブラリの使用

Datadog には Go 用に完全にサポートされたライブラリがいくつかあり、コードに実装することで自動トレーシングが可能になります。cmd/notes/main.go ファイルでは、go-chisqlhttp ライブラリが対応する Datadog ライブラリ (それぞれ chitracesqltracehttptrace) にエイリアスされているのが確認できます。

main.go

import (
  ...

  sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql"
  chitrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi"
  httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
  ...
)

cmd/notes/main.go では、Datadog ライブラリは WithServiceName オプションで初期化されます。例えば、chitrace ライブラリは以下のように初期化されます。

main.go

r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(chitrace.Middleware(chitrace.WithServiceName("notes")))
r.Mount("/", nr.Register())

chitrace.WithServiceName("notes") を使用すると、ライブラリによってトレースされるすべての要素がサービス名 notes に該当することを保証します。

main.go ファイルには、これら各ライブラリの実装例がより多く含まれています。ライブラリの拡張機能については、Go 互換性要件を参照してください。

カスタムトレースをコンテキストで使用する

コードがサポートされているライブラリに該当しない場合、スパンを手動で作成することができます。

notes/notesController.gomakeSpanMiddleware 関数の周りのコメントを削除してください。この関数は、リクエストを指定された名前のスパンでラップするミドルウェアを生成します。この関数を使用するには、以下の行をコメントアウトしてください。

notes/notesController.go

  r.Get("/notes", nr.GetAllNotes)                // GET /notes
  r.Post("/notes", nr.CreateNote)                // POST /notes
  r.Get("/notes/{noteID}", nr.GetNoteByID)       // GET /notes/123
  r.Put("/notes/{noteID}", nr.UpdateNoteByID)    // PUT /notes/123
  r.Delete("/notes/{noteID}", nr.DeleteNoteByID) // DELETE /notes/123

以下の行の周りのコメントを削除します。

notes/notesController.go

  r.Get("/notes", makeSpanMiddleware("GetAllNotes", nr.GetAllNotes))               // GET /notes
  r.Post("/notes", makeSpanMiddleware("CreateNote", nr.CreateNote))                // POST /notes
  r.Get("/notes/{noteID}", makeSpanMiddleware("GetNote", nr.GetNoteByID))          // GET /notes/123
  r.Put("/notes/{noteID}", makeSpanMiddleware("UpdateNote", nr.UpdateNoteByID))    // PUT /notes/123
  r.Delete("/notes/{noteID}", makeSpanMiddleware("DeleteNote", nr.DeleteNoteByID)) // DELETE /notes/123

また、以下のインポート周りのコメントも削除してください。

notes/notesController.go

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

サンプルアプリケーションには、カスタムトレースの例がいくつかあります。ここでは、さらにいくつかの例を紹介します。これらのスパンを有効にするには、コメントを削除してください。

doLongRunningProcess 関数は、親コンテキストから子スパンを作成します。

notes/notesHelper.go

func doLongRunningProcess(ctx context.Context) {
    childSpan, ctx := tracer.StartSpanFromContext(ctx, "traceMethod1")
    childSpan.SetTag(ext.ResourceName, "NotesHelper.doLongRunningProcess")
    defer childSpan.Finish()

    time.Sleep(300 * time.Millisecond)
    log.Println("Hello from the long running process in Notes")
    privateMethod1(ctx)
}

privateMethod1 関数は、コンテキストから完全に独立したサービスを作成することを示します。

notes/notesHelper.go

func privateMethod1(ctx context.Context) {
    childSpan, _ := tracer.StartSpanFromContext(ctx, "manualSpan1",
        tracer.SpanType("web"),
        tracer.ServiceName("noteshelper"),
    )
    childSpan.SetTag(ext.ResourceName, "privateMethod1")
    defer childSpan.Finish()

    time.Sleep(30 * time.Millisecond)
    log.Println("Hello from the custom privateMethod1 in Notes")
}

以下のインポートのコメントを解除します。

notes/notesHelper.go

  "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
  "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

make runNotes でアプリケーションを起動し、再度 curl コマンドを実行して、先ほど構成したカスタムスパンやトレースを観測します。

curl localhost:8080/notes
[]
curl -X POST 'localhost:8080/notes?desc=hello'
{"id":1,"description":"hello"}
curl localhost:8080/notes/1
{"id":1,"description":"hello"}
curl localhost:8080/notes
[{"id":1,"description":"hello"}]
privteMethod1、doLongRunningProcess のカスタムトレースを表示したフレームグラフ

カスタムトレースの詳細については、Go カスタムインスツルメンテーションを参照してください。

分散型トレースを検証する

単一のアプリケーションをトレースすることは素晴らしいスタートですが、トレースの本当の価値は、リクエストがサービスを通じてどのように流れるかを見ることです。これは、_分散型トレーシング_と呼ばれています。

サンプルプロジェクトには calendar という 2 番目のアプリケーションが含まれており、呼び出されるたびにランダムな日付を返します。ノートアプリケーションの POST エンドポイントには、add_date という名前の 2 つ目のクエリパラメーターがあります。このパラメータが y に設定されると、ノートアプリケーションはカレンダーアプリケーションを呼び出して、ノートに追加する日付を取得します。

カレンダーアプリケーションでトレースを有効にするには、cmd/calendar/main.go の以下の行のコメントを解除します。

cmd/calendar/main.go

  chitrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi"
  "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

cmd/calendar/main.go

  tracer.Start()
  defer tracer.Stop()

cmd/calendar/main.go

  r.Use(chitrace.Middleware(chitrace.WithServiceName("calendar")))
  1. ノートアプリケーションがまだ実行されている場合は、make exitNotes を使用して停止させます。

  2. make run を実行して、サンプルアプリケーションを起動します。

  3. add_date パラメーターを指定して、POST リクエストを送信します。

    curl -X POST 'localhost:8080/notes?desc=hello_again&add_date=y'

  4. トレースエクスプローラーで、この最新の notes トレースをクリックすると、2 つのサービス間の分散型トレーシングが表示されます。

    分散型トレーシングのフレームグラフ。

複数のアプリケーションからのインタラクションを組み合わせたフレームグラフです。

  • 最初のスパンは、ユーザーが送信した POST リクエストで、サポートされている go-chi ライブラリを通じて chi ルーターが処理します。
  • 2 つ目のスパンは、makeSpanMiddleware 関数によって手動でトレースされた createNote 関数です。この関数は、HTTP リクエストのコンテキストからスパンを作成します。
  • 次のスパンは、サポートされている http ライブラリと main.go ファイルで初期化されたクライアントを使用して、ノートアプリケーションから送信されたリクエストです。この GET リクエストは、カレンダーアプリケーションに送信されます。カレンダーアプリケーションのスパンは、別のサービスであるため、青色で表示されます。
  • カレンダーアプリケーションの内部では、go-chi ルーターが GET リクエストを処理し、GetDate 関数は GET リクエストの下にある独自のスパンで手動でトレースされます。
  • 最後に、紫の db 呼び出しは、サポートされている sql ライブラリからの独自のサービスです。GET /Calendar リクエストと同じレベルに表示されますが、これはどちらも親スパンの CreateNote から呼び出されるからです。

トラブルシューティング

もし、期待通りのトレースが受信できない場合は、Go トレーサーのでデバッグモードを設定してください。詳しくはデバッグモードの有効化を読んでください。

その他の参考資料