(COM307) OpenTelemetry Lambda Layersをローカル起動してみた
はじめに
re:Inventでのセッション「COM307 Seamless observability with AWS Distro for OpenTelemetry」で紹介されていた OpenTelemetry Lambda Layersが気になったので少し触ってみました。
セッション資料
やりたいこと
OpenTelemetry Lambda Layersにはテレメトリを収集、処理するコレクタだけのレイヤー(collector)と各言語用のラッパーを含むレイヤーが含まれています。 今回はCollectorの方をビルド&ローカルで起動してみようと思います。
動かしてみる
aws-lambda-runtime-interface-emulator
Lambda Extensionをローカルで起動するにはLambdaのランタイムが必要です。今回はaws-lambda-runtime-interface-emulatorを使います。 オフィシャルではmacOS向けのバイナリは配布されていないのでarm64版をコンテナ上で動かします。リポジトリのREADMEを参考に以下のようなDockerfileをビルドします。
このエミュレータはREADMEに記載されている関数起動用のエンドポイント(http://localhost:9000/2015-03-31/functions/function/invocations
)以外にRuntime APIを公開しますが、エンドポイントは--runtime-api-address
オプションで指定しないと外部からアクセスできないので明示的に指定します。
コードはこのあたり
Dockerfile
FROM node:18 RUN apt-get update && \ apt-get install -y \ g++ \ make \ cmake \ unzip \ libcurl4-openssl-dev && \ npm install -g aws-lambda-ric COPY ./aws-lambda-rie-arm64 /usr/local/bin/aws-lambda-rie WORKDIR /app COPY ./entrypoint.sh entrypoint.sh COPY ./index.js index.js ENTRYPOINT [ "/usr/local/bin/aws-lambda-rie", "--runtime-api-address", "0.0.0.0:9001"] CMD ["aws-lambda-ric", "index.handler"] EXPOSE 8000 EXPOSE 9001
docker-compse.yml
version: '3' services: runtime: build: ./rie ports: - 9000:8080 - 9001:9001 environment: - LOG_LEVEL=debug
ハンドラ
"use strict"; exports.handler = async (event, context) => { return 'Hello World!'; }
エミュレータだけで動かす
まずはエミュレータだけで動かしてみます。起動用のエンドポイントにPOSTするとハンドラが実行されてレスポンスが得られました。
> curl -XPOST http://localhost:9000/2015-03-31/functions/function/invocations -d '{}' "Hello World!"
エミュレータ側の出力は下記の通り。
runtime-1 | 28 Dec 2023 04:15:50,775 [INFO] (rapid) exec 'aws-lambda-ric' (cwd=/app, handler=index.handler) runtime-1 | 28 Dec 2023 04:15:50,776 [DEBUG] (rapid) Runtime API Server listening on 0.0.0.0:9001 runtime-1 | 28 Dec 2023 04:16:08,870 [DEBUG] (rapid) invoke: -> POST /2015-03-31/functions/function/invocations map[Accept:[*/*] Content-Length:[2] Content-Type:[application/x-www-form-urlencoded] User-Agent:[curl/8.4.0]] runtime-1 | START RequestId: e9db1493-b2e1-4fbf-9393-2daef083d7d2 Version: $LATEST runtime-1 | 28 Dec 2023 04:16:08,870 [INFO] (rapid) INIT START(type: on-demand, phase: init) runtime-1 | 28 Dec 2023 04:16:08,871 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded. runtime-1 | 28 Dec 2023 04:16:08,871 [DEBUG] (rapid) Preregister runtime runtime-1 | 28 Dec 2023 04:16:08,871 [DEBUG] (rapid) Start runtime runtime-1 | 28 Dec 2023 04:16:08,871 [INFO] (rapid) Starting runtime without AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN , Expected?: false runtime-1 | Executing 'index.handler' in function directory '/app' runtime-1 | 28 Dec 2023 04:16:08,941 [DEBUG] (rapid) API request - GET /2018-06-01/runtime/invocation/next, Headers:map[Accept:[*/*] User-Agent:[AWS_Lambda_Cpp/0.2.8]] runtime-1 | 28 Dec 2023 04:16:08,941 [INFO] (rapid) INIT RTDONE(status: success) runtime-1 | 28 Dec 2023 04:16:08,941 [INFO] (rapid) INIT REPORT(durationMs: 70.232000) runtime-1 | 28 Dec 2023 04:16:08,941 [INFO] (rapid) INVOKE START(requestId: d1006b4d-2117-4939-850c-f192e7b71cbe) runtime-1 | 28 Dec 2023 04:16:08,941 [DEBUG] (rapid) Initialize invoke flow barriers runtime-1 | 28 Dec 2023 04:16:08,941 [DEBUG] (rapid) Set renderer for invoke runtime-1 | 28 Dec 2023 04:16:08,941 [DEBUG] (rapid) Release agents conditions runtime-1 | 28 Dec 2023 04:16:08,941 [DEBUG] (rapid) Release runtime condition runtime-1 | 28 Dec 2023 04:16:08,941 [DEBUG] (rapid) Await runtime response runtime-1 | 28 Dec 2023 04:16:08,942 [DEBUG] (rapid) API request - POST /2018-06-01/runtime/invocation/d1006b4d-2117-4939-850c-f192e7b71cbe/response, Headers:map[Accept:[*/*] Content-Length:[14] Content-Type:[application/json] User-Agent:[AWS_Lambda_Cpp/0.2.8]] runtime-1 | 28 Dec 2023 04:16:08,942 [DEBUG] (rapid) Await runtime ready runtime-1 | 28 Dec 2023 04:16:08,942 [DEBUG] (rapid) API request - GET /2018-06-01/runtime/invocation/next, Headers:map[Accept:[*/*] User-Agent:[AWS_Lambda_Cpp/0.2.8]] runtime-1 | 28 Dec 2023 04:16:08,943 [INFO] (rapid) INVOKE RTDONE(status: success, produced bytes: 0, duration: 1.827000ms) runtime-1 | 28 Dec 2023 04:16:08,943 [DEBUG] (rapid) Invoke() success runtime-1 | 28 Dec 2023 04:16:08,943 [DEBUG] (rapid) execute finished, autoreset cancelled runtime-1 | END RequestId: d1006b4d-2117-4939-850c-f192e7b71cbe runtime-1 | REPORT RequestId: d1006b4d-2117-4939-850c-f192e7b71cbe Init Duration: 0.51 ms Duration: 72.27 ms Billed Duration: 73 ms Memory Size: 3008 MB Max Memory Used: 3008 MB
コレクタを起動する
APIが起動したのでいよいよコレクタを起動してみます。
設定ファイル
コレクタの設定ファイルを作成します。
receivers: otlp: protocols: grpc: http: exporters: debug: verbosity: detailed service: pipelines: metrics: receivers: [otlp] exporters: [debug]
環境変数
設定ファイルやランタイムAPIのエンドポイントを環境変数で指定します。
# エミュレータのエンドポイント export AWS_LAMBDA_RUNTIME_API=localhost:9001 # 設定ファイルのパス export OPENTELEMETRY_COLLECTOR_CONFIG_FILE=/app/config/config.yaml export OPENTELEMETRY_EXTENSION_LOG_LEVEL=DEBUG export AWS_SAM_LOCAL=true
起動する
例によってmacOS向けのビルドスクリプトは無いのでgo run
で起動します。
go run main.go
うまくいくと以下のようにコレクタが起動し待機状態になります。
{"level":"info","ts":1703738142.770088,"msg":"Launching OpenTelemetry Lambda extension","version":"latest"} (略) {"level":"info","ts":1703738142.786889,"caller":"[email protected]/service.go:171","msg":"Everything is ready. Begin running and processing data."}
はまった点
コレクタの起動時にはまった点がいくつかあります。
エミュレータ起動後は関数実行前にコレクタを登録する必要がある
関数実行後にはextensionの登録ができないので必ず最初にコレクタを起動します。
エミュレータ起動後コレクタは一度しか登録できない
コレクタの設定不備やエラーでコレクタを再起動しても同じextensionを重複して登録できない仕様のためにエラーになります。エミュレータとコレクタはセットで再起動する必要があります。
上記の対策のため以下のようなラッパーを準備して使いました。
#!/bin/bash docker compose down docker compose up -d # ランタイムの起動をちょっと待つ sleep 1 go run main.go
Extension API クライアントをパッチする
Extensionの登録中にエラー「Failed to register main: event SHUTDOWN: ShutdownEventNotSupportedForInternalExtension」が発生したので、SHUTDOWNイベントフックを登録しないようにAPIクライアントのここを下記のようにパッチしました。
reqBody, err := json.Marshal(map[string]interface{}{ "events": []EventType{Invoke}, })
まとめ
なんやかんややってOpenTelemetry Lambda Layersに含まれるコレクタをローカルで起動してみました。
おまけ: コレクタの実装
このExtensionのコレクタはOpenTelemetryの標準実装を使っています。またディストリビューションにはいくつかの独自コンポーネントが含まれています。コンポーネントの初期化とコレクタの起動処理はそれぞれ以下で確認できます。 - コンポーネントの初期化 - コレクタの起動