Code Engineで Node.jsサーバーを試して OpenTelemetryで Instanaにトレースを送るメモ

# はじめに
この投稿では、 [Code Engine]("https://www.ibm.com/jp-ja/cloud/code-engine")を使ってアプリケーションの開発・運用する際のモニタリングツールとして、
[OpenTelemetry]("https://opentelemetry.io/") と APMツールの[Instana]("https://www.ibm.com/jp-ja/products/instana")でアプリケーションの性能情報(呼び出し数、エラー数、遅延）を可視化する方法を試した時のメモになります。

# OpenTelemetryとは
[OpenTelemetry]("https://opentelemetry.io/")は、テレメトリデータ(メトリクス、ログ、およびトレース)を計測、生成、収集、そしてエクスポートを行うOSSのオブザーバビリティのフレームワークです。 OpenTelemetryはテレメトリデータを分析する機能がありません。ですので、今回は、Instanaにテレメトリデータを送信して表示・分析することにします。

# 前提作業
 [IBM Cloud Advent Calendar 2022]("https://qiita.com/advent-calendar/2022/ibmcloud")[9日目]("https://www.1ft-seabass.jp/memo/2022/12/09/code-engine-nodejs-vue-vite-try/")の @tseigo さんの記事で作成したNode.jsサーバで動くアプリケーションに、[OpenTelemetry]("https://opentelemetry.io/")で Instanaにトレースを送る機能を追加します。

https://www.1ft-seabass.jp/memo/2022/12/09/code-engine-nodejs-vue-vite-try/

事前に、 [Code Engine で Node.js サーバーを試して Vue + Vite の連携までやってみるメモ]("https://www.1ft-seabass.jp/memo/2022/12/09/code-engine-nodejs-vue-vite-try/") の手順でCode Engineでアプリが起動している状態まで準備してください。

具体的には、```Express を動くようにする``` の手順で 
- npmでExpressをインストール
- public フォルダを作って public/index.html を作成
- 変更したソースコードをコミット
- 再度 Code Engine 上で新しいアプリケーションを作成
の手順を実行すると以下の画像のようにアプリケーションのビルドとデプロイが開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/6f4f229d-a5f9-318f-c55f-78d9f26b35cc.png)

アプリケーションのデプロイが完了後、上記画面の```アプリケーションのテスト``` 、 ```アプリケーションのURL``` の順番にクリックすると下記画像のように ```Index``` という文字がブラウザに表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/dce64306-af4c-5323-89fc-a0a9a5f0fdb0.png)

:::note info
もし、Code Engineに不慣れな場合は、[数分でアプリがデプロイできるコンテナ環境！IBM Cloud Code Engine]("https://qiita.com/satokota/items/b3cecd591e244fed391f") で素振りすると良いでしょう。
:::

# OpenTelemetryを有効にするための手順
Instanaでは、OpenTelemetryでモニターされたアプリケーションのテレメトリデータ(メトリクス、ログ、およびトレース)をホスト・エージェントを介して、テレメトリーをバックエンドサーバに送信します。(下図エージェントタイプ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/2f7475eb-a764-ab3c-035e-fe3038fdfd8c.png)

しかし、Code Engineのようなサーバーレス環境(エージェントが存在しない環境）で実行されている場合は、[Instana OpenTelemetry Exporter]("https://www.ibm.com/docs/ja/instana-observability/current?topic=nodejs-instana-api#opentelemetry-exporter")を使用して、エージェントを介さずに直接、バックエンドサーバに送信する仕組みになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/bc7d4ee1-29f4-82f9-169a-69cdf4cedec6.png)
※ AWSのLambda、Fargate、Azure FunctionsなどのServerlessも同じ仕組みで利用します。

具体的には、以下の作業が必要になります。
- Dockerfileの編集
- パッケージのインストール
- Instana OpenTelemetry Exporter(tracer.js)の設定
- Instanaの環境変数を定義
    - INSTANA_AGENT_KEY
    - INSTANA_ENDPOINT_URL
- アプリケーションのビルド＆デプロイ

それでは、順番に設定を行っていきましょう。

## Dockerfile
Dockerfileを下記のように書き換えます。
 
