目次
背景
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 の基本形もサンプルコードから流用しています。
1 2 3 4 5 6 |
lib ├── [CDKProject名]-stack.ts ├── docker-image (新しく作成したディレクトリ) │ ├── Dockerfile │ ├── bootstrap │ └── function.sh |
[CDKProject名]-stack.ts
CDKで下記を実施しています。
- Lambda用のポリシー/ロール作成
S3バケットは無制限に読み込めてしまうので要絞り込み - Lambda Functionを docker-image ディレクトリ下のファイルから作成、上記ロールを付ける
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 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
サンプルコードのまま
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/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
- jqを使い、jsonで引き渡されたイベント内容から、S3バケット名・オブジェクト名を取得
- S3からファイルをダウンロード (UUIDを用い複数呼ばれた場合を多少考慮)
- nkfで文字コード・改行コード推測
- ダウンロードしたファイル削除
※あくまでも最小限の動作確認サンプルとなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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実行方法の記載があります。
1 2 3 4 5 |
% docker build -t xxx % docker run -p 9000:8080 <image name> % 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