これは、なにをしたくて書いたもの?
前にNode.jsでOpenTelemetry(トレースのみ)を試してみました。
Node.jsでOpenTelemetryのトレースを試す - CLOVER🍀
この時、OpenTelemetryをアプリケーションに組み込むためのメタパッケージとしてauto-instrumentations-nodeを使ったのですが、すべての
instrumentationライブラリーが含まれるためサイズが大きいという話があります。
気にならないかもしれませんが、仮に個々に使うとしたらどうなるのか?を今回試してみたいと思います。
OpenTelemetry JavaScript Instrumentationを自分で登録する
今回のお題で見るべきページは、こちらになります。
- Node.js / Instrumentation / Setup
- Using instrumentation libraries / Use Instrumentation Libraries / Registration
メタパッケージauto-instrumentations-nodeを使う時は、以下のようにしてauto-instrumentations-nodeのregisterスクリプトを
Node.jsのオプションで指定します。
$ export NODE_OPTIONS='-r @opentelemetry/auto-instrumentations-node/register'
個々のinstrumentationライブラリーを指定する場合は、こんな感じのコードを作成して同じくNode.jsの-r
(または--require
)オプションで
指定するようです。
/*instrumentation.js*/ const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http"); const { ExpressInstrumentation } = require("@opentelemetry/instrumentation-express"); const sdk = new NodeSDK({ ... instrumentations: [ // Express instrumentation expects HTTP layer to be instrumented new HttpInstrumentation(), new ExpressInstrumentation(), ] });
お題
今回は、こんなお題で試してみたいと思います。
- Redisを用意する
- Expressを使ったアプリケーションを作成し、ioredisを使ってRedisへもアクセスする
- トレースのテレメトリーデータはJaegerに収集する
instrumentationライブラリーの使い方の確認が主テーマなので、今回は簡単な構成にします。
環境
今回の環境は、こちら。
Node.js。
$ node --version v18.17.1 $ npm --version 9.6.7
Redis。
$ bin/redis-server --version Redis server v=7.2.1 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=81a2b5148e5873e4
Redisへは172.17.0.2でアクセスするものとし、redis-user
/password
で使えるユーザーを作成しているものとします。
Jaeger。
$ ./jaeger-all-in-one version 2023/09/17 10:50:43 maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined 2023/09/17 10:50:43 application version: git-commit=2d351c3f30072cae7f5755be20e34c2697b9e3b5, git-version=v1.49.0, build-date=2023-09-07T13:13:08Z {"gitCommit":"2d351c3f30072cae7f5755be20e34c2697b9e3b5","gitVersion":"v1.49.0","buildDate":"2023-09-07T13:13:08Z"}
Jaegerへは、172.17.0.3でアクセスするものとします。
アプリケーションを作成する
それではアプリケーションを作成します。こちらは、TypeScriptで作成しましょう。
Node.jsのプロジェクトの作成。
$ npm init -y $ npm i -D typescript $ npm i -D @types/node@v18 $ npm i -D prettier $ mkdir src
Expressとioredisのインストール。
$ npm i express $ npm i -D @types/express $ npm i ioredis
現時点での依存関係。
"devDependencies": { "@types/express": "^4.17.17", "@types/node": "^18.17.17", "prettier": "^3.0.3", "typescript": "^5.2.2" }, "dependencies": { "express": "^4.18.2", "ioredis": "^5.3.2" }
scripts
。
"scripts": { "build": "tsc --project .", "build:watch": "tsc --project . --watch", "format": "prettier --write src" },
tsconfig.json
{ "compilerOptions": { "target": "esnext", "module": "commonjs", "moduleResolution": "node", "lib": ["esnext"], "baseUrl": "./src", "outDir": "dist", "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "esModuleInterop": true }, "include": [ "src" ] }
.prettierrc.json
{ "singleQuote": true, "printWidth": 120 }
簡単なアプリケーションを作成。
src/app.ts
import express from 'express'; import Redis from 'ioredis'; const app = express(); app.use(express.text()); const redis = new Redis({ host: '172.17.0.2', port: 6379, username: 'redis-user', password: 'password', db: 0, }); app.get('/:id', async (req, res) => { const id = req.params['id']; const data = await redis.get(id); res.contentType('text/plain'); res.send(data); }); app.post('/:id', async (req, res) => { const id = req.params['id']; const body = req.body; await redis.set(id, body); res.contentType('text/plain'); res.send(body); }); const port = 3000; app.listen(port, () => { console.log(`[${new Date().toISOString()}] server startup.`); });
ビルドすると、dist
ディレクトリ内に結果が出力されるので
$ npm run build
起動。
$ node dist/app.js [2023-09-17T11:08:49.591Z] server startup.
動作確認。
$ curl -XPOST -H 'Content-Type: text/plain' localhost:3000/foo -d 'Hello World' Hello World $ curl -H 'Content-Type: text/plain' localhost:3000/foo Hello World
OKですね。これでベースのアプリケーションは作成できました。
Node.jsのOpenTelemetry Instrumentationライブラリーを追加する
続いて、OpenTelemetryのinstrumentationライブラリーを追加しましょう。
auto-instrumentations-nodeに含まれているパッケージは以下に記載されています。
今回は、この中から@opentelemetry/instrumentation-express、@opentelemetry/instrumentation-ioredis、@opentelemetry/instrumentation-httpを
使うことにします。
また、@opentelemetry/sdk-nodeも必要になります。
それぞれインストール。
$ npm i @opentelemetry/sdk-node $ npm i @opentelemetry/instrumentation-express $ npm i @opentelemetry/instrumentation-ioredis $ npm i @opentelemetry/instrumentation-http
依存関係は、こうなりました。
"devDependencies": { "@types/express": "^4.17.17", "@types/node": "^18.17.17", "prettier": "^3.0.3", "typescript": "^5.2.2" }, "dependencies": { "@opentelemetry/instrumentation-express": "^0.33.1", "@opentelemetry/instrumentation-http": "^0.43.0", "@opentelemetry/instrumentation-ioredis": "^0.35.1", "@opentelemetry/sdk-node": "^0.43.0", "express": "^4.18.2", "ioredis": "^5.3.2" }
次に、アプリケーションに組み込むスクリプトを書いていきます。
OpenTelemetryのドキュメントではJavaScriptで直接書くか、TypeScriptで書いてts-nodeで実行という例になっていますが、今回は
TypeScriptで書いてJavaScriptにビルドして実行したいと思います。
以下の2つを見つつ、
- Node.js / Instrumentation / Setup
- Using instrumentation libraries / Use Instrumentation Libraries / Registration
こんなスクリプトを作成。
src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node'; import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis'; const sdk = new NodeSDK({ instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation(), new IORedisInstrumentation()], }); sdk.start();
NodeSDK
のインスタンスを作成し、instrumentation等の設定を行い、最後にNodeSDK#start
します。
ビルド。
$ npm run build
起動。
$ node dist/app.js [2023-09-17T11:08:49.591Z] server startup.
環境変数を設定。
$ export OTEL_TRACES_EXPORTER=otlp $ export OTEL_METRICS_EXPORTER=none $ export OTEL_LOGS_EXPORTER=none $ export OTEL_EXPORTER_OTLP_ENDPOINT=http://172.17.0.3:4318 $ export OTEL_NODE_RESOURCE_DETECTORS='env,host,os,process,container' $ export OTEL_SERVICE_NAME=app $ export NODE_OPTIONS='-r ./dist/instrumentation.js'
NODE_OPTIONS
には、-r
オプションで先程作成したスクリプトのビルド結果を指定します。
$ export NODE_OPTIONS='-r ./dist/instrumentation.js'
起動。
$ node dist/app.js [2023-09-17T12:16:25.041Z] server startup.
また、NODE_OPTIONS
を指定せずに以下のような指定でもOKです。
$ node -r ./dist/instrumentation.js dist/app.js
確認。
$ curl -XPOST -H 'Content-Type: text/plain' localhost:3000/foo -d 'Hello World' Hello World $ curl -H 'Content-Type: text/plain' localhost:3000/foo Hello World
JaegerのWeb UI(http://[Jaegerが動作しているホスト]:16686/
)にアクセスして、確認してみます。
検索すると、確認した時に記録されたトレースが表示されました。
OKそうですね。
というわけで、こんな感じでセットアップのスクリプトを書けばauto-instrumentations-nodeを使わずともOpenTelemetryによるトレースを
組み込むことができました。
ハマったこと
オマケ的に、ハマったことを書いてみます。
組み込み方がわからない
OpenTelemetryのNode.js instrumentationライブライーを組み込む時に、以下を参考にしたわけですが。
- Node.js / Instrumentation / Setup
- Using instrumentation libraries / Use Instrumentation Libraries / Registration
実は、パッケージ個々のページにも組み込み方が書かれています。
- https://github.com/open-telemetry/opentelemetry-js-contrib/tree/instrumentation-express-v0.33.1/plugins/node/opentelemetry-instrumentation-express
- https://github.com/open-telemetry/opentelemetry-js-contrib/tree/instrumentation-ioredis-v0.35.1/plugins/node/opentelemetry-instrumentation-ioredis
- https://github.com/open-telemetry/opentelemetry-js/tree/experimental/v0.43.0/experimental/packages/opentelemetry-instrumentation-http
これらのページには、以下のようにNodeTracerProvider
というものを使う方法が書かれていて、OpenTelemetryのドキュメントと異なるので
どちらが良いのか迷ったのですが。
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express'); const provider = new NodeTracerProvider(); provider.register(); registerInstrumentations({ instrumentations: [ // Express instrumentation expects HTTP layer to be instrumented new HttpInstrumentation(), new ExpressInstrumentation(), ], });
OpenTelemetryのドキュメントの履歴を見ると、NodeTracerProvider
を使っていたものから書き直されていたので、ドキュメントの方が
良さそうですね。
Rework js library instrumentation (#2988) · open-telemetry/opentelemetry.io@6fadff8 · GitHub
しかも、NodeTracerProvider
を使う方で試したらうまく動きませんでした…。
@opentelemetry/instrumentation-httpを追加するのを忘れる
トレース結果を見ているとExpressとioredisがあれば良さそうに思ったので、@opentelemetry/instrumentation-httpを追加するのをやめたら
見事に動かなくなりました…。
@opentelemetry/instrumentation-expressのドキュメントを見ると、@opentelemetry/instrumentation-httpが必要なことは書かれて
いるんですよね。
registerInstrumentations({ instrumentations: [ // Express instrumentation expects HTTP layer to be instrumented new HttpInstrumentation(), new ExpressInstrumentation(), ], });
ローカルモジュールを./
で指定するのを忘れる
最初、スクリプトを-r dist/instrumentation.js
のような指定をしていて
$ node -r dist/instrumentation.js dist/app.js
以下のエラーに悩まされました…。
node:internal/modules/cjs/loader:1080 throw err; ^ Error: Cannot find module 'dist/instrumentation.js' Require stack: - internal/preload at Module._resolveFilename (node:internal/modules/cjs/loader:1077:15) at Module._load (node:internal/modules/cjs/loader:922:27) at internalRequire (node:internal/modules/cjs/loader:174:19) at Module._preloadModules (node:internal/modules/cjs/loader:1433:5) at loadPreloadModules (node:internal/process/pre_execution:598:5) at setupUserModules (node:internal/process/pre_execution:117:3) at prepareExecution (node:internal/process/pre_execution:108:5) at prepareMainThreadExecution (node:internal/process/pre_execution:37:3) at node:internal/main/run_main_module:10:1 { code: 'MODULE_NOT_FOUND', requireStack: [ 'internal/preload' ] } Node.js v18.17.1
自分で作成したファイルを指定する時は、./
を付けるんでした…。
おわりに
OpenTelemetryのNode.jsライブラリーを、auto-instrumentations-nodeを使わずに組み込んでみました。
できることはできるのですが、OpenTelemetryの各種instrumentatationライブラリーやOpenTelemetryのSDKの知識が相応に求められる感じ
みたいなので、素直にauto-instrumentations-nodeを使うのが良さそうに思います。
凝ったことは、もっと慣れてから…。