Node.jsアプリケーションの計装にOpenTelemetryを導入してNew Relicへ送信してみる
ゲームソリューション部の えがわ です。
OpenTelemetryを活用して、Node.jsアプリケーションを計装し、テレメトリデータをNew Relicに送信してみます。
用語集
計装とかテレメトリデータとか何?という方向けに用語をまとめました。
計装(Instrumentation)とは?
計装とは、アプリケーションの動作状況を監視・計測するために、コードに観測用のロジックを埋め込むプロセスのことです。
従来は各社SDKを利用して実装していましたが、これだと特定ベンダーに依存してしまうという問題がありました。
OpenTelemetryは業界標準として策定され、共通の計装手法を提供することで、将来的なツール変更や拡張を容易にします。
by o3 mini
テレメトリデータとは?
テレメトリデータとは、アプリケーションやシステムの動作状態、パフォーマンス、利用状況、エラー情報などの各種データを自動的に収集し、解析・監視に利用するための情報の総称です。
by o3 mini
アプリケーションを観測できるようにして(計装)、メトリクスとかトレースのような観測情報(テレメトリデータ)を送ろう!っということらしいです。
私はOpenTelemetryについて検索するようになってから知りました。
環境
- Ubuntu 22.04.4 LTS(WSL2)
- Docker
- Node.js (Express)
- MySQL
- New Relic
ローカルで起動したアプリケーションを計装し、テレメトリデータをNew Relicに送信します。
ソースの全般はこちら
インストール
必要パッケージをインストールします。
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/instrumentation-http @opentelemetry/instrumentation-express @opentelemetry/exporter-trace-otlp-grpc @opentelemetry/exporter-metrics-otlp-grpc
計装処理
細かい設定は行わず、自動計装のみ実装しています。
最低限のコードだけ書いているので、もし本番環境等で使用する場合は適切に設定してください。
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import { Resource } from "@opentelemetry/resources";
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: process.env.SERVICE_NAME,
}),
traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }),
}),
instrumentations: [
getNodeAutoInstrumentations({
"@opentelemetry/instrumentation-fs": { enabled: false },
"@opentelemetry/instrumentation-express": { enabled: true },
}),
],
});
sdk.start();
サンプルアプリケーション
シンプルなタスク管理APIを例として、MySQLへの接続やHTTPリクエストまでを実装しています。
index.ts
import './instrumentation';
import express, { Request, Response } from 'express';
import mysql from 'mysql2/promise';
const app = express();
const port = 3000;
app.use(express.json());
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
app.get('/post', async (req: Request, res: Response) => {
const [result] = await pool.query(
`SELECT * FROM todos`
);
res.send({
"todos": result
})
});
app.post('/post', async (req: Request, res: Response) => {
try {
const { title, content } = req.body;
const now = new Date();
const [result] = await pool.query<mysql.ResultSetHeader>(
`INSERT INTO todos (title, content, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP(3))`,
[title, content]
);
const todo = {
id: result.insertId,
title,
content,
status: 'TODO' as const,
created_at: now,
updated_at: now
};
res.status(201).send({ "todo": todo });
} catch (error) {
console.error('Error creating todo:', error);
res.status(500).send({ error: 'Database error' });
}
});
app.delete('/post/:id', async (req: Request, res: Response) => {
const todoId = req.params.id;
const [result] = await pool.query(
'DELETE FROM todos WHERE id = ?',
[todoId]
);
res.status(200).send()
});
app.listen(port, () => {
console.log(`Server running at port:${port}/`);
});
一行目に以下を入れるだけで自動的に計装できます。
import './instrumentation';
Collectorの設定
OpenTelemetryのCollectorをサイドカーとして利用し、テレメトリデータの転送を一元管理します。
Collectorを介することで、データの前処理(フィルタリング、バッチ処理)や転送先の変更が容易になります。
<New Relic API Key>
はINGEST - LICENSE
タイプのAPI Keyを設定してください。
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
exporters:
otlp:
endpoint: https://otlp.nr-data.net:4317
headers:
api-key: <New Relic API Key>
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
Docker Compose構成例
アプリケーション、MySQL、OTEL Collectorを連携させるためのcompose.ymlファイルです。
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
environment:
DB_HOST: db
DB_USER: root
DB_PASSWORD: password
DB_NAME: todo_db
SERVICE_NAME: todo-demo
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
volumes:
- ./src:/usr/src/app/src
- ./nodemon.json:/usr/src/app/nodemon.json
- ./package.json:/usr/src/app/package.json
- ./package-lock.json:/usr/src/app/package-lock.json
ports:
- "3000:3000"
depends_on:
- db
- otel-collector
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: todo_db
ports:
- "3306:3306"
volumes:
- db-data:/var/lib/mysql
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: [ "--config=/etc/otel-collector-config.yml" ]
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- "4317:4317" # OTLP gRPC receiver
volumes:
db-data:
確認
New Relicのダッシュボードでテレメトリデータを確認できます。
APIを複数回呼び出すことで、New Relic上にテレメトリデータが正しく送信されているかを確認できます。
各トランザクションの実行時間を可視化することも可能です。
各トランザクションのトレース情報やSpan情報を確認することで、処理の遅延やボトルネックとなるポイントが明確になります。
たとえば、データベースクエリの実行時間が他の処理と比べて著しく長い場合、SQLの最適化やインデックスの見直しなどの対応が必要になります。
このような解析結果をもとに、パフォーマンス改善を段階的に実施することが、運用中のシステムの信頼性向上につながります。
さいごに
計装にOpenTelemetryを利用することで、特定ベンダーへの依存性を減らし、柔軟で拡張性のあるアプリケーションを構築できます。
New Relicのような可視化サービスと組み合わせることで、システムのパフォーマンスや信頼性を向上させることができます。
この記事がどなたかの参考になれば幸いです。