🔭

コンテナでデプロイした Lambda から OpenTelemetry で X-Ray にトレースを送る

2022/12/05に公開

はじめに

この記事は AWS LambdaとServerless Advent Calendar 2022 及び OpenTelemetry Advent Calendar 2022 5 日目の記事です。

この記事では 1. コンテナでデプロイした 2. AWS Lambda から 3. OpenTelemetry を使って 4. AWS X-Ray にトレースを送る方法を解説します。

OpenTelemetry でトレースを送るには

OpenTelemetry のコンセプト

例えばトレースの可視化用ツール A、メトリクスの可視化用ツール B、クラウドプロバイダのロギングツール C というふうに複数のツールを使っている場合を考えてみます。それぞれのツール用のエージェントをサイドカーで動かし、アプリケーションにはツール用の SDK を組み込んでそれぞれのツール向けにデータを送信するというパターンが多いでしょう。これだけ複数のツールを使うのは極端な場合だけかもしれませんが、それでも複数のツール用にサイドカーのエージェントを管理するのは煩雑ですし、アプリケーションにツール専用の SDK を組み込むと他のツールへの乗り換えが難しくなります。またツールごとに API や機能に差異があるとさらに乗り換えは難しくなるでしょう。

そこで OpenTelemetryです。公式ドキュメントでは以下のように説明されています。

OpenTelemetry, also known as OTel for short, is a vendor-neutral open-source Observability framework for instrumenting, generating, collecting, and exporting telemetry data such as traces, metrics, logs. As an industry-standard it is natively supported by a number of vendors.

OpenTelemetry はベンダー中立の Observability フレームワークでトレース・メトリクス・ログといったテレメトリデータを取り扱うことができます。詳しい歴史はドキュメントのここら辺にまとまっていて、今は CNCF の incubating プロジェクトでありベンダー中立の SDK や API を策定しています。後述する Collector をサイドカーなどの形式で動かしておいて、アプリケーション側からは OpenTelemetry の SDK を使って Collector にテレメトリデータを送信すれば、あとは Collector の設定に従ってバックエンドのツールにテレメトリデータを送信して可視化などを行えます。ツールごとにエージェントや SDK を組み込むのではなく、バックエンドのツールごとに設定を変えた上で Collector を共通で動かして OpenTelemetry という共通の SDK を使ってテレメトリデータを送信することができます。

Collector は Receiver・Processor・Exporter の3つから構成されていて、それぞれの設定を yaml ファイルで定義します。それぞれのコンポーネントはドキュメントのこのページで詳しく解説されています。例えば OTLP プロトコルでテレメトリを受信して、Batch Processor でまとめて処理して、バックエンドの AWS X-Ray に送信することができます。

OpenTelemetry と Lambda

OpenTelemetry の基本的な考え方では Collector を別プロセスで動かしておいて、アプリケーションからテレメトリを送信し Collector からさまざまなバックエンドにテレメトリを送信します(一旦 push型/pull型の話は傍に置いておきます)。一方で Lambda のようなイベントドリブンで発火するコンピュートサービスの場合この Collector を別プロセスで動かしておくことをそのまま実現するのは難しいです(イベントを処理した時間だけ料金が発生することがイベントドリブンのメリットであり、常時 Collector を動かしておくのは料金的に勿体無いため)。

ですがまだ Experimental であるものの Lambda のハンドラーを軽装しテレメトリを送信することはできます。AWS が提供している OpenTelemetry のディストリビューションである AWS Distro for OpenTelemetry (以下 ADOT)のドキュメントだともう少し詳しく説明されています。どのようにしてテレメトリを送っているのかを見ていきましょう。

Lambda から OpenTelemetry でテレメトリを送る(zip の場合)

まず AWS Lambda のデプロイ方法として依存物を zip でまとめてデプロイするのと、コンテナでまとめてデプロイするの2つの方法があります。

zip でデプロイする場合は Lambda Layer を使うことができ、この中に OpenTelemetry の諸々を保存しています。Lambda には Lambda Extension という仕組みがあり特定のパスにバイナリを登録しておくと Lambda のライフサイクルの中でそのバイナリを実行することができます(例えば Lambda 関数を初期化すると同時に Collector もスタンバイしておき、テレメトリを受信したらしばらくバッファに溜めておき、Lambda が消えていく前にそのバッファに溜まったテレメトリを忘れずにバックエンドに送信するなど)。なので OpenTelemetry Collector をビルドしてバイナリにし、Lambda Extension に登録した Lambda Layer を用意して、Lambda 関数からはその Layer を使うようにすれば Lambda から OpenTelemetry を使ってバックエンドにテレメトリを送信することができます。

