AWS X-Rayの基本とPtyhon + Flask + ECS + FargateでのX-Ray導入手順

AWS X-Rayの機能や料金といった基本的な情報とPtyhon + Flask + ECS + Fargateの構成のアプリケーションにX-Rayを導入する手順を記載しました。

# AWS X-Rayとは
AWS X-Rayは、分散アプリケーションの分析とデバッグを行うためのサービスです。サービスやリソースの関係性の可視化や、AWSサービスをまたぐリクエストをトレースし関連するログの調査やアプリケーションのパフォーマンスの分析などが可能です。

- 参考:
  - <a href="https://aws.amazon.com/jp/xray/">AWS X-Ray（分散アプリケーションの分析とデバッグ）| AWS</a>
  - <a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/aws-xray.html">とは AWS X-Ray - AWS X-Ray</a>

# X-Rayの主な機能

**サービスマップ:**
X-Rayを有効にした範囲のサービスマップが自動で作成されます。サービスマップ上ではノードに対して、異常の有無やそのノードの成功率やエラー率、応答時間の統計が表示されます。

![tracemap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86684/7ed2d04c-6ccd-2e6b-8bf5-b2e6579d8be5.png)

**トレース:**
各リクエストに一意のトレースIDが付与され、リクエストの処理経路をトレースすることができます。トレースにはステータスコードやリクエストの処理経路、リクエストにかかった時間、リクエストに関連するログなどが含まれます。また、SDKを導入したアプリケーション内でトレースに対しアノテーションとメタデータを付与することができデバッグや分析に役立たせることができます。

サンプリングルールのカスタマイズができ、デフォルトでは1秒ごとに最初のリクエストが記録され、それ以降は5%のリクエストが記録されます。この設定はカスタマイズ可能です。

![trace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86684/a83921dc-b857-5b3a-f29e-851af9d9d93f.png)



# X-Rayの対応
X-RayはAWSの各種サービスに対してトレースを行うことができまた、アプリケーションコードに対してもSDKを組み込むことでトレースを行うことができます。

X-Ray SDKが対応している言語:
- Java
- .NET
- Node.js
- Python
- Ruby
- Go


X-Rayとの統合可能なサービス:
- AWS Distro for OpenTelemetry
- API Gateway
- App Mesh
- App Runner
- AWS AppSync
- CloudTrail
- CloudWatch
- AWS Config
- Amazon EC2
- Elastic Beanstalk
- Elastic Load Balancing
- EventBridge
- Lambda
- Amazon SNS
- AWS Step Functions
- Amazon SQS
- Amazon S3

※ CloudWatchやCloudTrail等のトレースを収集以外の統合サービスも記載しています。どのように統合されるかは以下の参考のリンクを参照ください。

- 参考:
  - <a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-services.html">他の AWS X-Ray との統合 AWS のサービス - AWS X-Ray</a>




**X-Ray インサイトと通知:**
しきい値の設定やアプリケーションの変更不要で通常とは異なる動作やパフォーマンスの問題の異常を自動的に検出する機能。通知を有効化することでAmazon EventBridgeを使用してインサイトイベントを配信することができます。デフォルトでは、インサイトは有効化されていません。

- 参考:
    - <a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-console-insights.html">X-Ray インサイトの使用 - AWS X-Ray</a>

# X-Rayの料金
後述の「サンプリングルレートの変更」に詳細は記載していますが、AWS X-Rayではデフォルトですべてのリクエストをトレースするわけではありません。またこのサンプリングのルールを設定できるため規模の大きいアプリケーションでも無料枠に収めることが可能です。

**無料枠**
トレースの記録が 10 万回まで無料（月あたり）  
トレースの取得とスキャンが合わせて 100 万回まで無料（月当り）  

**有料枠**
トレースの記録 5.00USD/100万件　(トレース 1 件あたり 0.000005 USD)  
トレースの取得 0.50USD/100万件　(トレース 1 件あたり 0.0000005 USD)   
トレースのスキャン 0.50USD/100万件　(トレース 1 件あたり 0.0000005 USD)  
X-Ray Insightsトレースの記録 1.00USD/100万件 (トレース 1 件あたり 0.000001USD)  

- 参考:
  - <a href="https://aws.amazon.com/jp/xray/pricing/?nc1=h_ls">料金- AWS X-Ray | AWS</a>

# X-Rayの保存期間
X-Rayは直近30日間のトレースデータを保存します。

> <a href="https://aws.amazon.com/jp/xray/faqs/">よくある質問 - AWS X-Ray | AWS</a>
> 追跡データは、どれだけさかのぼってクエリを実行できますか? X-Ray はトレースデータをどのくらいの期間保存しますか?  
> X-Ray では、直近 30 日間の追跡データが保存されます。したがって、過去 30 日間の追跡データに対してクエリを実行できます。



