AWS X-Rayにサクッと入門する

はじめに

こんにちは。バックエンド/インフラ担当の森井です。

業務でログの改善をしてからというもの、オブザーバビリティにお熱をあげている昨今です。 ログ改善については下記のブログにまとめているのでご興味があればご覧ください。 engineers.fenrir-inc.com

さて、オブザーバビリティやっていくぞという気持ちが高まっているなか、やはりトレース、そして特にAWS環境においてはX-Rayを避けては通れないだろうということで、サクッと試してみました。

X-Rayとは

X-Rayとはどのようなサービスでしょうか。 公式ドキュメントの説明を引用します。

AWS X-Ray は、アプリケーションが処理するリクエストに関するデータを収集し、そのデータを表示、フィルタリング、分析して問題や最適化の機会を特定するためのツールを提供するサービスです。アプリケーションへのトレースされたリクエストについては、リクエストと応答だけでなく、アプリケーションがダウンストリームの AWS リソース、マイクロサービス、データベース、Web API に対して行う呼び出しに関する詳細情報も確認できます。(※ Google翻訳)

What is AWS X-Ray? - AWS X-Ray

ちょっと難しいので、私なりの言葉で要約してみると、

  • アプリケーションが処理するリクエストに関するデータ(開始終了時刻、ステータスコードなど)を収集します
  • アプリケーションの依存先のデータも収集して1つのリクエスト単位に結合し、処理がどのように流れていったのかを可視化、分析できるようにします

ということかなと思います。

もしかすると、「それならログに出力すればよいのでは」と思われる方もいるかもしれません。 たしかに単一のアプリケーションだけを対象とするならば、ログにさまざまな情報を出しておいて分析していけば良いでしょう。 しかし、他のシステムやサービスへの依存関係を持っている場合、ログを統合して分析していくのは至難の技です。 全ての依存先とログのインターフェースを定めたりしておく必要があるため現実的ではないでしょう。

トレースを導入することにより複数の依存先を跨いだデバッグがしやすくなるだけでなく、予防的なアプローチも可能になるのが良いところだと思います。
特にX-RayはAWSネイティブな形で提供されているのが嬉しいポイントでしょうか。

詳しく知りたい方はAWS Black Beltもご参照ください。 youtu.be

サンプルアプリケーションでサクッと試す

難しいことはさておき、何事も触ってみるのが大事です。
早速試すぞとコンソールを開いたら、CloudWatchの画面に遷移して驚きました。 いつの間にかCloudWatchのコンソールと統合されていたのですね。

X-Ray コンソールは、再設計され CloudWatch と統合して使いやすくなりました。

ただし、アナリティクス機能はまだ旧コンソールにしかなかったりと、まだ統合途中という感じがあります。

トレースマップがないときの画面

「CloudFormationを使用してサンプルアプリケーションを作成」を押します。

ちなみに、テンプレートのURLはhttps://s3.amazonaws.com/aws-xray-assets.us-west-2/samples/aws-xray-scorekeep-app.yamlでした。
ドキュメントは、Scorekeep サンプルアプリケーションの開始方法 - AWS X-Rayにあるので、ここを見ながら進めていくのが良さそうです。

CloudFormationスタックが作成完了すると、X-Rayコンソールに表示されました。 純粋にX-Rayコンソールの操作を知りたい方は、このサンプルアプリケーションを起動するのが良さそうです。

scorekeep スタックページの [出力] タブを選択し、LoadBalancerUrl URL リンクを選択してウェブアプリケーションを開きます。

scorekeepアプリケーションのマルバツゲームで遊ぶことでトレースが記録されていくようです。

ScoreKeep

マネジメントコンソールでトレースマップを表示します。良い感じです!

トレースも出てます。

X-Rayコンソールの使い方が知りたい場合はここで練習するのがよさそうですね。

自分のアプリケーションにX-Rayを組み込む

ここまででX-Rayコンソールの使い方は把握できましたが、どうやって自分のアプリケーションにトレースを組み込んだら良いのかも知りたいところです。

まずは、何を使ってX-Rayにトレース情報を送るかを決めないといけません。 公式ドキュメントのフローチャートによると、SDKがサポートされていない言語ならX-Ray API、サポートされている言語でシンプルに使いたいならX-Ray SDK、OpenTelemetry標準で使いたいならADOT SDKが良いようです。
私の愛用するGo言語は幸いにもX-Ray SDKに対応していましたので、こちらを利用したいと思います。 OpenTelemetryも盛り上がっていますし、OpenTelemetry準拠のライブラリとADOT SDKはできることも広がりそうです。今後はOpenTelemetryを使用した自動計装も検証してみたいところですね。

  • X-Ray API
  • X-Ray SDK
  • ADOT SDK

インターフェイスの選択 - AWS X-Ray

サーバーアプリケーションの準備

まずは、作業するディレクトリを作成しましょう。

$ mkdir go-x-ray

Go Modulesを初期化します。
今回は使い捨てなので適当にgo-x-rayというモジュールパスを指定していますが、本来はリポジトリを指定します。

$ cd go-x-ray
$ go mod init go-x-ray
go: creating new go.mod: module go-x-ray

