はじめに
こんにちは。バックエンド/インフラ担当の森井です。
業務でログの改善をしてからというもの、オブザーバビリティにお熱をあげている昨今です。 ログ改善については下記のブログにまとめているのでご興味があればご覧ください。 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のコンソールと統合されていたのですね。
ただし、アナリティクス機能はまだ旧コンソールにしかなかったりと、まだ統合途中という感じがあります。
「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アプリケーションのマルバツゲームで遊ぶことでトレースが記録されていくようです。
マネジメントコンソールでトレースマップを表示します。良い感じです!
トレースも出てます。
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
サーバーアプリケーションの準備
まずは、作業するディレクトリを作成しましょう。
$ 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 「 エージェント」トピックを参照してください。
CloudWatchエージェントをローカル環境で動かしても良いですが、環境が汚れるので、そろそろEC2上で作業した方が楽かもしれません。
EC2インスタンスを起動します。
OSはAmazonLinux 2023、パブリックサブネットを指定しました。
また、AmazonSSMManagedInstanceCore
とAWSXrayWriteOnlyAccess
ポリシーがアタッチされたIAMロールを指定しました。
その他の項目はデフォルトでいきます。
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側の情報が統合できるのかなども気になりました。 今後も色々と研究してみる余地がありそうです。