Docker環境のNode.js + TypeScriptでDatadogAPMを検証する

前提として、ローカル環境にDockerインストール済み、Datadogのアカウント作成済みの方を対象としています。

# Datadogにて前準備

APM導入に必要なAPIキーとアプリケーションキーを作成します。

## APIキーの取得

Datadogにログインしたらメニューの一番上にある「Go to」を押下します。
検索窓にて"api"と入力して「API Keys」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/57d4cd87-0809-705d-6b47-1f5af3eec4ac.png)

遷移したら右の方にある「+ New Key」というボタンを押下します。

適当な名前を入力してAPIキーを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/88495ede-d4c8-e41b-b9cb-d9f819ea9c5e.png)

## アプリケーションキー

次にアプリケーションキーを作成します。

アプリケーションキーはAPIキーのスコープ(権限)を設定するものです。
デフォルトでは自動的に作成したAPIキーに紐づけられるようです。

先程と同じように「Go to」にて"key"と入力し、検索窓で表示された「Application Keys」を選択します。
このときOrganization SettingsとPersonal Settingsの2つが表示されますが、どちらを選んでも大丈夫です。

遷移後のページにて「+ New Key」ボタンを押下してアプリケーションキーを作成します。
名前はなんでも大丈夫ですが、APIキーと同名にすることをおすすめします。


# ローカル環境構築

次にローカル環境を構築していきます。

## nodeのインストール

macの場合はbrewでインストールできます。

```
brew install node
```

windowsの場合はインストーラーがあるようです。
https://nodejs.org/en/download


## 必要パッケージのインストール

作業用ディレクトリを作成し、移動します。
```
mkdir dd_trace_test
```
```
cd dd_trace_test
```

package.jsonを生成するためにinitします。
```
npm init -y
```

package.jsonが作られたことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/befc119e-4ea0-d7e3-1eb3-8fe15f467abc.png)

必要パッケージをインストールします。
```
npm install -D typescript @types/express express @types/node ts-node nodemon dd-trace
```

package-lock.jsonが生成され、package.jsonの内容も賑やかになれば成功です。
```package.json
{
  "name": "dd_trace_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.11.17",
    "dd-trace": "^5.2.0",
    "express": "^4.18.2",
    "nodemon": "^3.0.3",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}
```

## サンプルコードの追加

srcディレクトリを作成し、その中にserver.tsファイルを追加します。
```
mkdir src
```
```
touch src/server.ts
```

サンプルコードの内容で保存します。

```server.ts
import express from 'express'
const app: express.Express = express()
const port = 3000

app.get('/', (_req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
```

## コンパイル

TypeScriptでコンパイルを行う前に、tsconfig.jsonを生成(初期化)します。
```
npx tsc --init
```

"Created a new tsconfig.json"と表示され、tsconfig.jsonファイルが生成されていれば成功です。
確認できたらコンパイルします。
※コンパイル時にファイル指定や移動はしなくて大丈夫のようです(知らなくて少し詰まりました)
```
npx tsc
```

## サンプルコードの起動

DatadogAPM導入前にDockerを使用せずに一旦素の状態でアプリの動作確認しておきます。

```
nodemon src/server.ts
```

ブラウザで[localhost:3000](http://localhost:3000)でアクセスして"Hello World!"が表示されれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/c4f6c366-64d8-312c-5d26-f1c5c623ee7d.png)

## dockerファイルの用意

dockerファイルを用意します。

dockerディレクトリを作成し、その中にdatadogディレクトリ、nodeディレクトリを作成します。
```
mkdir -p docker/datadog
```
```
mkdir docker/node
```

作成したdatadogディレクトリ、nodeディレクトリにDockerfileを作成します。
内容については下記をお使いください。

DD_API_KEYは冒頭で作成したAPIキーに置き換えてください。

```Dockerfile
# docker/datadog/Dockerfile
FROM gcr.io/datadoghq/agent:latest

ENV DD_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
    DD_SITE=datadoghq.com \
    DD_APM_ENABLED=true
```

```Dockerfile
# docker/node/Dockerfile
# 余分な定義混じってますが勘弁してくださいm(_ _)m
FROM node:18.19.0

ENV DD_AGENT_HOST=datadog

WORKDIR /usr/src/app

RUN npm install -D typescript @types/node \
    && npm install -D @types/express \
    && npm install -D ts-node \
    && npm install nodemon -g

CMD [ "nodemon", "src/server.ts" ]
```

docker-compose.yamlを作成します。
こちらも下記をお使いください。
```docker-compose.yaml
version: "3.7"
services:
  app:
    build: ./docker/node
    ports: 
      - '3000:3000'
    volumes:
      - ./:/usr/src/app

  datadog:
    build: ./docker/datadog
    volumes: 
       - /var/run/docker.sock:/var/run/docker.sock:ro 
       - /proc/:/host/proc/:ro
       - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
```

階層はこんな感じになっていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/bef82d91-1385-c37b-e91a-e4425201dbc3.png)