こう書くと面倒に感じますが実は OpenTelemetry Lambda Layer が提供されている のでこの Layer を使って X-Ray にトレースを送る分にはそこまで手間ではありません。リンク先にあるように Lambda と同じリーションの Layer を使うようにして、トレースを有効にして環境変数をセットすれば後は Lambda のコードを書くだけです。

一方でこの節の最初に書いたように Lambda にはコンテナからデプロイすることもできます。そしてコンテナからデプロイする場合だと以下のような準備が必要になってきます。

Lambda から OpenTelemetry でテレメトリを送る(コンテナの場合)

Lambda Layer は zip でデプロイした場合しか使うことができません[1]。コンテナでデプロイする場合は Layer に格納していた依存物を含めて Lambda Extension に登録する必要があります。

Lambda Layer を独自でビルドする場合はこのリポジトリの手順に従えばいいです。build.sh を実行すれば諸々がまとまった layer.zip が生成されます。ここで注意点として git submodule の Lambda 用 OpenTelemetry Collector には awsxwayexporter が含まれていません(こちらをみるとわかるようにgithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporterが含まれていません)。なのでシェルスクリプトをそのまま実行するだけではダメで、コードを書き換えて xrayexporter を含め、 config.yaml も exporter で xrayexporter を使うようにする必要があります。

具体的にやること

READMEに書いているようにまずは git clone します。

git clone --recurse-submodules https://github.com/aws-observability/aws-otel-lambda.git

次に opentelemetry-lambda 内のファイルをいくつか編集していきます。

まず go.mod を編集して xrayexporter を追加します。

opentelemetry-lambda/collector/lambdacomponents/go.mod
require (
    // 他の exporter と同じバージョンで awsxrayexporter を追加
	github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter v0.62.0
	github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter v0.62.0
// 以下省略
)

次に default.go を編集します。

opentelemetry-lambda/collector/lambdacomponents/default.go

import (
// (中略)
	// awsxrayexporter を import
	"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter"
// (以下省略)
)
func Components() (component.Factories, error) {
    // (中略)

	exporters, err := component.MakeExporterFactoryMap(
		loggingexporter.NewFactory(),
		// awsxrayexporter を追加
		awsxrayexporter.NewFactory(),
		otlpexporter.NewFactory(),
		otlphttpexporter.NewFactory(),
		prometheusremotewriteexporter.NewFactory(),
	)
}

最後に Collector の設定でトレースの exporter に xrayexporter を使うようにします。

opentelemetry-lambda/collector/config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

exporters:
  logging:
    loglevel: debug
  # awsxray exporter を追加
  awsxray:

service:
  pipelines:
    traces:
      receivers: [otlp]
      # awsxray exporter を追加
      exporters: [logging, awsxray]
    metrics:
      receivers: [otlp]
      exporters: [logging]

後は build.sh を実行すると aws-otel-lambda/opentelemetry-lambda/python/src/layer.zip が生成されます。この zip ファイルを Dockerfile の中で使えばいいです。/opt に解凍している[2]ことが注意点です。

FROM public.ecr.aws/lambda/python:3.8

# 上で生成した layer.zip をコピー
COPY layer.zip layer.zip

RUN yum update -y && yum install -y unzip
# /opt に解凍する。
RUN unzip layer.zip -d /opt

COPY app.py requirements.txt ./

RUN python3.8 -m pip install -r requirements.txt -t .

CMD ["app.lambda_handler"]

最後どうなるか

以上の手順を踏んで OpenTelemetry Collector を Extension に登録した Lambda からトレースを X-Ray に送ることができます。CloudWatch の Service Map の画面で見るとこんな感じで、わかりにくいですが下の Lambda はコンテナでデプロイしたものです。

おわりに

以前 App Runner から OpenTelemetry で X-Ray にトレースを送る記事を書いたことがあるのですがそれよりは準備が大変でした。ただ Lambda のようなイベントドリブンのサービスからでも OpenTelemetry でテレメトリをバックエンドに送ることはできてよかったです。

脚注
  1. ドキュメントに "You can use layers only with Lambda functions deployed as a .zip file archive. For functions defined as a container image, you package your preferred runtime and all code dependencies when you create the container image. "と書いています ↩︎

  2. ドキュメントにあるように Lambda が extension を /opt/extensions から探すため。 ↩︎

Discussion