Amazon ECSで動かしているRailsアプリケーションにAWS X-Rayを導入した話

アプリケーションやシステムの性能管理・監視、いわゆる、アプリケーションパフォーマンス管理として、APMツールを導入しているプロジェクトは多いと思います。最近では、よくDatadogや、NewRelicなどを導入している記事をよく見かけますが、今回は、アプリケーションのパフォーマンス管理のために、Amazon ECSで動かしているRailsアプリケーションにAWS X-Rayを導入したことについて書きます。
# AWS X-Rayとは
概要は以下を参照。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/aws-xray.html

https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_AWS-X-Ray_0228_v1.pdf

# 料金

料金については、[こちら](https://aws.amazon.com/jp/xray/pricing/)に全て書かれているが、要約すると、

- デフォルトでかかる料金
    - トレースの記録数による従量課金
    - トレースの取得数による従量課金
    - トレースのスキャン数による従量課金


- トレースの記録数
    - リクエスト単位で、記録しているトレース数のこと
- トレースの取得数
    - クエリで検索して取得したトレース数のこと
- スキャン数
    - クエリでの検索のためスキャンしたトレース数のこと

- 料金例: 公式から抜粋
```
料金の例
1 時間あたり 2,000 件の受信リクエストを受け取るアプリケーションを使用していて 10% のサンプリングの頻度を使用している場合、料金は以下のように計算されます。

記録されたトレース
1 か月あたりのトレース記録件数 = 1 時間あたり 2,000 リクエスト x 24 時間 x 31 日 x 10% = 148,800 トレース
1 か月あたりの有料トレース記録件数 = 148,800 トレース – 無料利用枠の 100,000 トレース = 48,800 トレース
トレース記録の月額料金 = 48,800 トレース * 0.000005 USD = 0.24 USD
 
さらに、1 日あたり 100 回のクエリを実行し、各クエリでは直近 1 時間のキャプチャされたデータ (200 トレース) をスキャンし、クエリ 1 回につき 50 件のトレースのフルトレースデータを取得すると仮定します。
 
取得およびスキャンされたトレース
1 か月あたりのトレースのスキャン件数 = 100 クエリ x 1 時間あたり 200 トレース x 31 日 = 620,000 トレース
1 か月あたりのトレースの取得件数 = 100 クエリ x 1 クエリあたり 50 トレース x 31 日 = 155,000 トレース
1 か月あたりのトレースの取得/スキャンの合計件数 = 155,000 トレース + 620,000 トレース = 775,000 トレース
1 か月あたりの有料でのトレースの取得/スキャン件数 = 775,000 トレース – 無料利用枠の 1,000,000 トレース = 0 トレース
トレースの取得とスキャンの月額料金 = 0 トレース * 0.0000005 USD = 0 USD
 
加えて、一部またはすべての X-Ray グループで X-Ray Insights を有効にしているとしましょう。

処理された X-Ray Insights トレース
1 か月あたりのトレース記録件数 = 1 時間あたり 2,000 リクエスト x 24 時間 x 31 日 x 10% = 148,800 トレース
1 か月あたりの処理された Insights トレース変更件数 = 148,800 トレース * 0.000001 USD = 0.15 USD

AWS X-Ray の月額利用料金は 0.24 USD + 0.15 USD = 合計 0.39 USD となります
```


# 手順

## アプリ側の設定
- 基本はこの[README通り](https://github.com/aws/aws-xray-sdk-ruby)

- gem

```
gem 'aws-xray-sdk', require: ['aws-xray-sdk/facets/rails/railtie']
gem 'oj', platform: :mri
gem 'jrjackson', platform: :jruby
```

- initializersの設定
```config/initializers/aws_xray.rb
Rails.application.config.xray = {
  # default segment name generated by XRay middleware
  name: "アプリ名-#{ENV.fetch('RAILS_ENV')}", # ←ノード名(x-ray上のサーバー名になるので環境毎に命名を分けてダッシュボードで絞る)
  patch: %I[net_http aws_sdk],
  # record db transactions as subsegments
  active_record: true,
  context_missing: 'LOG_ERROR'
}
```

### ECSの設定
- 基本はこの公式通り
    - https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html
    - コンソールにも大体のやり方の紹介は出ている（Rubyアプリケーションの事例はないのでここからは作れない）
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/86ae9f76-159c-a296-b7e9-e1dabab29f16.png)
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/9a3e5ce4-3c2b-59c7-7f91-4cf0f12a1548.png)
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/9b86c57c-c14e-5b2d-c53a-ef3606e59514.png)

    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/cfb5e992-9312-7326-7064-c4ac28877223.png)

- タスク定義に以下の設定のコンテナを追加
    - cpuの値はもう少し小さくても良さそう