```dockerfile
FROM icr.io/codeengine/node:12-alpine
COPY package.json .
RUN npm install
RUN mkdir public
COPY public/ public/
COPY server.js .
COPY tracer.js .
EXPOSE 8080
CMD [ "node", "--require", "./tracer.js", "server.js" ]
```
変更した内容は、以下の２つに
- (追加) ```COPY tracer.js .```
    - トレースモジュールの tracer.jsを追加すること
- (変更) ```CMD [ "node", "--require", "./tracer.js", "server.js" ]```
    - node起動時に、トレースモジュール ```tracer.js``` を読み込んで起動

:::note info
OpenTelemeryでサポートしているNode.jsのバージョンは14以上になってます。
Support一覧: https://github.com/open-telemetry/opentelemetry-js#supported-runtimes
Dockerfileのコンテナイメージが ```node:12-alpine```　と古いバージョンですが、今回は、動作したのでそのまま使用しています。
:::
    
## パッケージのインストール
以下のOpenTelemetryと InstanaのOpenTeremetry Exporterをインストールします。

- OpenTelemetry
    - @opentelemetry/api
    - @opentelemetry/sdk-node
    - @opentelemetry/auto-instrumentations-node
    - @opentelemetry/resources
    - @opentelemetry/semantic-conventions 
- Instana
    - @instana/opentelemetry-exporter

npmコマンドでインストールを実行します。
```console
$ npm i @opentelemetry/sdk-node @opentelemetry/api \
@opentelemetry/auto-instrumentations-node @opentelemetry/resources \
@opentelemetry/semantic-conventions @instana/opentelemetry-exporter
```

インストールが完了すると ```package.json``` の ```dependencies``` に OpenTelemetryとInstanaのパッケージが出力されます。

```json
  "dependencies": {
    "@instana/opentelemetry-exporter": "^2.13.1",
    "@opentelemetry/api": "^1.3.0",
    "@opentelemetry/auto-instrumentations-node": "^0.35.0",
    "@opentelemetry/resources": "^1.8.0",
    "@opentelemetry/sdk-node": "^0.34.0",
    "@opentelemetry/semantic-conventions": "^1.8.0",
    "express": "^4.18.2"
  }
```

## Instana OpenTelemetry Exporter(tracer.js)の設定

Instana OpenTelemetry Exporter(tracer.js)は、Node.jsのアプリケーションが実行すると OpenTelemetryでテレメトリデータを生成して、そしてInstana Exporterが　OpenTelemetry Spanを Instana Spanに変換して、 Instanaのバックエンドサーバーに送信します。

下記のソースコードが、Instana OpenTelemetry Exporter(tracer.js)のソースコードで[Instanaドキュメント]("https://www.ibm.com/docs/ja/instana-observability/current?topic=nodejs-instana-api#opentelemetry-exporter")からコピー＆ペーストすることが可能です。

- <b>tracer.js</b>
```js
'use strict';

const process = require('process');
require('@opentelemetry/api');
const opentelemetry = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

// Import the Instana OpenTelemetry Exporter
const { InstanaExporter } = require('@instana/opentelemetry-exporter');

// Instantiate the Instana Exporter.
// Make sure to provide the proper agent key and backend endpoint URL.
// You can provide the agent key and backend endpoint URL via the following environment variables:
// * INSTANA_AGENT_KEY
// * INSTANA_ENDPOINT_URL
//
// Alternatively, you can pass these values as an argument to the constructor (see bellow), although it is strongly
// recommended that such sensitive data is not hard coded in the code base.
// Eg: const instanaTraceExporter = new InstanaExporter({ agentKey: 'agent_key', endpointUrl: 'endpoint_url' });
const instanaTraceExporter = new InstanaExporter();

const nodeAutoInstrumentations = getNodeAutoInstrumentations();

const sdk = new opentelemetry.NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'サービス名'
  }),
  // Configure OpenTelemetry to use the Instana Exporter
  traceExporter: instanaTraceExporter,
  instrumentations: [nodeAutoInstrumentations]
});

sdk
  .start()
  .then(() => console.log('Tracing initialized'))
  .catch(error => console.log('Error initializing tracing', error));

process.on('SIGTERM', () => {
  sdk
    .shutdown()
    .then(() => console.log('Tracing terminated'))
    .catch(error => console.log('Error terminating tracing', error))
    .finally(() => process.exit(0));
});
```

上記、Instana OpenTelemetry Exporter(tracer.js)のソースコードの中で、

