Lambda Custom RuntimeをCDKで簡単にデプロイする

目次

背景

S3バケットにUploadされたファイルの文字コード判別をNKFで行いたいが、EC2等がないのでLambdaで何とか実施したい。
また、他のLinuxコマンドを利用したいケースも出てきそう。

という事で、メンテナンス性も考慮しつつデプロイ工数も少なくなるようにCDKでCustom Runtime Lambdaをデプロイし、S3バケットにUploadされたファイルの文字コード・改行コード判別を実現してみました。

実装

ディレクトリ階層/作成したファイル

cdk initで作成した初期状態から下記ファイルを追加しています。

参考にした情報は下記となります。

チュートリアル – カスタムランタイムの公開
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html

ブートストラップのコードはサンプルコードそのまま。
function.sh の基本形もサンプルコードから流用しています。

lib
├── [CDKProject名]-stack.ts
├── docker-image (新しく作成したディレクトリ)
│   ├── Dockerfile
│   ├── bootstrap
│   └── function.sh

[CDKProject名]-stack.ts

CDKで下記を実施しています。

  • Lambda用のポリシー/ロール作成
    S3バケットは無制限に読み込めてしまうので要絞り込み
  • Lambda Functionを docker-image ディレクトリ下のファイルから作成、上記ロールを付ける
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';

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

    // IAMポリシー作成
    const lambda_policy = new iam.ManagedPolicy(this, 'iam-policy', {
      managedPolicyName: "custom-runtime-lambda-policy",
      description: 'Lambda basic execution policy',
      statements: [
          new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                  'logs:CreateLogGroup',
                  'logs:CreateLogStream',
                  'logs:PutLogEvents',
              ],
              resources: ['arn:*:logs:*:*:*'],
          }),
          new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                  's3:Get*',
                  's3:List*'
              ],
              resources: [
                '*'
              ],
          }),
      ],
    });

    // ロール作成
    const iam_role = new iam.Role(this, 'iam-role', {
        roleName: "custom-runtime-lambda-role",
        assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    });
    iam_role.addManagedPolicy(lambda_policy);
    
    // LambdaFunction作成
    const LambdaFunction = new lambda.DockerImageFunction(this, 'AssetFunction', {
      functionName: "custom-runtime-lambda-function",
      code: lambda.DockerImageCode.fromImageAsset('./docker-image'),
      timeout: cdk.Duration.seconds(120),
      memorySize: 128,
      role: iam_role
    });
  };
}

Dockerfile

※後述しますが M1/M2等 Armアーキテクチャ MacでBuildする場合は –platform=linux/amd64 を有効化

AWSの提供する、Lambda用のDockerImage
https://hub.docker.com/r/amazon/aws-lambda-provided

# FROM --platform=linux/amd64 public.ecr.aws/lambda/provided:al2
FROM public.ecr.aws/lambda/provided:al2

RUN yum install amazon-linux-extras -y \
  && amazon-linux-extras enable epel \
  && yum clean metadata \
  && yum install -y epel-release \
  && yum install -y nkf \
  && yum install -y jq \
  && yum install -y awscli

# Copy custom runtime bootstrap
COPY bootstrap ${LAMBDA_RUNTIME_DIR}

# Copy function code
COPY function.sh ${LAMBDA_TASK_ROOT}

RUN chmod +x ${LAMBDA_RUNTIME_DIR}/bootstrap \
  && chmod +x ${LAMBDA_TASK_ROOT}/*.sh

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "function.handler" ]

bootstrap

チュートリアル – カスタムランタイムの公開
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html

サンプルコードのまま

#!/bin/sh

set -euo pipefail

# for Debug
# echo "##  Environment variables:"
# env

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event. The HTTP request will block until one is received
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")

  # Extract request ID by scraping response headers received above
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Run the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done

function.sh

  1. jqを使い、jsonで引き渡されたイベント内容から、S3バケット名・オブジェクト名を取得
  2. S3からファイルをダウンロード (UUIDを用い複数呼ばれた場合を多少考慮)
  3. nkfで文字コード・改行コード推測
  4. ダウンロードしたファイル削除

※あくまでも最小限の動作確認サンプルとなります。

function handler () {
  EVENT_DATA=$1
  echo "$EVENT_DATA" 1>&2;
  RESPONSE="Echoing request: '$EVENT_DATA'"
  echo $RESPONSE

  # S3オブジェクトのパスを取得
  BUCKET_NAME=$(echo ${EVENT_DATA} | jq -r '.Records[0].s3.bucket.name')
  OBJECT_PATH=$(echo ${EVENT_DATA} | jq -r '.Records[0].s3.object.key')
  
  # uuidgen
  UUID=$(uuidgen)

  # Download
  echo "aws s3 cp s3://${BUCKET_NAME}/${OBJECT_PATH} /tmp/${UUID}" 1>&2;
  aws s3 cp s3://${BUCKET_NAME}/${OBJECT_PATH} /tmp/${UUID}

  # NKFでファイル情報確認/削除
  nkf --guess /tmp/${UUID} 1>&2;
  rm -rf /tmp/${UUID}
}

結果

S3Triggerを別途作成し、ファイルをUploadすると文字コード・改行コード結果が表示されました。

作成されたECRレポジトリ

作成されたLambda

通常のLambdaと異なり[コード]タブがありません。

まとめ

取り急ぎの動作確認環境としてささっと構築する事ができました。
ECRに自前でBuildしたImageのPush、IAMロール作成やら何やらを手動実施するとなると、頭痛が痛いと思いますのでCDKでの構築おすすめです!

Tips

CDKで短縮できたとはいえ、いくつかハマったポイントがありました。

lambda-entrypoint.sh: exec format error

何の事か分からないエラー、大変助かりました感謝

M1 MacでAWS Lambdaへ dockerイメージを cdk deploy すると exec format error になる
https://qiita.com/takurot/items/fd797caf8a2a830916bc

ローカル環境での動作確認

cdk deployしてDebugを繰り返すのは非効率なため、まずはLocalでdocker runしてテストイベントを食わせて動作確認をしていく事をお勧めします。
下記参照先にLocal実行方法の記載があります。

% docker build -t xxx
% docker run -p 9000:8080 

% curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'
Echoing request: '{"payload":"hello world!"}'%

※DockerImageのビルドが成功し適切にデプロイ出来る状態であれば、CurlでイベントJSONをPOSTするとリクエスト内容をエコーしてくれる

AWSの提供する、Lambda用のDockerImage
https://hub.docker.com/r/amazon/aws-lambda-provided

投稿者プロフィール

takashi
開発会社での ASP型WEBサービス企画 / 開発 / サーバ運用 を経て
2010年よりスカイアーチネットワークスに在籍しております

機械化/効率化/システム構築を軸に人に喜んで頂ける物作りが大好きです。
個人ブログではRaspberryPiを利用したシステムやロボット作成も
実施しております。

スカイアーチネットワークスで一緒に働きましょう!

ABOUTこの記事をかいた人

開発会社での ASP型WEBサービス企画 / 開発 / サーバ運用 を経て 2010年よりスカイアーチネットワークスに在籍しております 機械化/効率化/システム構築を軸に人に喜んで頂ける物作りが大好きです。 個人ブログではRaspberryPiを利用したシステムやロボット作成も 実施しております。 スカイアーチネットワークスで一緒に働きましょう!