```json
{
    "name": "xray-daemon",
    "image": "amazon/aws-xray-daemon",
    "cpu": 32,
    "memoryReservation": 256,
    "portMappings": [
        {
            "containerPort": 2000,
            "hostPort": 2000,
            "protocol": "udp"
        }
    ],
    "essential": true,
    "environment": [],
    "mountPoints": [],
    "volumesFrom": []
}
```

- タスクロールにX-Rayを動かすための権限を追加
    - ECSにX-Rayの実行権限を追加しないとデータが取れない
- サービスの更新をして反映
- X-Rayデーモンをコンテナで動かした状態で、アプリケーションに何かしらのリクエストを送ると、トレースがスタートし、サービスマップなどが自動で生成される

標準としての設定はこれで完成！！

# インサイト（有料）
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-console-insights.html
- デフォルトグループについて試しに有効化してみた（通知はオフ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/2a82ee1c-e9c8-84fe-59b7-3b7b4678efb1.png)

- 公式抜粋
>X-Ray Insights はアプリケーションパフォーマンスの異常を自動的に検出します。しきい値を手動で設定したり、X-Ray で計測するためにアプリケーションを変更したりする必要はありません。
- 料金はかかるため費用インパクトが出てきたら無効にする（すぐに無効にできる）

# その他追加設定
## サンプリングルール
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-console-sampling.html

以下、公式のサマリ。
- サンプリングルールをカスタマイズすることで、記録するデータの量を制御できる
- デフォルトルールは、1 秒ごとに最初のリクエストを記録し、それ以降のリクエストは 5% ずつ記録
- サービスがリクエストを処理している限り、毎秒少なくとも 1 つのトレースが記録され、5% は、リザーバサイズを超えて追加リクエストがサンプリングされるレート
- デフォルトサンプリングルール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/1ed67bb4-6e72-27b7-7e7d-49c5cab5fe72.png)

- サンプリングルールの追加
    - トレースする割合などをデフォルトから調整したい場合に作成
    -  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/1b39c950-7111-d7da-7728-d3c2ea4b9f44.png)

    - 一致基準が必要な場合は設定（外部APIへのリクエスト含め全部トレースするのであればhostでは絞らない）
        - 正規のリクエストだけに絞ることもあり
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/f810ac36-ceac-5c91-e290-b62afb3b5108.png)


- Rails でのレコーダー設定をしていればコンソールで作成したサンプリングルールが設定される
    - 環境毎にルールの適用を変えたい場合は環境のhost名ごとにサンプリングルールを作成するなどして出し分けできるように考える
    - とりあえずは全環境共通でデフォルトのサンプリングルールでも大きな問題はなさそう

- トレンドでサンプリングルールが適用されているか確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/5ad6c396-9a61-c0d0-90f5-6162966f2453.png)


- デフォルトルールよりも絞る場合は、リザーバーのサイズを0にして、固定レートをたとえば30%とかにすればだいたい全リクエストの30%のみトレースするという感じにできそうで実際トレースもできていた（もし費用を安くしたいなどがあれば）
- 最大を100とかにしてレートを0にするとかもあり（サービスの性質による）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/b1375e91-da9f-6e90-debd-2dfd7b033ec1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/1e412d1a-407a-eec1-164e-7d01f47c205e.png)


## サービスマップ/トレース
- ノード名にはRailsアプリで設定した命名で設定されRailsアプリへのリクエストのパフォーマンスのデータが表示される
- Railsアプリから派生して、databaseや、AmazonSNSなどのAWSリソースへのアクセス、別のhostの外部APIへのアクセスなども全てトレースされる
- https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-console-servicemap.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/15e3f90d-1a44-33fe-7d55-529ee1ea9884.png)

- グループによるサービスマップのフィルタリング
    - マップで表示するリクエストデータをあらかじめ登録しておいた条件となるクエリで絞って表示することができる
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/e31ab0e3-8d7c-a786-9762-912e06143860.png)
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/7abfb3a7-3923-7355-16c3-9a8359d7979a.png)

- トレースの検索
    - status200でないリクエストを検索
    - URL毎に絞ったり、status毎など様々なオプションあり
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2367608/59b2cfc5-fd3f-626f-5059-98c544b90f89.png)

## 環境変数
用意された環境変数を使うとX-Ray SDK for Ruby で書いているアプリの設定ソースコードの値を上書きできる。
- AWS_XRAY_TRACING_NAME
    - SDK がセグメントに使用するサービス名を設定。セグメントはノードの名前のこと

- AWS_XRAY_DAEMON_ADDRESS 
    - X-Ray デーモンリスナーのホストとポートを設定。デフォルトでは、SDK は、127.0.0.1:2000にトレースデータを送信。別のポートでリッスン、または別のホストで実行されている場合に設定するので、今回は特に上書きする必要なし。

- AWS_XRAY_CONTEXT_MISSING
    - 実装されたコードについてセグメントが開いていないときにデータを記録しようとした場合に例外のスローを回避するために設定
        - LOG_ERROR
            - エラーを記録して続行