OpenTelemetry入門：Prometheus & Grafanaで実現するオブザーバビリティハンズオン

# オブザーバビリティとは
>「オブザーバビリティ（Observability）」は、「Observe （観察する）」と「Ability （能力）」の組み合わせから示す通り、日本語では「可観測性」や「観察する能力」と翻訳されています。ではその概念は、いつ頃に登場したのでしょうか。
> 
>  これは決して新しい概念ではなく、最初に提唱したのは「カルマンフィルター」で知られるRudolf E. Kálmán氏だと言われており、1960年に発表された「On the general theory of control systems（https://www.sciencedirect.com/science/article/pii/S1474667017700948?via%3Dihub）」に登場しています。
> 
>  その意味は「システム内部の状態を外部出力の情報から推測できる度合いを示す指標」であり、もともとは「自己調整システムの制御理論」に関して言及された言葉でした。
> 
> 
> 現在IT業界で使われている「オブザーバビリティ」も、基本的な意味は同様です。
> 
> 　つまり「システムの状態に係る出力情報を調査することによって、システム内部の状態を推測、把握する能力」です。
> 
> 　オブザーバビリティを高める上で得られるメリット、期待できることは主に下記の 2 点です。
> 
> ・システム状態の即時把握による、問題への迅速な初動対応
> ・ユーザーに対するサービスレベルの維持が容易になる

https://www.business-on-it.com/2005-observability 

## Open Telemetryとは
[OpenTelemetry（オープンテレメトリー）](https://opentelemetry.io/docs/what-is-opentelemetry/)は、マイクロサービスにおける性能や健全性を示す「テレメトリーデータ」を生成、管理するためのオープンソースのオブザーバビリティフレームワークです。
オブザーバビリティにおけるOpenTelemetryの役割は「データ収集基盤」として機能します。アプリケーションからテレメトリデータを収集し、監視バックエンドに送信することでオブザーバビリティを実現します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/4d7e4ca1-cacb-4b83-aef1-35c530cdea4a.png)


#### テレメトリーデータとは
テレメトリーデータとは、システムやアプリケーションの動作状態を示すデータのことで、主にログ・メトリクス・トレースのことを指します。



## Prometheusとは
[Prometheus（プロメテウス）](https://prometheus.io/docs/introduction/overview/)は、オープンソースの監視システムおよび時系列データベースです。主に、システムやアプリケーションのメトリクスを収集し、分析するために使用されます。
オブザーバビリティにおけるPrometheusの役割は「メトリクス収集」の基盤として機能します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/b7cdf84d-8848-4920-9fd2-dc8365514033.png)


## Grafanaとは
[Grafana](https://grafana.com/docs/grafana/latest/introduction/)はリアルタイムでデータを可視化・モニタリングを行ってくれるオープンソースの監視ソリューションです。
主な特徴は、多様なデータソースとの連携や、カスタマイズ可能なダッシュボードが挙げられます。オブザーバビリティにおけるGrafanaの役割は「可視化」の部分になります。テレメトリデータ（メトリクス、ログ、トレース）を表示することでシステムを統合的に把握することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/0e8ccc92-ad1d-418c-82d4-25e824955826.png)


## デモ構成図
デモは以下の構成になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/94c41f2a-1a5b-42c1-afd9-b9b4b88dfb31.png)

## ファイル構成
```
.
├── instrumentation.js
├── main.js
├── package-lock.json
├── package.json
├── docker-compose.yml
├── prometheus.yml
└── otel-collector-config.yml
```

## デモ
### 計装するアプリ作成
まず必要なパッケージをインストールします。

```
npm i express @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-grpc @opentelemetry/api
```

```json:package.json
{
  "name": "o11y",
  "version": "1.0.0",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "type": "module",
  "dependencies": {
    "@opentelemetry/api": "^1.9.0",
    "@opentelemetry/auto-instrumentations-node": "^0.56.1",
    "@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.2",
    "@opentelemetry/sdk-metrics": "^1.30.1",
    "@opentelemetry/sdk-node": "^0.57.2",
    "express": "^4.21.2"
  }
}

```

