シェルスクリプトをOpenTelemetryで計測してNew Relicで可視化してみる

# TD;DR
シェルスクリプトで記載された処理をOpenTelemetryで計装してNew Relicで可視化してみます。

# 概要
サービスを計装するオープンな手段としてOpenTelemetryが注目されています。New Relicは OpenTelemetry の設立当初からの主要な貢献者として活動していますが、同時にOpenTelemetryを使って送信されたデータの送信先（エンドポイント）としても利用でき、データを分析するためのUIも充実しています。

サービスを計装する手段としては、Javaなどのアプリケーションの言語毎のAPMエージェントがありますが、すでにOpenTelemetryを利用している場合やAPMエージェントがサポートしていない環境を計装する場合にはOpenTelemetryも選択肢の一つとして有効です。今回は少し変わった例としてシェルスクリプトをOpenTelemetryで計装し、シェルスクリプトの処理をトレースデータとして可視化する例を解説します。新規に主要な言語でWebアプリが開発できる場合はAPMエージェント等を活用すれば良いですが、過去に作られたシェルスクリプトに依存していて一般的なWebアプリに作り替えることが難しい場合などに有効な手段になり得ます。

なお、今回はいちからOpenTemetryを使った計装をシェルスクリプトに実装するのではなく、先駆者が実装して公開しているプログラムを活用して計装を追加します（プログラムは[ここのリポジトリ](https://github.com/krzko/opentelemetry-shell)で公開されています）。このプログラムは、シェルスクリプト内でトレースやメトリクスを送信するための関数を公開しており、この関数を呼び出すとOpenTelemetryを使ってデータを送信します。データは、[OpenTelemetryコレクター](https://github.com/open-telemetry/opentelemetry-collector)を経由してOpenTelemetryのエンドポイントであるNew Relicにデータが送られて可視化されます。

![Otel-Shell.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/7edcba96-e1e3-9859-bd5a-27e5456cf88e.png)


# 手順
1. opentelemetry-shellの関数をシェルスクリプトから呼び出せるようにする
1. シェルスクリプトにopentelemetry-shellで用意された計装用のコードを追加する
1. opentelemetry-collectorを使ってOpenTelemetry Collectorを起動する
1. シェルスクリプトを実行する
1. OpenTelemetryでシェルスクリプトが計装され、New Relic上でデータが見えることを確認する



## 1. opentelemetry-shellの関数をシェルスクリプトから呼び出せるようにする
今回は、OpenTelemetryの実装として既に一般公開されているシェルスクリプト向けのサンプルプログラム（opentelemetry-shell）を利用するので、Githubリポジトリからクローンします

    git clone https://github.com/krzko/opentelemetry-shell.git

当該リポジトリのREADMEに記載がありますが、このプログラムはトレースやメトリックを送信する以下のような関数がOpenTelemetryを使って実装されていますのでアプリケーションであるシェルスクリプトからはそれらを呼び出す形になります。
■トレース（スパン）のデータ登録関数の例
otel_trace_start_parent_span
otel_trace_start_child_span
■メトリックのデータ登録関数の例
otel_metrics_push_gauge

## 2. シェルスクリプトにopentelemetry-shellで用意された計装用のコードを追加する
アプリケーションのシェルスクリプトにおいて主要な処理に上記で示した関数を呼び出すコードを追加していきます。今回は、上記のリポジトリに[サンプルコード](https://github.com/krzko/opentelemetry-shell/blob/main/examples/shell/basic_trace_httpbin.sh)があるので、簡単のためそのまま利用してみます。
以下、シェルスクリプトの一部です。元々curl_httpbinという処理があったところに、otel_trace_start_parent_spanやotel_trace_start_child_spanという関数呼び出しが追加されています。このシェルスクリプトでは、合計3回のcurlによりHTTP呼び出しが行われており、最初の呼び出しを親のSpanその後の呼び出しを子のSpanとしています。

    # Main
    otel_trace_start_parent_span curl_httpbin 200

    otel_trace_start_child_span curl_httpbin 201

    otel_trace_start_child_span curl_httpbin 203

## 3. opentelemetry-collectorを使ってOpenTelemetry Collectorを起動する
OpenTelemetryコレクターは、テレメトリデータを受信して処理してから、NewRelicまたは別の可観測性バックエンドにエクスポートするために実装できるコンポーネントです。以下の手順でセットアップします。

GithubからOpenTelemetryコレクターのコードをクローンする

    git clone https://github.com/open-telemetry/opentelemetry-collector

OpenTelemetryコレクターの設定ファイル（otel-config.yaml）にて、New Relicのエンドポイントとライセンスキーを指定します。ライセンスキーの取得方法についてはNew Relicのブログ（[API Keyの生成方法について](https://newrelic.com/jp/blog/how-to-relic/api-key-generation-steps)）を参考にしてください。エンドポイントやライセンスキーは設定ファイルに直に書くのではなくOpenTelemetryコレクターの起動時に環境変数で渡すようにしています。




    exporters:
      otlp:
        endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
        headers:
          api-key: ${NEW_RELIC_LICENSE_KEY}

OpenTelmetryコレクターを起動します。設定ファイルに指定したエンドポイントとライセンスキーを環境変数として渡します。

    docker run --rm \
      -e OTEL_EXPORTER_OTLP_ENDPOINT \
      -e NEW_RELIC_LICENSE_KEY \
      -p 4318:4318 \
      -v "${PWD}/otel-config.yaml":/otel-config.yaml \
      --name otelcol \
      otel/opentelemetry-collector \
      --config otel-config.yaml


## 4. シェルスクリプトを実行する
アプリケーションのシェルスクリプトを実行します。今回は上記リポジトリの中でトレースデータを送る[サンプルコード](https://github.com/krzko/opentelemetry-shell/blob/main/examples/shell/basic_trace_httpbin.sh)を実行します。正しく設定ができていればトレースデータがOpenTelemetryコレクター経由でNew Relicに送付されます。

    > ./basic_trace_httpbin.sh


## 5. OpenTelemetryでシェルスクリプトが計装され、データが見えることを確認する
OpenTelemetryで計装したデータをNew Relicが受信すると、OpenTelemetryで計装されたアプリケーションとして認識できます。アプリケーション名は、OTEL_SERVICE_NAME環境変数で指定しています。
![Otel-App.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/da58c095-ca40-06ac-af73-00a0d399bab4.png)

OpenTelemetry経由で計装できているのでルートの処理がトランザクションとして認識されています
![Otel-App-Transactions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/d0bd3b64-8a8e-552c-b686-c1d69a5fcbe6.png)


シェルスクリプト内部の処理の所要時間などがトレースとして確認できるようになりました。これによりボトルネックやエラー箇所が簡単に特定できます。
![Otel-App-Traces.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/861c2289-358a-6a71-a5e8-179b191ab7fd.png)


# 一般的なWebアプリから呼び出した場合につなげてみてみる
ここまででシェルスクリプト単体をOpenTelemetryで計装して可視化できることを確認しました。次は、別のWebアプリのトランザクション処理の中で該当のシェルスクリプトを呼び出している場合に、Webアプリからシェルスクリプトの一連の処理が一気通貫で確認できることを確認します。
ここでは、Webアプリは簡単なSpringBootアプリを用い、Java APMエージェントで計装します。APMエージェントからもトレースなどのデータは送られますが、トレースに一意に付与されるトレースIDがWebアプリとシェルスクリプト間で引き継がれることにより、APMエージェントが計装したWebアプリのトレースとOpenTelemetryで計装したシェルスクリプトのトレースが繋がるというメカニズムです。
![Otel-Diagram-DT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/2af8d732-943f-c2b5-023f-e1f70eb2f6cf.png)

## Webアプリ側でトレースのコンテキスト情報を取得する。
New Relic APMエージェントはトレースのコンテキスト情報を取得するAPIを備えていますので、Webアプリのプログラムの中でその情報を取得し、シェルスクリプトに渡します。各エージェントのAPIについては[New Relicの公式ドキュメント](https://docs.newrelic.com/jp/docs/logs/logs-context/annotate-logs-logs-context-using-apm-agent-apis/#apis)を参考にしてください。

以下はJavaの例ですが、エージェントのAPIを使ったコンテキスト情報の取得の方法です。トレースID、スパンIDをWebアプリから呼び出すシェルスクリプトに渡します。トレースIDはWebアプリとシェルスクリプトの処理が同一のトレースであることを示し、スパンIDは呼び出されたシェルスクリプトの呼び出し元であるWebアプリの処理を親のスパンIDとして認識させるために利用します。なお、[W3Cの仕様](https://www.w3.org/TR/trace-context/#design-overview)ではtraceparentやtracestateと言ったリクエストヘッダにこの辺りの情報が入ります。

    import com.newrelic.api.agent.NewRelic;
    
    // アプリケーションの処理(中略)
    
    // トレースのコンテキスト情報（トレースID、スパンID）を取得する
    String traceId = NewRelic.getAgent().getTraceMetadata().getTraceId();
    String spanId = NewRelic.getAgent().getTraceMetadata().getSpanId();
    
    // コンテキスト情報をシェルスクリプト起動時に渡す
    ... (呼び出しのコード）

シェルスクリプト側では、渡されたトレースIDを引き継ぎ、かつスパンIDを親スパンIDとして扱われるようにします。

今度は呼び出し元であるWebアプリにアクセスし、Webアプリとシェルスクリプトの処理が走るようにします。するとNew Relicの画面上では一連の処理が繋がります。このように単一の処理だけでなく、APMエージェントやOpenTelemetryで計装されるその他のアプリと連携している場合も一筆書きのように処理を追うことが可能になるため、シェルスクリプトを含め一連のアプリケーションのトラブルシュートやボトルネック把握が確認できます。
![Otel-App-Traces.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/12f28eec-be0b-90e2-65f8-1e4c42a0ec4c.png)


# まとめ
今回はOpenTelemetryを使ってシェルスクリプトの処理をNew Relic上で可視化することができることを確認しました。また、他のアプリケーションを含めたて処理が行われている場合も、一気通貫で確認できることを示しました。これにより分析の効率が上がり、ダウンタイムの軽減や運用効率の向上を実現することが可能になります。現状、計装手段がなくてお困りの場合はぜひお試しください。

New Relicのブログ「[Envoy の Trace 情報を New Relic に送る](https://newrelic.com/jp/blog/how-to-relic/envoy-tracing)」や「[OpenTelemetry サービスの JVM パフォーマンス比較が可能になりました](https://newrelic.com/jp/blog/nerdlog/compare-jvm-performance-for-opentelemetry)」では、OpenTelemetryを使った別の例も記載していますのでそちらも参考にしてください。

なお、今回のサンプルアプリやOpenTelemetryのデータ送信部分は、一般公開されているプログラムを活用させていただきました。トレースやメトリクスなどを送信する際の情報を増やしたい場合やロジックを変更したい場合は独自に改修・実装していく必要がある点はご承知おきください。

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

:::