# Ptyhon + Flask + ECS + FargateでのX-Rayの導入
X-Rayは複数の言語・サービスに対応していますが、ここではPython + FlaskのアプリケーションをECS + Fargateにデプロイした場合のX-Rayの導入手順を記載しています。

## Pytyhon + FlaskでのX-Rayの導入手順
**SDKをpipでインストールする**

参考: <a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-python.html">AWS X-Ray SDK for Python - AWS X-Ray</a>

```bash
pip install aws-xray-sdk
```

**アプリケーションにミドルウェアを追加する (Flask)**
X-Ray SDK for Pythonは、次のミドルウェアをサポートしています。

- Django
- Flask
- Bottle

上記以外のpythonコードでも手動で実装しX-RayをSDKから利用することができます。

参考: <a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-python-middleware.html">X-Ray SDK for Python ミドルウェアを使用して受信リクエストをトレースします。 - AWS X-Ray</a>

Flaskの場合は以下のようにxray_recorderを作成しミドルウェアを追加します。

```python
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.ext.flask.middleware import XRayMiddleware

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

plugins = ('ECSPlugin', )
xray_recorder.configure(
    service='FLASK-XRAY-APP',
    plugins=plugins,
)
XRayMiddleware(app, xray_recorder)
```

`plugins = ('ECSPlugin', )`はECSPluginを有効にするための設定です。ECSPluginはECSのコンテナIDをトレース情報に追加し、ClowdWatch logsとトレースを紐づけるために設定するもので、動作環境に合わせて適切なもの(EKSPlugin, ElasticBeanstalkPluginなど)を指定します。

`service='FLASK-XRAY-APP'`はAmazon X-Rayによって追跡されるアプリケーションやサービスの任意の名前を指定するパラメータです。この名前はX-Rayコンソールで表示されます。


**ダウンストリームコールを実装するためのライブラリへのパッチ適用**
参考: <a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-python-patching.html">ダウンストリームコールを実装するためのライブラリへのパッチ適用 - AWS X-Ray</a>

requestsやsqlite3等pythonのライブラリに対してパッチを適用することで、ダウンストリームコールをトレースすることができます。

```python
from aws_xray_sdk.core import patch_all

patch_all()
```

**put_annotationとput_metadataでトレース情報に追加データを付与する**

- put_annotation: トレースにカスタムキーと値のペアを「注釈」として追加します。この注釈は、検索とフィルタリングが可能で、クエリなどで絞り込みを行いたい情報を持たせる際に便利です。
- put_metadata: トレースのデバッグや詳細な調査用に追加情報を付加します。このメタデータはクエリには使用できませんが、トレースビューで詳細なデータとして確認できます

参考: <a href="https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/basic.html#set-annotation-or-metadata">Basic Usage — aws-xray-sdk 2.14.0 documentation</a>

```python
from aws_xray_sdk.core import xray_recorder

segment = xray_recorder.current_segment()
# value can be string, number or bool
segment.put_annotation('key', value)
# namespace and key must be string and value is an object
# that can be serialized to json
segment.put_metadata('key', json, 'namespace')
```

**ログの出力とX-Rayとの連携**
ログ出力にX-RayのトレースIDを含めることで、X-Rayのトレースに紐づくログをX-Rayコンソール上で確認することができます。

```python
trace_id = xray_recorder.current_segment().trace_id
app.logger.info(f"[ AWS-XRAY-TRACE-ID: {trace_id} ] Request headers: {request_headers_json}")
```

## ECS + FargateでのX-Rayの導入
**Roleの作成**
ECSのタスクの`taskRoleArn`と`executionRoleArn`に指定するロールを作成します。