## APMトレースの定義

src/tracer.tsを作成し、下記のように定義します。
```tracer.ts
import tracer from 'dd-trace';
tracer.init(); // initialized in a different file to avoid hoisting.
export default tracer;
```

src/server.tsの方でtracerをimportします。
```server.ts
import './tracer'; // 追加
import express from 'express'
const app: express.Express = express()
const port = 3000

app.get('/', (_req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
```

一通り修正したらTypeScriptコードのコンパイルをします。
```
npx tsc
```
server.jsの他に新しくtracer.jsがsrcディレクトリに追加されるはずです。


## APMトレースの確認

dockerのイメージビルドをします。
```
docker compose build
```

dockerコンテナを起動します。
```
docker compose up -d
```

ここまでの手順で問題が無ければ自動トレースが送信されているはずです。
Datadogにログインし「Service Mgmt」→「Event Management」へ遷移して確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/89244cc2-c627-c21f-da47-3b4c4d260a4c.png)

docker起動のイベントが表示されていれば成功です。

次はAPMトレースの確認です。
再度[localhost:3000](http://localhost:3000)にアクセスします。

アクセスしたらDatadog管理画面に戻り「APM」→「Traces」へ遷移します。

GETリクエストがトレースされていれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1671245/f395bb78-e74f-d1f4-1e7b-f0b444781b87.png)

以上で終わりです。
お疲れ様でした。

※実はDatadogもNode.jsもTypeScriptも触ったことなかったので、APM導入は骨が折れました…。
※近々AWS ECS上でのAPM導入方法についてまとめたいと思います。

## DatadogAPMトレースが表示されない場合

以下、僕が実際にハマった事象、ハマりそうな事象の対処法です。

### APMメニューにTracesが存在しない
APMで一度もトレース出来ていない場合に発生していました。
一度でもAPMトレースが成功すればAPMメニューに表示されます。
※「Service Mgmt」→「Event Management」で表示されるグラフとは別です(APMでトレースが行えていないと駄目です)

### Tracesに何も表示されない

#### DD_API_KEYにアプリケーションキーを設定してしまっている
僕は最初にやってしまいました。
アプリケーションキーは冒頭でもお伝えした通りAPIキーに対しての認可スコープを設定するものなので、APIキーを設定しましょう。

#### 検索窓に余計な文字列が入力されている
"Search for any tag on your spans"とあるように、アプリ側のdockerイメージLABELにタグを追加することで識別することができます。
```Dockerfile
LABEL com.datadoghq.tags.service="hogehoge"
LABEL com.datadoghq.tags.env="dev"
LABEL com.datadoghq.tags.version="0.1.0"
```

遷移後の検索窓に稼働していないタグが入力されている場合があるため、入力内容を削除することで表示されることがあります。

#### うんともすんともAPMトレースされない
僕の場合かなりハマりました。
既にあるプロジェクトでAPMを導入した時のことですが、docker-compose.yamlのdatadogサービスでnetworksの定義が抜けていたため、同一NW内に存在せずにトレースが出来ないことがありました。
```docker-compose.yaml
version: "3.7"
services:
  app:
    build: ./docker/node
    ports: 
      - '3000:3000'
    volumes:
      - ./:/usr/src/app
    networks:
      - backend

  datadog:
    build: ./docker/datadog
    volumes: 
       - /var/run/docker.sock:/var/run/docker.sock:ro 
       - /proc/:/host/proc/:ro
       - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
    networks: # ここと
      - backend # ここが抜けていた

networks:
  backend:
```