AWS LambdaのコンテナイメージでAWS Distro for OpenTelemetry Lambdaを使ってみる

2022.11.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CX事業部Delivery部の新澤です。

コンテナイメージでデプロイしたLambda関数の詳細なトレース情報を取得したいと思ったのですが、AWS Distro for OpenTelemetry Lambda(ADOT Lambda)はLambdaレイヤーで提供されており、コンテナイメージのLambda関数ではレイヤーがサポートされていないためそのままでは利用することができません。

そこで、コンテナイメージのLambda関数でADOT Lambdaを利用する手順について確認してみました。

概要

コンテナイメージでのLambdaレイヤーの利用に関する手順は、以下の公式ブログに記載がありますので、これを参考にやってみることにします。

コンテナイメージの作成

Lambdaレイヤーは、Lambda関数から参照するライブラリなどをLambda関数本体とは別にアーカイブしてLambda関数にアタッチする形で利用されます。

具体的には、Lamda関数初期化時にアタッチしたLambdaレイヤー(zipファイル)が/opt配下に展開されてLambda関数から参照できるようになります。

今回のADOT Lambdaの場合は、OpenTelemetryのライブラリに加え、テレメトリーデータをX-Rayなどに中継するためのADOT CollectorがLambda拡張機能として含まれていますので、これらをLambda関数のコンテナイメージの/opt配下に展開してあげることで、zip形式のLambda関数にLambdaレイヤーをアタッチするのと同様に機能させることができます。

まず、コンテナイメージにLambdaレイヤーを追加するためにはLambdaレイヤーのURLを取得する必要があります。

LambdaレイヤーのURL取得には以下のようにAWS CLIで取得が可能です。

※LambdaレイヤーのARNは、Lambda関数のランタイム、アーキテクチャ(amd64,arm64)によって異なるので、こちらで確認してください。

$ aws lambda get-layer-version-by-arn \
    --arn arn:aws:lambda:ap-northeast-1:901920570463:layer:aws-otel-nodejs-arm64-ver-1-7-0:2 \
    --query 'Content.Location' \
    --output text
https://awslambda-ap-ne-1-layers.s3.ap-northeast-1.amazonaws.com/snapshots/901920570463/aws-otel-nodejs-arm64-ver-1-7-0-bf76ad2a-15e5-4acc-8222-30e4203383ac?versionId=8JiC5dG8D31WVU3qf_UI4jl4yyDE6fyP&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDUaDmFwLW5vcnRoZWFzdC0xIkYwRAIgCl4mqIpdi2aXh9VO9ALLBPfmYnygAIqCNhv2rLV%2BywcCIHBQp7dZc%
2Bk0fLhFzrrpIk1O5E2HaL94yg7gP%2FSa1XrOKtwECD4QAxoMOTE5OTgwOTI1MTM5IgxOfFoYfTaiOpsM0gMquQQLMRvnj7NrQFuAPQmLOiEc
(略)

このURLからcurlなどを使ってダウンロードすればOKです!

それでは早速Dockerfileを書いてみます。

構成としては以下のようなマルチステージビルドにしています。

  1. Lambda関数本体のビルド
  2. ADOT Lambdaレイヤーの取得・展開
  3. 1と2をまとめる

Dockerfile

###################################
# Build application
FROM public.ecr.aws/lambda/nodejs:16 as builder
WORKDIR /usr/app
COPY src  ./src
COPY package*.json ./
RUN npm install
RUN npm run build

###################################
# Download ADOT Lambda Layer & unzip
FROM amazon/aws-cli as adotlayer
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG AWS_DEFAULT_REGION
ARG ADOT_LAYER_ARN
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
ENV AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
WORKDIR /opt
RUN yum -y install unzip && \
    curl $(aws lambda get-layer-version-by-arn --arn ${ADOT_LAYER_ARN} --query 'Content.Location' --output text) --output layer.zip && \
    unzip layer.zip && rm -f layer.zip

###################################
FROM public.ecr.aws/lambda/nodejs:16
WORKDIR /opt
COPY --from=adotlayer /opt .
WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /usr/app/dist ./dist
COPY --from=builder /usr/app/node_modules ./node_modules
CMD ["dist/index.handler"]

