Node.jsアプリケーションの計装にOpenTelemetryを導入してNew Relicへ送信してみる

Node.jsアプリケーションの計装にOpenTelemetryを導入してNew Relicへ送信してみる

Clock Icon2025.02.03

ゲームソリューション部の えがわ です。

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に送信します。

ソースの全般はこちら
https://github.com/Remin18/nodejs-express-otel

インストール

必要パッケージをインストールします。

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

計装処理

細かい設定は行わず、自動計装のみ実装しています。
最低限のコードだけ書いているので、もし本番環境等で使用する場合は適切に設定してください。

instrumentation.ts
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
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を設定してください。

otel-collector-config.yml
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ファイルです。

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上にテレメトリデータが正しく送信されているかを確認できます。

otel_express_newrelic_01.png

各トランザクションの実行時間を可視化することも可能です。

otel_express_newrelic_02.png

各トランザクションのトレース情報やSpan情報を確認することで、処理の遅延やボトルネックとなるポイントが明確になります。
たとえば、データベースクエリの実行時間が他の処理と比べて著しく長い場合、SQLの最適化やインデックスの見直しなどの対応が必要になります。

otel_express_newrelic_03.png

このような解析結果をもとに、パフォーマンス改善を段階的に実施することが、運用中のシステムの信頼性向上につながります。

さいごに

計装にOpenTelemetryを利用することで、特定ベンダーへの依存性を減らし、柔軟で拡張性のあるアプリケーションを構築できます。
New Relicのような可視化サービスと組み合わせることで、システムのパフォーマンスや信頼性を向上させることができます。
この記事がどなたかの参考になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.