``` [SemanticResourceAttributes.SERVICE_NAME]: 'サービス名' ``` のサービス名を以下の様にサービス名を定義します。
``` [SemanticResourceAttributes.SERVICE_NAME]: 'CodeEngineApps' ``` 


## Instanaの環境変数を定義
Instanaのバックエンドサーバに接続するために必要な ```エンドポイントのURL (INSTANA_ENDPOINT_URL)``` と　```Agent Key(INSTANA_AGENT_KEY)``` の環境変数を作成します。
  
Code Engineのプロジェクト、アプリケーションの画面を開き、 ```①構成``` 、```②環境変数``` 、```③編集``` して新規リビジョンを作成を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/60670714-1db7-9cee-2d8b-871f36f63133.png)

環境変数の追加をクリックして、 下記赤枠の様に ```INSTANA_ENDPOINT_URL``` 、 と ``` INSTANA_AGENT_KEY ``` の環境変数を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/3a6ed371-e2e7-0c17-d398-d6dae257d095.png)

上記、環境変数の追加をする際は、定義タイプ```リテラル値```を選択して、環境変数を作成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/b15287a4-1016-bc62-af24-a1979e8f2744.png)

## アプリケーションのビルド＆デプロイ
前の手順で、環境変数を作成した後、```①コード``` タブの ```②ビルドの再実行``` をクリックして、ビルドを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/cfe63ee2-3c70-b109-824e-cc85c6a590ca.png)

そして、上記画像の ```③保存して作成```を押すことで、下記画像の様に、ビルド完了後、デプロイが実施されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/69e448ad-d642-5e8e-de85-af886e57772b.png)

アプリケーションのデプロイが完了後、上記画面の```アプリケーションのテスト``` 、 ```アプリケーションのURL``` の順番にクリックすると下記画像のように ```Index``` という文字がブラウザに表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/dce64306-af4c-5323-89fc-a0a9a5f0fdb0.png)

ここまでの作業で、Node.jsのアプリケーションを OpenTelemetryでモニターしたテレメトリデータをInstanaのバックエンドサーバに送る設定は完了しました。
この後は、Node.jsのアプリケーションに負荷をかけて、Instanaのバックエンドサーバの画面で、テレメトリデータを確認します。


# Node.jsのアプリケーションに負荷生成
アプリケーションのアクセスが発生しないとテレメトリデータは生成されません。
今回は、シンプルにcurlを使って、連続的に負荷を生成させて、次の手順のInstanaのバックエンドサーバでの画面確認を行います。

```shell
while true
do
    curl --silent https://application-21.xxx.jp-tok.codeengine.appdomain.cloud/index.html
    sleep 1
done
```

# Instanaバックエンドサーバでの画面確認
これまでの手順が正常で、Node.jsのアプリケーションに負荷を発生させると、
Instanaのアプリケーションの画面では、Exporter設定(tracer.js)で定義したサービス名```CodeEndgineApps```が検出され、呼び出し数、待ち時間、エラーのある呼び出し率が下記画像のように表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/0c9fc401-01ae-e7d1-3715-9187ef5faa97.png)

以降、InstanaでOpenTelemetryのテレメトリデータをどのように表示するのかスクリーンショットを貼っておきます。

## アプリケーション画面 呼び出し、エラーのある呼び出し率、待ち時間、処理時間などが表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/be1377b6-7ecb-c3ab-0425-4312bcffa665.png)

## トレースの詳細(キャプチャーされたトレースのリスト）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/f1068298-0882-f166-47ba-8762ecdada06.png)

## OpenTelemetryで収集したスパン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/3dd2ddfc-e6a9-4818-d5ee-46280fba5add.png)

## 個々のトレースの詳細
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/ac89fd8b-4e5f-6ccb-0680-514c6144b37d.png)

# 最後に
この投稿ではCode Endingeで動作するNode.jsのアプリケーションをOpenTelemetryでモニターして、Instanaのバックエンドサーバの画面で、テレメトリデータを可視化する事を確認しました。

今回は、簡単に、OpenTelemetry & Instanaの動作確認するために、サービスは１つ（Node.jsサーバ１つ）で行いましたが、
DBサーバなど、複数のサービスとの連携も行うことができます。

この投稿をきっかけにぜひ、お試しください。