適当にサーバーアプリケーションを書きましょう。

$ touch main.go
package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    _, err := w.Write([]byte("hello!\n"))
    if err != nil {
        panic(err)
    }
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    fmt.Println("Server start!")
    http.ListenAndServe("localhost:8080", nil)
}

サーバーアプリケーションを起動してcURLでリクエストを送ると、無事動作することが確認できました。

$ go run main.go                                                              
Server start!
$ curl localhost:8080/hello
hello!

さて、動くことが確認できたので満を持してX-Ray SDKを導入してみます。

$ go get -u github.com/aws/aws-xray-sdk-go/... 

適当にAWSにGETリクエストを送ってみる処理を追加し、その情報をX-Rayに送信してみましょう。

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/aws/aws-xray-sdk-go/xray"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    // セグメント開始
    _, seg := xray.BeginSegment(context.Background(), "api-request")
    resp, err := http.Get("http://aws.amazon.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    // セグメント終了
    seg.Close(nil)

    _, err = w.Write([]byte("hello!\n"))
    if err != nil {
        panic(err)
    }
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    fmt.Println("Server start!")
    http.ListenAndServe("localhost:8080", nil)
}

動いているっぽいです。 どうやらデフォルトの2000番ポートでX-Rayデーモンにトレースを送信しているようですね。

$ go run main.go
Server start!
2024-09-14T14:15:16+09:00 [INFO] X-Ray proxy using address : 127.0.0.1:2000
2024-09-14T14:15:16+09:00 [INFO] Emitter using address: 127.0.0.1:2000

ということでX-Rayデーモンを起動しなければと思いドキュメントを確認したところ、 なんと現在はCloudWatchエージェントで代替してくれるようです。 管理するデーモンが減って嬉しいですね。

CloudWatch エージェントを使用して、Amazon EC2インスタンスおよびオンプレミスサーバーからメトリクス、ログ、トレースを収集できるようになりました。 CloudWatch エージェントバージョン 1.300025.0 以降ではSDKs、 OpenTelemetryまたは X-Ray クライアント からトレースを収集して X-Ray に送信できます。Distro for OpenTelemetry (ADOT) Collector AWS または X-Ray デーモンの代わりに CloudWatch エージェントを使用してトレースを収集することで、管理するエージェントの数を減らすことができます。詳細については、「 CloudWatch ユーザーガイド」のCloudWatch 「 エージェント」トピックを参照してください。

AWS X-Ray デーモン - AWS X-Ray

CloudWatchエージェントをローカル環境で動かしても良いですが、環境が汚れるので、そろそろEC2上で作業した方が楽かもしれません。

EC2インスタンスを起動します。 OSはAmazonLinux 2023、パブリックサブネットを指定しました。 また、AmazonSSMManagedInstanceCoreAWSXrayWriteOnlyAccessポリシーがアタッチされたIAMロールを指定しました。 その他の項目はデフォルトでいきます。

EC2の起動画面

EC2が起動したらログインして、CloudWatch Agentを起動して設定します。

$ sudo yum install amazon-cloudwatch-agent
$ sudo vim /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
{
    "agent": {
        "metrics_collection_interval": 60,
        "run_as_user": "root"
    },
    "traces": {
        "traces_collected": {
            "xray": {}
        }
    }
}
$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json

EC2側の準備が整ったので、サーバーアプリケーションをビルドします。

env GOOS=linux GOARCH=amd64 go build -o go-x-ray .

バイナリのサイズを確認すると意外にもバイナリサイズが24MBにもなってしまいました。
試しにX-Ray SDKをコメントアウトしてビルドし直すと7.4MBに減ったので、X-Ray SDKだけで16.6MB分のようです。 もしかすると、バイナリサイズを小さくしておきたい環境では導入しずらいこともあるかもしれません。

EC2にビルドしたバイナリを送信した後、実行します。

$ ./go-x-ray

別のシェルからリクエストを送ります。

$ curl localhost:8080/hello
hello!

ログがでました。

$ ./go-x-ray
Server start!
2024-09-15T00:12:25Z [INFO] X-Ray proxy using address : 127.0.0.1:2000
2024-09-15T00:12:25Z [INFO] Successfully fetched sampling rules
2024-09-15T00:12:25Z [INFO] Emitter using address: 127.0.0.1:2000

ではX-Rayコンソールを開いてみましょう。
無事トレースが記録されているようです!

トレースマップ

クライアントからapi-requestに直接線が伸びてしまっているので、本来はxray.Handlerを使用して受信リクエストをトレースしないといけなかったようです。 X-Ray SDK for Goを使用して受信する HTTP リクエストの計測を行う - AWS X-Ray

まあ今回はサクッと入門するのが目的なので良しとしましょう。

おわりに

今回はX-Rayに入門してみました。 事前に想像していたよりも、コードがX-Ray SDKに依存してしまうため導入時にはよく検討したほうが良いと思いました。一方、OpenTelemetryを使用して実装していくと別環境への可搬性を得られる代わりに学習コストが増えてしまいそうです。

他にも、CloudWatch Logsに出力したトレースIDとX-Ray側の情報が統合できるのかなども気になりました。 今後も色々と研究してみる余地がありそうです。