次にOpenTelemetry SDKの設定を行います。
```javascript:instrumentation.js
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";

// OpenTelemetryのSDKを初期化する
const sdk = new NodeSDK({
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter(), // OTLPエンドポイントにテレメトリーデータを送信
  }),
  instrumentations: [
    getNodeAutoInstrumentations(), // 自動計装を有効化
  ],
});

// SDKを起動することで自動で計装が開始される
sdk.start();
```
次にメトリクス生成を含めたExpressアプリを作成します。
```javascript:main.js
import express from "express";
await import("./instrumentation.js");
import { metrics } from "@opentelemetry/api";

const app = express();

app.get("/", (req, res) => {
  const meter = metrics.getMeter("test"); // プロバイダーからメーターを取得する。
  const countMetric = meter.createCounter('metric.counter'); // 新しいカウンターメトリクスを作成する
  countMetric.add(1); // カウントする
  res.send("OK")
});

app.listen(3001, () => {
  console.log("Server is running on port 3001");
});

```

### OpenTelemetry Collector設定
```yaml:otel-collector-config.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: otel-collector:4317
      http:
        endpoint: otel-collector:4318
        cors:
          allowed_origins:
            - "http://*"
            - "https://*"

processors:
  batch:

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889" # すべてのインターフェースでリッスン
  debug:
    verbosity: detailed

extensions:
  health_check:

service:
  extensions: ["health_check"]
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus, debug]

```
### docker-compose.ymlの設定
```yml:docker-compose.yml
services:
  grafana:
    image: grafana/grafana:11.4.0
    container_name: grafana
    ports:
      - 3000:3000

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus.yml
    command:
      - "--config.file=/etc/prometheus.yml"
    ports:
      - "9090:9090"

  otel-collector:
    image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.116.1
    container_name: otel-collector
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    command: ["--config=/etc/otel-collector-config.yml"]
    ports:
      - "8888:8888" # Prometheus metrics exposed by the collector
      - "8889:8889" # Prometheus exporter metrics
      - "13133:13133" # health_check extension
      - "4317:4317" # OTLP gRPC receiver
      - "4318:4318" # OTLP HTTP receiver

```
### docker-compose.ymlの設定
```yml:prometheus.yml
global:
  scrape_interval: 15s # デフォルトのインターバル時間。15秒間に1度スクレイピングする。

scrape_configs:
  - job_name: "prometheus" # ジョブという単位で管理
    scrape_interval: 5s # このジョブ特有の値
    static_configs:
      - targets: ["otel-collector:8889"]

```
### 実行
ターミナルを3つ開き、それぞれで以下のコマンドを実行します。
```
$ docker compose up
```

```
$ node main.js
```

```
$ curl http://localhost:3001
```

### Grafanaダッシュボードでメトリクスを確認
Grafana (http://localhost:3000) にアクセスしてGrafanaのログイン画面を開きます。
初期ユーザー名とパスワードは共に「admin」です。
ログイン後は以下の画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/5e9f0ac3-92a7-43a6-a527-2c88660c0f24.png)

次にGrafanaのデータソースにPrometheusを設定します。
サイドバーのData sourcesをクリックして、Prometheusを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/7b73200d-5028-4d8b-9206-f7b83703b4b8.png)

次に赤枠箇所にDockerコンテナで実行しているPrometheusサーバーのURLを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/fce5d298-48c9-418b-a979-06ac042873d7.png)

「Save&test」をクリックして、成功すれば接続完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/30beb0d4-8450-43b8-8287-4f9a6ed45c96.png)

ダッシュボードを作成します。
サイドバーの`Dashboards`をクリックして、Prometheusを選択します。
時間範囲を適切に設定して、Metricのプルダウンから`metric_counter_total`を選択します。
Prometheusのデータを可視化し、リクエスト数をダッシュボードで確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689023/11ffae44-be9d-4419-a8cc-e63edaf7f5af.png)