■ `taskRoleArn`
信頼されたエンティティとしては「AWSのサービス」のユースケースが「Elasitc Container Service」→「Elastic Container Service Task」を選択します。
許可ポリシーはAWS管理の`AWSXRayDaemonWriteAccess`ポリシーをアタッチします。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSXRayDaemonWriteAccess",
            "Effect": "Allow",
            "Action": [
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords",
                "xray:GetSamplingRules",
                "xray:GetSamplingTargets",
                "xray:GetSamplingStatisticSummaries"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```


■ `executionRoleArn`
信頼されたエンティティとしては「AWSのサービス」のユースケースが「Elasitc Container Service」→「Elastic Container Service Task」を選択します。
許可ポリシーはAWS管理の`AmazonECSTaskExecutionRolePolicy`ポリシーをアタッチします。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
```


**X-Rayデーモンをサイドカーとして起動**
FlaskアプリとX-Rayデーモンを同じタスクで起動するために、X-Rayデーモンをサイドカーとして起動します。
`<ecs_task_role_arn>`や`<region>`、またロググループ名等は適宜変更してください。
以下のタスク定義では`flask-xray-app:latest`でFlaskアプリのコンテナイメージを指定しています。`amazon/aws-xray-daemon`がX-Rayデーモンのコンテナイメージです。


```json
{
  "family": "flask-xray-task",
  "taskRoleArn": "<ecs_task_role_arn>",
  "executionRoleArn": "<ecs_task_execution_role_arn>",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "flask-xray-app",
      "image": "<aws_account_id>.dkr.ecr.<region>.amazonaws.com/flask-xray-app:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/flask-xray",
          "awslogs-region": "<region>",
          "awslogs-stream-prefix": "flask-xray-app"
        }
      }
    },
    {
      "name": "xray",
      "image": "amazon/aws-xray-daemon",
      "essential": true,
      "environment": [
        {
          "name": "AWS_REGION",
          "value": "<region>"
        }
      ],
      "portMappings": [
        {
          "containerPort": 2000,
          "hostPort": 0,
          "protocol": "udp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/flask-xray",
          "awslogs-region": "<region>",
          "awslogs-stream-prefix": "xray"
        }
      }
    }
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "256",
  "memory": "512"
}

```

もし何らかの理由によりX-Rayデーモンを別の場所で起動する場合はX-Ray SDK側に環境変数`AWS_XRAY_DAEMON_ADDRESS`でデーモンのアドレス(デフォルトは127.0.0.1:2000)を指定するか

```
xray_recorder.configure(
    daemon_address='127.0.0.1:3000'
```

のようにpythonコード内のxray_recorder.configureに直接設定を書き込む必要があります。

# サンプリングレートの変更
X-Rayのトレースはデフォルトではすべてのリクエストを記録しません。デフォルトでは以下の設定になっています。

- 1秒ごとに以下を記録
    - リザーバのサイズ設定として最初のリクエストが1件記録される
    - リザーバ以降は5%のリクエストが記録される

この設定を変更する場合は`CloudWatch > 設定 > X-Ray トレース - 新規 > サンプリングルール ビュー設定`から設定できます。
サンプリングルールは`API Gateway Entrypoint`, `AWS AppSync`, `X-Ray SDK`

![sampling_rate_setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86684/d754f814-dac6-91fe-8c64-a59c84547dd7.png)



サンプリングルールは以下のように優先度リストになっていて、一致基準に一致した最も優先度の高いルールが適用されます。
初期状態では画像のDefautlのルールのみが設定されています。

![sampling_rate_list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86684/035e184a-46d7-16ee-1e92-d991482e83b7.png)


新規作成する場合は右上の`サンプリングルールの作成`から行います。

![sampling_rate_rule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86684/75673936-06da-af4d-8415-a537e755ce8e.png)


各設定値の詳細は以下の通りです。

- ルールの情報
  - 名前: 任意のルール名
  - 優先度: ルールの優先度。数値が小さいほど優先度が高い
- 制限
  - リザーバのサイズ: 1秒ごとに先頭から指定した値のリクエスト数が記録される
  - 固定レート: リザーバを使い切った後のリクエストの記録割合
- 一致基準
  - サービス名: Amazon API GatewayやX-Ray SDKで設定するサービス名
  - サービスタイプ: トレースマップに表示されるサービスタイプ。
    　- SDKのプラグインとして設定したもの: `AWS::EC2::Instance`, `AWS::ECS::Container`, `AWS::ElasticBeanstalk::Environment`
      - X-Rayを有効化したサービス: `AWS::APIGateway::Stage`, `AWS::AppSync::GraphQLAPI`
  - HTTPメソッド: ルールを適用するHTTP メソッド、GET, POST, PUT, DELETEなど
  - URLパス: リクエストのURL パス
  - リソースARN：ルールを適用する AWS リソースの ARN
  - ホスト: ルールを適用するホスト名。HTTPヘッダーから取得される。
- 一致する属性
  - key-valueのペアで一致する属性を指定する。API Gatewayで元のHTTPリクエストのヘッダーを参照する。X-Ray SDKではサポートされない。

# まとめ
AWS X-Rayの基本とAWS上にECSで動くPython + FlaskのアプリケーションでX-Rayを有効化する手順を紹介しました。X-Rayを有効化することで、アプリケーションのトレースやデバッグが容易になり、アプリケーションのパフォーマンスの分析や問題の特定が行いやすくなります。特にトレースした個別のリクエストに対してCloudwatch Logsと連携することで、リクエストに紐づくログを確認することができるのは非常に便利です。ただし、サンプリングレートの設定やサンプリングルールの設定によっては全てのリクエストがトレースされるわけではないので、適切な設定を行うことが重要です。