ADOT Lambdaを取得・展開するステージをビルドして、/opt配下にLambdaレイヤーを展開できているか確認してみます。

$ docker build --target adotlayer -t adotlayer \
  --build-arg AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  --build-arg AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  --build-arg AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION \
  --build-arg ADOT_LAYER_ARN='arn:aws:lambda:ap-northeast-1:901920570463:layer:aws-otel-nodejs-arm64-ver-1-7-0:2' \
  .
(略)
$ docker run -it --entrypoint /bin/bash adotlayer
bash-4.2# yum -y install tree
(略)
bash-4.2# tree -L 2 /opt
/opt
|-- adot-extension.d.ts
|-- adot-extension.d.ts.map
|-- adot-extension.js
|-- adot-extension.js.map
|-- collector-config
|   `-- config.yaml
|-- extensions
|   `-- collector
|-- nodejs
|   `-- node_modules
|-- otel-handler
|-- otel-handler-upstream
|-- wrapper.d.ts
|-- wrapper.d.ts.map
|-- wrapper.js
`-- wrapper.js.map

4 directories, 12 files

問題ないようですね!

Lambda関数の作成

それでは、S3バケットのオブジェクトリストを取得する簡単なLambda関数を作成してデプロイしてみます。

Lambda関数の環境変数には、ADOT Lambdaレイヤーをzip形式のLambda関数で使う時と同様に"AWS_LAMBDA_EXEC_WRAPPER"にラッパースクリプト"/opt/otel-wrapper"を指定します。

index.ts

import { ListObjectsCommand, S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({ region: "ap-northeast-1" });

const handler = async (event) => {
    const command = new ListObjectsCommand({
        Bucket: process.env.BUCKET_NAME,
        Delimiter: '/',
    });
    try {
        const response = await client.send(command);

        return {
            statusCode: 200,
            body: JSON.stringify(response),
        }
    } catch (error) {
        throw error;
    }
}
module.exports = { handler }

Lambda関数とS3バケットをCDKで作成します。

import * as cdk from 'aws-cdk-lib';
import { Architecture, DockerImageCode, DockerImageFunction, Tracing } from 'aws-cdk-lib/aws-lambda';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const layerArn = 'arn:aws:lambda:ap-northeast-1:901920570463:layer:aws-otel-nodejs-arm64-ver-1-7-0:2';

    // S3 Bucket
    const bucket = new Bucket(this, 'SampleBucket', {
      bucketName: `sample-bucket-${this.account}`,
    });

    // Lambda Function
    const func = new DockerImageFunction(this, 'DockerImageFunction', {
      functionName: 'samplefunc',
      architecture: Architecture.ARM_64,
      code: DockerImageCode.fromImageAsset('lambda/', {
        buildArgs: {
          AWS_ACCESS_KEY_ID: process.env['AWS_ACCESS_KEY_ID'] || '',
          AWS_SECRET_ACCESS_KEY: process.env['AWS_SECRET_ACCESS_KEY'] || '',
          AWS_DEFAULT_REGION: process.env['AWS_DEFAULT_REGION'] || '',
          ADOT_LAYER_ARN: layerArn,
        }
      }),
      memorySize: 1024,
      environment: {
        BUCKET_NAME: bucket.bucketName,
        AWS_LAMBDA_EXEC_WRAPPER: '/opt/otel-handler',
      },
      tracing: Tracing.ACTIVE,
    });

    bucket.grantReadWrite(func);

  }
}

実行してみる

デプロイしたLambda関数をコンソールからテストを実行します。

「ログ出力」にLambda拡張機能のADOT Lambda Collecorが起動されたログが出ています。問題なく起動できていそうです。

ADOT Lambda CollectorからX-Rayにトレースが出力されているか確認してみます。

トレースを確認することができました!

最後に

コンテナイメージのLambda関数でもトレースを取得できることが確認できました。

トレース・ログ・メトリックスといったトレーサビリティデータの取得は、アプリケーションの開発・運用において非常に重要ですので、アプリケーションのデプロイ方式に依らず取得できるようにしておきたいですよね。