Rails api （aws fargate利用）を datadog で監視する

以下記事の続きです。

https://qiita.com/ikeyaH/items/cee9ff6f824bbacf2eb9

datadogによる監視実装 part2です。
本記事では、api側で利用しているrailsにdatadogによる監視導入について記載します。

:::note info
本記事では、fargate向けの設定を記載しています。
ローカルのdockerで試してからデプロイしたいという形は、[ローカルサンプル記事](https://qiita.com/ikeyaH/items/338edb2121dbea6b130c)を参照してください。可能であればローカルで試すことを推奨します
:::

:::note warn
**想定実装環境について**
今回は、**AWS ECS + FARGATE** 利用をしている想定の記事になります。
ec2などの場合違う設定になると思いますので、お気をつけください。
参考にはしていただけると思います。
:::

:::note warn
**API KEYについて**
APMを利用する場合、datadogで発行するAPI keyが必要になります。
今回はそれを発行済みである前提になりますので、ない場合は別途用意してください
:::

:::note warn
**aws ecs タスク定義について**
datadog導入にあたり、ecsタスク定義を更新する必要があります。
今回その更新はcli（正確にはgithub actionsからpush）を利用していますので、webなどから実装する際は公式ドキュメントを参照するか、サンプルecsタスク定義を載せますので、そちらをAIに投げ、自分の状況に合わせた対応方法を出力してもらうなどが良い気がします。
:::


datadog apm × aws fargate ドキュメントもちゃんとあります。
さすがですね。

https://docs.datadoghq.com/ja/integrations/ecs_fargate/?tab=webui

# 実行環境（macは開発のみ）
```
ruby
rails
MacOs
```

# 概要
今回はnextjsと違いサーバーサイドの監視をするため、APM（application performance monitoring）を導入します。
これは、利用されているメソッドや発行されているクエリなどを監視できます。
（上記については正直まだ理解半分です。申し訳なし。）

フロントエンドよりは少し複雑ですが、やることはそこまで難しくないはずです。
見ていきましょう。

# 方針
てさぐr導入するのは難しいので、やはり各サービスの連携を想定・理解した上で実装します。
[fargateに導入公式ドキュメント](https://docs.datadoghq.com/ja/integrations/ecs_fargate/?tab=webui) を読むと、以下のように言及されています。
> Datadog で ECS Fargate タスクを監視するには、アプリケーションコンテナと同じタスク定義内のコンテナとして Agent を実行します。Datadog でメトリクスを収集するには、各タスク定義にアプリケーションコンテナのほかに Datadog Agent コンテナを含める必要があります。

上記の通り、fargateを利用する場合はメインアプリケーションのコンテナ（今回でいうrailsアプリケーション）とは別に、datadog agentを動かすコンテナを追加する必要があると述べられています。

このdatadog agentは、誤解を恐れずいうと**datadog と アプリケーションを繋ぐインターフェース**です。railsアプリケーションにももちろん設定は必要ですが、その設定をすれば直接datadogにデータが送られるわけではなく、railsコンテナ -> datadog agentコンテナ -> datadog という形でデータが送信されます。

だから、「各タスク定義にアプリケーションコンテナのほかに Datadog Agent コンテナを含める必要があります」ということですね。

claudが出してくれた簡易図だと以下になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/cff10eb9-d355-4436-acae-f9213fe2173f.png)

このローカルホストも後々重要ですが、今は「タスク定義にdatadog agentのコンテナを含める必要がある」というところをおさえていただければと思います。

※この、メインコンテナではないコンテナのことをサイドカーコンテナというそうです。バイクのサイドカー的なことでしょうか。よりイメージしやすい気がします。

というわけで、**ecsタスク定義にdatadogのサイドカーコンテナを追加し、[railsコンテナ -> datadog agentコンテナ -> datadog] という連携ができる**構成にする方針で進みましょう。

# 実装手順
以下流れです。
1. datadog gemをrailsに追加 & 設定ファイル作成
1. ecs タスク定義を更新し、datadog agentコンテナを追加する

割とこれだけです。多少の躓きポイントをケアしてあげれば大丈夫かなと思います。

### 1. railsの設定
①：datadog gem追加
[ruby向けドキュメント](https://docs.datadoghq.com/ja/tracing/trace_collection/automatic_instrumentation/dd_libraries/ruby/)にある通り、datadog gemを追加します。

```ruby:Gemfile
gem 'datadog', require: 'datadog/auto_instrument'
```
bundle installを実行し、正常にインストールできることを確認して下さい。

※最新はdatadogというgem名ですが、ver1~の頃は `ddtrace`という名前だったようです。
多くの記事ではそれが利用されており、またAIもそれを出力することが多いです。サンプルが多いからだと思います。
しかし、
> このドキュメントは datadog gem v2.x が対象です。ddtrace gem v1.x のドキュメントをお探しの場合は、旧バージョンの Ruby アプリケーションのトレース ドキュメントを参照してください。

とある通り、最新はdatadogという名前であり、`ddtrace`からのアップグレード等の必要が出てくるので注意が必要です。

参考：claud.aiがあたかもddtraceを最新と見せかける
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/9a0d7710-a035-4357-bf2c-795e994edfcf.png)


<details><summary>gem install でエラーが出る場合の1つの可能性</summary>

基本的にgemファイルに記載すべきものは、上記datadogのみで大丈夫です。
もしbundle install時にエラーが出る場合、datadogの依存関係として必要なgemインストールでエラーとなっている可能性があります。

私の場合ですが、以下のようなエラーが出てしまい、最初インストールできませんでした。
```ruby:エラー抜粋
# 一つ目：gemfile.lockがfrozenになっていてダメ
Cannot write a changed lockfile while frozen.
# 二つ目：bootsnapでエラー
 bundler: failed to load command: bootsnap 
(/usr/local/bundle/ruby/3.3.0/bin/bootsnap)
# 三つ目：libdatadog-16.0.1.1.0が見つからない
/usr/local/lib/ruby/3.3.0/bundler/definition.rb:594:in `materialize': 
Could not find libdatadog-16.0.1.1.0 in locally installed gems 
(Bundler::GemNotFound)
```

色々出ていて最初わからず、明示的に`libdatadog-16.0.1.1.0`の追加・bootsnapの削除などを試しましたが、根本違ったようです。

試しにgemfile.lockのfreezを解除してインストールしてみたところ、以下のような差分がgemfile.logに出ました。

```diff
PLATFORMS
  aarch64-linux
- aarch64-linux-gnu
- aarch64-linux-musl
- arm-linux
- arm-linux-gnu
- arm-linux-musl
  arm64-darwin
- x86-linux
- x86-linux-gnu
- x86-linux-musl
- x86_64-darwin
- x86_64-linux
- x86_64-linux-gnu
- x86_64-linux-musl
```

どうやら、PLATFORMSに設定されているアーキテクチャで不要なものが多く、libdatadogのインストールの際もこれらが適用されるため、datadogが用意していないものなどもありインストールできないとなっていたっぽいです。
そのため、事前に依存関係を解決してくれるbootsnapでもエラーが出ており、gemfileのfreezでもエラーが出ていたということでした。

最終的に、ciやfargateで`x86_64-linux`は必要だったのでそれだけPLATFORMに再度追加し、無事インストールできました。

エラーそれぞれが絡み合っているように一見思えますが、本質をちゃんと捉えることは大切だなと思いました。
（最も、最近このプロジェクトに入ったばかりでどうしてこうなっていたのか気になるところではありますが。）

</details>

② 設定ファイルの作成
新しく、datadog向けの設定ファイルを作成します。
```ruby:config/initializers/datadog-tracer.rb（新規作成）
# ※envから取得しているものは、環境ごと設定するとdatadogで絞り込みができるなどのものです。
# ローカルコンテナでのテスト時はハードコーディングでも大丈夫です
Datadog.configure do |c|
  c.env = ENV["DD_ENV"]
  c.service = ENV["DD_SERVICE"]
  c.tracing.sampling.default_rate = (ENV["DD_TRACE_SAMPLE_RATE"] || 1.0).to_f
  c.tracing.instrument(:rails, service_name: ENV["DD_SERVICE"])
  c.appsec.enabled = true
end
```

:::note warn
設定時の注意
fargateに上げる際、datadog agentのhostはローカルホストである必要があります。明示的な設定も可能かと思いますが、ドキュメントの通り指定なしが良いのかなと思っています。
:::

> 注: Fargate APM のアプリケーションでは、DD_AGENT_HOST を設定しないでください。デフォルトの localhost で動作します。

設定値などについては別途調べていただくと良いと思います。

これでrailsの設定は終了です。
configで利用できない値を利用しているなどなければ、datadog agentがなくても特にエラーとはならず動作はしてくれます。

### 2. datadog コンテナの追加
あとは、datadogのコンテナをecsに追加してあげ、タスク定義を更新します。

タスク定義はjsonで管理していたので、そこに追加したもののみ抜粋します。

rails側は、secretsにenv等を追加してあげるのみです。
実際の値はパラメータストアで管理しています。
```json:ecs-task-definition.json railsコンテナーの設定に追加
"secrets": [
    ~略~
    {
      "name": "DD_ENV",
      "valueFrom": "<パラメータストア>/dd_env"
    },
    {
      "name": "DD_SERVICE",
      "valueFrom": "<パラメータストア>/dd_service"
    }
]
```

datadogエージェントは以下のように追加しました。
```json:ecs-task-definition.json datadog agent コンテナ追加
※メインコンテナの下に追加すればOK
"datadog_container": {
    "name": "datadog-agent",
    "image": "public.ecr.aws/datadog/agent:latest",
    "essential": true,
    "environment": [
      {
        "name": "DD_APM_ENABLED",
        "value": "true"
      },
      {
        "name": "DD_APM_NON_LOCAL_TRAFFIC",
        "value": "true"
      },
      {
        "name": "DD_LOGS_ENABLED",
        "value": "true"
      },
      {
        "name": "DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL",
        "value": "true"
      },
      {
        "name": "DD_PROCESS_AGENT_ENABLED",
        "value": "true"
      },
      {
        "name": "DD_DOGSTATSD_NON_LOCAL_TRAFFIC",
        "value": "true"
      },
      {
        "name": "DD_SITE",
        "value": "datadoghq.com"
      },
      {
        "name": "ECS_FARGATE",
        "value": "true"
      }
    ],
    "secrets": [
      {
        "name": "DD_API_KEY",
        "valueFrom": "<パラメータストア>/dd_api_key"
      },
      {
        "name": "DD_ENV",
        "valueFrom": "<パラメータストア>/dd_env"
      },
      {
        "name": "DD_SERVICE",
        "valueFrom": "<パラメータストア>/dd_service"
      },
      {
        "name": "DD_TRACE_SAMPLE_RATE",
        "valueFrom": "<パラメータストア>/dd_trace_sample_rate"
      }
    ],
    "portMappings": [
      {
        "containerPort": 8126,
        "hostPort": 8126,
        "protocol": "tcp"
      },
      {
        "containerPort": 8125,
        "hostPort": 8125,
        "protocol": "udp"
      }
    ],
    "healthCheck": {
      "command": [
        "CMD-SHELL",
        "/opt/datadog-agent/bin/agent/agent health"
      ],
      "interval": 30,
      "timeout": 10,
      "retries": 3,
      "startPeriod": 60
    },
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/dev-schop-webapp/rails/application",
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "rails-datadog"
      }
    }
  }
```

ポイントは、
以下をtrueに設定していることでしょうか。
`DD_APM_NON_LOCAL_TRAFFIC`
`DD_APM_ENABLED`
`ECS_FARGATE`
これらはfargateの場合必須だったと思います。他の値は任意だったりするので、ベースとして利用しつつ、公式ドキュメントを見て設定していくのが良さそうですね。

awsのパラメータストアにapi keyなどがちゃんと設定されていれば、datadogのコンソールからログなどが確認できるはずです。
※反映まで少しディレイがあるかもしれません。

![SCR-20250511-mjai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/168fbad5-9e6b-44f7-83d6-8a16a6c9e1f0.png)


# 最後に
以上で完了です！
うまく動かない場合、ドキュメントやawsのログを注意深くみてみてください。
外部ツールは思ったようにいかないことも多いかもですが、実装できた時はとても嬉しいので、ファイトです。