nginx × New Relic: OpenTelemetryを用いたトレース情報の集約と可視化

:::note info
最新のアップデートの詳細はこちら。　[New Relic アップデート一覧](https://newrelic.com/jp/blog/nerdlog/new-relic-update "New Relic アップデート一覧")
:::

:::note info
[New Relic株式会社のQiita Organization](https://qiita.com/organizations/newrelic)では、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。
:::

:::note info
無料のアカウントで試してみよう！
[New Relic フリープランで始めるオブザーバビリティ！](https://qiita.com/ktst79/items/5ee74b34e6adb8a7a6fa)
:::

# 現状
　nginx は、高性能で安定した Web サーバーとして広く利用されています。ロードバランシングやリバースプロキシ、キャッシュの機能を持ち、Web アプリケーションの構築には欠かせない存在です。その柔軟な設定とスケーラビリティにより、多くの企業やサービスで信頼されているソフトウェアとなっています。

　一方、New Relic は、Infrastructure や APM のエージェントを用いてミドルウェアやアプリケーションのステータスやパフォーマンス情報を収集しています。APM エージェントは、トランザクションのトレースデータやログを収集することで、アプリケーションのパフォーマンス劣化をトランザクション単位で追跡し、出力されたログまで自動的に特定することを可能にしています。

　Web アプリケーションのオブザーバビリティを高めるには、nginx の可視化が欠かせません。特に、アプリケーションと nginx 間の関連性やトランザクション毎の分析を自動化することで、調査の効率化や工数削減が実現できます。

:::note warn
NGINX 公式の OpenTeremetry モジュールが公開されています。
[こちらのQiitaの記事](https://qiita.com/MarthaS/items/f8fa258100b28a628516)で詳細に紹介しています！ 
:::
https://qiita.com/MarthaS/items/f8fa258100b28a628516

# 概要
　この記事ではOpenTeremetry を用いて nginx のテレメトリデータのうち、メトリクス、トレースを収集し、New Relicでデータを可視化する詳細な設定手順を解説していきます。
　nginx の計装には、[OpenTelemetry nginx module](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx)を使用します。このモジュールは、OpenTelemetryの分散トレース機能を活用して nginx のテレメトリデータの収集を可能にしています。

https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx

# テレメトリーデータをNew Relic上で確認する
　ここでは、OpenTelemetry nginx module で収集したテレメトリーデータを OpenTelemetry Collector を経由してNew Relicへ連携し、データを確認する方法を紹介します。

![スクリーンショット 2023-08-29 9.33.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/bfac3bce-5cd1-0b18-da06-5cfdf5e47bdf.png)

## 前提
 Docker Composeを使用して検証環境を構築しています。
```:フォルダ構成
project/
 ├─ containers /
 │   ├─ nginx.dockerfile
 │   └  php.dockerfile
 ├─ nginx /
 │   ├─ nginx.conf
 │   └  default.conf
 ├─ src /
 │   ├─ index.php
 │   └  test.php
 └  docker-compose.yml
```

```docker-compose.yml
version: '3'
services:
  # nginx
  web:
      container_name: nginx_web
      platform: 'linux/amd64'
      build:
        context: .
        dockerfile: ./containers/nginx.dockerfile
      ports:
          - "8080:80"
      depends_on:
          - php
  # PHP
  php:
      container_name: php-fpm
      platform: 'linux/amd64'
      build:
        context: .
        dockerfile: ./containers/php.dockerfile
      environment:
          # 環境変数でNew Relicのライセンスキーを設定
          - NEW_RELIC_LICENSE_KEY
```
　後続のPHPアプリケーションには、New RelicのAPMエージェントをインストールして、アプリケーションのテレメトリデータを収集しています。
```dockerfile:php.dockerfile
FROM php:7-fpm

COPY ./src /var/www/html

# New RelicのAPMエージェントの導入
RUN \
  curl -L https://download.newrelic.com/php_agent/release/newrelic-php5-10.11.0.3-linux.tar.gz | tar -C /tmp -zx && \
  export NR_INSTALL_USE_CP_NOT_LN=1 && \
  export NR_INSTALL_SILENT=1 && \
  /tmp/newrelic-php5-*/newrelic-install install && \
  rm -rf /tmp/newrelic-php5-* /tmp/nrinstall* && \
  sed -i \
       # New Relicのライセンスキーの設定
       -e 's/"REPLACE_WITH_REAL_KEY"/"${NEW_RELIC_LICENSE_KEY}"/' \
       # New Relicで表示されるアプリケーションの名称
       -e 's/newrelic.appname = "PHP Application"/newrelic.appname = "Docker PHP Application"/' \
       -e 's/;newrelic.daemon.app_connect_timeout =.*/newrelic.daemon.app_connect_timeout=15s/' \
       -e 's/;newrelic.daemon.start_timeout =.*/newrelic.daemon.start_timeout=5s/' \
       /usr/local/etc/php/conf.d/newrelic.ini
```

## OpenTelemetry Collector のセットアップ
　nginx のテレメトリデータをOpenTelemetry Collector を経由して New Relic へ連携します。 まず、OpenTelemetry Collectorの設定を行います。
```diff_yaml:docker-compose.yml
+  # OpenTelemetry Collector
+  otel-collector:
+    container_name: otel-collector
+    platform: 'linux/amd64'
+    image: otel/opentelemetry-collector-contrib:0.83.0
+    restart: always
+    command: ["--config=/etc/otel-config.yml", ""]
+    volumes:
+      - ./otel-config.yml:/etc/otel-config.yml
+    environment:
+      # 環境変数でNew Relicのライセンスキーを設定
+      - NEW_RELIC_LICENSE_KEY
```
　OpenTelemetry Collector から New Relic へデータを連携するために exporters の設定を行います。設定内容はNew Relicの[公式ドキュメント](https://docs.newrelic.com/jp/docs/more-integrations/open-source-telemetry-integrations/opentelemetry/collector/opentelemetry-collector-basic/)を参考にしています。
```yaml:otel-config.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
processors:
  batch:

exporters:
  otlp:
    endpoint: https://otlp.nr-data.net:4317
    headers:
      api-key: ${NEW_RELIC_LICENSE_KEY} # New Relicのライセンスキーを環境変数に設定

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]
```
　OpenTelemetry と New Relic 間の連携はOTLPのプロトコルを使用しているため、プラグイン等の追加は必要ありません。OpenTelemetry と New Relicの連携については[こちら](https://newrelic.com/jp/blog/best-practices/what-is-opentelemetry)のブログで詳しく紹介しているため、参考にしてください。

https://newrelic.com/jp/blog/best-practices/what-is-opentelemetry

## モジュールのビルド
　OpenTelemetry nginx module は、GitHub Actions から.soファイルをダウンロードして使用する方法が案内されています。しかし、要件が OS：Linux、 nginx がStable(``1.18.0``)かmainline(``1.19.8``)を使っていることとなっているため、Docker環境では使用できません。
　今回は、モジュールをビルドして使用します。GitHubのReadmeには情報が少ないため、[こちら](https://github.com/open-telemetry/opentelemetry-cpp-contrib/issues/199#issuecomment-1263857801)のissueを参考にしています。

https://github.com/open-telemetry/opentelemetry-cpp-contrib/issues/199#issuecomment-1263857801

```dockerfile:nginx.dockerfile
FROM nginx:1.23.1-alpine as builder

RUN apk update \
  && apk add --update \
      alpine-sdk build-base cmake linux-headers libressl-dev pcre-dev zlib-dev \
      curl-dev protobuf-dev c-ares-dev \
      re2-dev abseil-cpp

# 依存関係にある gRPC のビルド
ENV GRPC_VERSION v1.43.2
RUN git clone --shallow-submodules --depth 1 --recurse-submodules -b ${GRPC_VERSION} \
  https://github.com/grpc/grpc \
  && cd grpc \
  && mkdir -p cmake/build \
  && cd cmake/build \
  && cmake \
    -DgRPC_INSTALL=ON \
    -DgRPC_BUILD_TESTS=OFF \
    -DCMAKE_INSTALL_PREFIX=/install \
    -DCMAKE_BUILD_TYPE=Release \
    -DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF \
    -DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF \
    -DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \
    -DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \
    -DgRPC_BUILD_GRPC_PYTHON_PLUGIN=OFF \
    -DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF \
    ../.. \
  && make -j2 \
    && make install

# 依存関係にある opentelemetry-cpp のビルド
ENV OPENTELEMETRY_VERSION v1.5.0
RUN git clone --shallow-submodules --depth 1 --recurse-submodules -b ${OPENTELEMETRY_VERSION} \
  https://github.com/open-telemetry/opentelemetry-cpp.git \
  && cd opentelemetry-cpp \
  && mkdir build \
  && cd build \
  && cmake -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/install \
    -DCMAKE_PREFIX_PATH=/install \
    -DWITH_ZIPKIN=OFF \
    -DWITH_JAEGER=OFF \
    -DWITH_OTLP=ON \
    -DWITH_OTLP_GRPC=ON \
    -DWITH_OTLP_HTTP=OFF \
    -DBUILD_TESTING=OFF \
    -DWITH_EXAMPLES=OFF \
    -DWITH_ABSEIL=ON \
    -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
    .. \
  && make -j2 \
  && make install

# otel_ngx_module.so のビルド
RUN git clone https://github.com/open-telemetry/opentelemetry-cpp-contrib.git \
  && cd opentelemetry-cpp-contrib/instrumentation/nginx \
  && mkdir build \
  && cd build \
  && cmake -DCMAKE_BUILD_TYPE=Release \
    -DNGINX_BIN=/usr/sbin/nginx \
    -DCMAKE_PREFIX_PATH=/install \
    -DCMAKE_INSTALL_PREFIX=/usr/lib/nginx/modules \
    -DCURL_LIBRARY=/usr/lib/libcurl.so.4 \
    .. \
  && make -j2 \
  && make install

FROM nginx:1.23.1-alpine

RUN apk update \
  && apk add --update \
      grpc protobuf c-ares

# ビルドしたモジュールを実行環境へコピー
COPY --from=builder /usr/lib/nginx/modules/otel_ngx_module.so /usr/lib/nginx/modules/

# NGINXの設定ファイルを所定の場所へコピー
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
```
　依存関係にある **gRPC** と　**opentelemetry-cpp**　をビルドした後、otel_ngx_module.soをビルドしています。ビルドには時間がかかるため、使用する場合はビルド用のDocker環境を用意してモジュールを取り出して使うことをお勧めします。

## nginx とモジュールの設定
　nginx の設定ファイルを変更してotel_ngx_module.soを読み込ませます。nginx.dockerfileで otel_ngx_module.so を ``/usr/lib/nginx/modules/otel_ngx_module.so`` に配置しているため、このファイルを nginx.conf でロードします。また、otel_ngx_module.so の設定ファイルを定義します。

```diff_conf:./nginx/nginx.conf 
user  nginx;
worker_processes  1;

+# otel_ngx_module.soをロード
+load_module /usr/lib/nginx/modules/otel_ngx_module.so;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

+    # otel_ngx_module.soの設定ファイル
+　　　　　　　　opentelemetry_config /conf/otel-nginx.toml;

    log_format json escape=json '{"time": "$time_iso8601",'
                                '"host": "$remote_addr",'
                                '"vhost": "$host",'
                                '"user": "$remote_user",'
                                '"status": "$status",'
                                '"protocol": "$server_protocol",'
                                '"method": "$request_method",'
                                '"path": "$request_uri",'
                                '"req": "$request",'
                                '"size": "$body_bytes_sent",'
                                '"reqtime": "$request_time",'
                                '"apptime": "$upstream_response_time",'
                                '"user_agent": "$http_user_agent",'
                                '"forwardedfor": "$http_x_forwarded_for",'
                                '"forwardedproto": "$http_x_forwarded_proto",'
                                '"referrer": "$http_referer"}';

    access_log  /var/log/nginx/access.log  json;

    sendfile        on;

    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}
```
　次にotel_ngx_module.so の設定ファイル``otel-nginx.toml``を作成しましょう。この設定ファイルは、データの連携や画面上で表示する際のリソース名、トレースのサンプリングレートが設定可能です。

```toml:otel-nginx.toml
exporter = "otlp"
processor = "batch"

[exporters.otlp]
# Alternatively the OTEL_EXPORTER_OTLP_ENDPOINT environment variable can also be used.
# OpenTelemetry Collectorを指定する
host = "otel-collector"
port = 4317

[processors.batch]
max_queue_size = 2048
schedule_delay_millis = 5000
max_export_batch_size = 512

[service]
# Opentelemetry resource name
name = "nginx-php-proxy"

[sampler]
name = "AlwaysOn" # Also: AlwaysOff, TraceIdRatioBased
ratio = 0.1
parent_based = false
```
作成した設定ファイルが使用できるようにNGINXのDockerfileの設定を変更します。
```diff_conf:nginx.dockerfile
# ビルドしたモジュールを実行環境へコピー
COPY --from=builder /usr/lib/nginx/modules/otel_ngx_module.so /usr/lib/nginx/modules/
+ # otel_ngx_module.soの設定ファイル所定の場所へコピー
+ COPY ./otel-nginx.toml /conf/otel-nginx.toml 

# NGINXの設定ファイルを所定の場所へコピー
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
```
　これで NGINX のテレメトリデータが収集できるようになりました。Dockerを起動後、サイトにアクセスでデータを作成しましょう。
```console
docker-compose up --build 
```

## New Relic上でデータを確認する
　New Relic の画面上でOpenTelemetryが連携したデータを確認しましょう。OpenTelemetry のデータは、**APM & Services** に **Services - OpenTelemetry** として表示されます。
![entity-list 21.25.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/e59c1f68-93cc-e57e-2139-e7966a95c98f.png)
　**Entity** の **nginx-php-proxy** が今回作成した nginx サーバーです。一覧に表示される **Entity Name** は、otel-nginx.toml で設定した ``[service]``　の ``name`` が適用されます。

![NGINX_Metrics.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/dbd9c719-38ec-1149-8afa-e24a3a8a15d3.png)
　**Response Time** や **Throughput**、 **Error rate** が確認できました。次に、分散トレーシング（ Distributed Tracing）を確認しましょう。
![NGINX_DT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/ecd73c7b-64eb-9062-6f9b-6d636bbbf1c2.png)
　トレースのデータが確認できましたが、名称が空白になっています。トレースの詳細も確認しましょう。
![NGINX_DT詳細.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/8dfd248d-a393-9537-1440-205097e7227a.png)
　nginx とPHPアプリケーションの関連が表示されていないことがわかりました。
　この設定内容では、**トランザクションの名称が表示されないこと** と **分散トレーシングが後続のPHPアプリケーションとのつながりが表示されていないこと**がわかりました。念の為、PHPアプリケーションのトレースが取得されているのかを確認しておきましょう。
![DT_PHP .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/acf7b67d-0b1f-beb4-bcbb-3db8a482f53c.png)
　PHPアプリケーションのトレースも取得できているため、nginx から PHPアプリケーションへW3C形式の **TraceParent** が引き継がれていないことが原因と考えられます。

# 分散トレーシングを有効にする
　分散トレーシングで nginx と PHPアプリケーションのつながりが確認できるように設定していきます。OpenTelemetry nginx module の設定項目を[こちらのドキュメント](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx#nginx-directives)で確認しましょう。
* [opentelemetry_operation_name](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx#opentelemetry_operation_name) でトランザクション名を設定できます。
* [opentelemetry_propagate](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx#opentelemetry_propagate) でW3C形式の TraceParent を後続のアプリケーションへ受け渡すことができるようになります。

## 分散トレーシングの設定
　nginx の設定ファイルにOpenTelemetry nginx moduleの2つの設定項目を追加します。この設定はlocation毎に実施が必要です。
```diff_conf:default.conf
server {

    index index.php index.html;
    server_name localhost;
    root /var/www/html;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
+       # W3C形式のTraceParentを後続のサービスへ引き継ぐ
+       opentelemetry_propagate;
+       # トランザクション名を設定
+       opentelemetry_operation_name  $request_uri;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}
```
　トランザクション名にはRequest URIを設定しています。これでトランザクション名と分散トレーシングの設定は完了です。コンテナを再起動して動作を確認していきます。
```console
docker-compose up --build 
```

## New Relic上でデータを確認する_Distributed Tracing
　まずはトランザクション名の設定を確認しましょう。New Relic APM の Trancastions もしくは　Distributed Tracingで確認が可能です。
![スクリーンショット 2023-08-27 1.49.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/3a3d04ab-9bc9-b902-f2be-9362aaf50a58.png)
　RequestUriに設定された内容がトランザクション名に設定されていることが確認できました。
　次に、Distributed Tracingの詳細画面で後続のPHPアプリケーションとのつながりを確認しましょう。
![スクリーンショット 2023-08-27 1.50.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/17fe64d8-5ebc-4e8b-6a0b-cb9590ef0e26.png)
　先ほどとは異なり、PHPアプリケーションをCallしているという関連が画面上で表現されています。

## New Relic上でデータを確認する_Service Map
　New RelicのService Map上でもアプリケーション間の関連を確認しておきましょう。New Relic　APM の Service mapから確認が可能です。
![スクリーンショット 2023-08-27 1.52.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/b296b3c7-ebf7-50e2-d944-53f4c891c8ad.png)
　Service Mapでも　Distributed Tracingと同様にアプリケーション間の関連が表示されることが確認できました。

# 参考情報
　この記事では nginx の計装をメインで説明したため、バックエンドのアプリケーションは簡易なものでした。実際のアプリケーションでは、以下のような表示になります。
![EC-CUBE_DT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/38a30b7c-a16a-62e2-3705-9dec07951dcf.png)
　このように、フロントエンドから nginx のリバースプロキシを経由してバックエンド、DBの関連が図示されています。

# まとめ
　この記事の中で、OpenTelemetry を使用して nginx のテレメトリデータを収集し、New Relic でそのデータを可視化する方法を詳細に解説しました。具体的には、以下の手順を実施しました。

1. OpenTelemetry nginx module の導入と設定
1. Docker Compose を使用して検証環境の構築
1. OpenTelemetry Collector のセットアップと設定
1. NGINX と OpenTelemetry nginx module の設定
1. 分散トレーシングの設定と確認
1. New Relic 上でのデータの確認

　この手順により、nginx のテレメトリデータを効果的に収集し、New Relic での可視化を実現することができました。特に、分散トレーシングを有効にすることで、アプリケーション間の関連性やパフォーマンスのボトルネックを明確に把握することができるようになりました。
　テレメトリデータの収集と可視化は、システムの健全性を維持し、問題の早期発見・対応に役立つ重要な作業です。OpenTelemetry と New Relic を組み合わせることで、これらの作業を効率的に行うことができます。また、New Relic の Infrastructure エージェントには nginx 統合の機能があり、nginx のステータスページから情報を収集して New Relic のプラットフォーム上で接続数などを確認することができます。
　今回は OpenTelemetry を使って nginx も含めてフロントエンドからバックエンドまでを一気通貫で計装できることを説明しました。この他にも　Apache や Envoy など、OpenTelemetry や Zipkin 等がサポートしているものであれば、同じように計装することが可能です。
　Envoy の Trace 情報を New Relic に連携する方法は、[こちらのブログ](https://newrelic.com/jp/blog/how-to-relic/envoy-tracing)で解説されているため、ぜひご覧ください。

https://newrelic.com/jp/blog/how-to-relic/envoy-tracing

　試してみたいという方は、今回紹介した OpenTelemetry と New Relic の連携は、永久無償ライセンスでも利用できます。是非永久無償ライセンスを[こちら](https://newrelic.com/jp/sign-up-japan)から試して見てください。

https://newrelic.com/jp/sign-up-japan

# Next Step
　この記事では、OpenTelemetry を活用して nginx のテレメトリデータを収集する方法を紹介しました。しかし、テレメトリデータのうちトレースにフォーカスしていたため、システムの調査において切り離すことのできないログについて触れていません。New Relic には、トレースとログを関連付けして表示する Logs in context いう強力な機能があります。nginx のログをトレース情報と紐付けて表示する方法については[別の記事](https://qiita.com/MarthaS/items/723685ec8f3eaa788b80)で紹介しています。 

https://qiita.com/MarthaS/items/723685ec8f3eaa788b80

:::note info
無料のアカウントで試してみよう！
[New Relic フリープランで始めるオブザーバビリティ！](https://qiita.com/ktst79/items/5ee74b34e6adb8a7a6fa)
:::

:::note info
最新のアップデートの詳細はこちら。　[New Relic アップデート一覧](https://newrelic.com/jp/blog/nerdlog/new-relic-update "New Relic アップデート一覧")
:::

:::note info
[New Relic株式会社のQiita Organization](https://qiita.com/organizations/newrelic)では、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。
:::