S3+AWS Lambdaでアップロードした画像の自動リサイズ処理をしてみる¶
※AWSomedayの資料「AWSomeday_2021_2H_Session1_Basic_Compute.pdf」より抜粋
■手順概要¶
CloudFrontと連携する関係で、今回はバージニア北部リージョンに各種リソースを作成することにします。
- S3バケットの作成
- IAMロールの作成
- Lambda関数の作成と設定
- ファイルのアップロードと動作確認
■S3バケットの作成¶
- 自身のAWSアカウントIDを調べてメモります
- S3のコンソール画面を開きます
- 下記2つのバケットを作成します
- study-s2-src-AWSアカウントID
- study-s2-target-AWSアカウントID
「AWSアカウントID」部は各自の値を調べて置き換えます
S3バケット名は世界中で一意な名前をつける必要があり、他で使用済の名前は利用できません。
また、S3バケット名には大文字やアンダースコアが使えません。
srcの方のバケットにアップロードされた画像をリサイズしてtargetに保存する処理を行うように後の手順でLambdaを構成します。
■IAMロールの作成¶
IAMのコンソール画面を開きます
- 一般的なユースケースからLambdaを選択 > 次のステップ
- AWSLambdaBasicExecutionRole、AmazonS3FullAccess の2つのAWS管理ポリシーを選択 > 次のステップ
- タグは空のまま > 次のステップ
- ロール名に「StudyS2-Lambda-Role」を入力
- 作成
■Lambda関数の作成と設定¶
今回は"sharp"というNode.jsの画像処理のライブラリを利用してリサイズ処理を行います。
Lambdaのコンソール画面を開きます。
- 関数名:StudyS2-ImgResize、ランタイム:Node.js 14.x
- アクセス権限: 既存のロールを使用するを選択 > 前の手順で作成した「StudyS2-Lambda-Role」を選択
- 関数の作成
- コードソースに下記のコードをペースト > 保存(Deploy)
'use strict';
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
const Sharp = require('sharp');
const width = 150;
exports.handler = async (event, context) => {
const src_bucket = event.Records[0].s3.bucket.name;
const objectKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
await resizeToThumbnail(src_bucket, objectKey);
};
const resizeToThumbnail = async (src_bucket, objectKey) => {
const fileNameParts = objectKey.split('.');
const extention = fileNameParts[fileNameParts.length - 1];
const requiredFormat = extention === 'jpg' ? 'jpeg' : extention;
if (!(requiredFormat === 'jpeg' || requiredFormat === 'png')) {
// support only jpeg or png.
return;
}
let s3Data;
try {
s3Data = await S3.getObject({ Bucket: src_bucket, Key: objectKey }).promise();
} catch (err) {
console.log('Exception while reading source image :%j', err);
throw new Error("get object error");
}
let dest_bucket = `${src_bucket}-thumbnail`;
if (process.env.DEST_BUCKET) {
dest_bucket = process.env.DEST_BUCKET;
console.log('set dest_bucket:' + dest_bucket);
}
try {
// perform the resize operation
const sharpedBuff = await Sharp(s3Data.Body).resize(width).toBuffer();
await S3.putObject({
Body: sharpedBuff,
Bucket: dest_bucket,
ContentType: `image/${requiredFormat}`,
Key: objectKey,
StorageClass: 'STANDARD'
}).promise();
} catch (err) {
console.log(err);
throw new Error("resize or putObject error");
}
};
- 「設定」タブを開く
- 一般設定を編集しメモリを256MBに、タイムアウトを15秒に変更
- 環境変数「DEST_BUCKET」にtargetの方のS3バケット名を設定
- 保存
- 「コード」タブを開く > 画面下部の レイヤー に移動
レイヤーの追加 > レイヤーを選択 > ARNを指定 で下記のARNを設定
arn:aws:lambda:us-east-1:583465444362:layer:sharp-layer:1
追加
■Lambda関数とS3更新イベントの紐付け¶
- 「関数の概要」内の「トリガーを追加」をクリック
- S3を選択
- バケットに前の手順で作成した src の方を選択
- イベントタイプに「PUT」を選択
- プレフィックスに"images/"を入力、サフィックスは空のまま
- 「再帰呼び出し」のチェックをONにする
- 追加
■ファイルのアップロードと動作確認¶
- srcのバケットに"images"フォルダを作成
- 適当な画像ファイルをアップロード(拡張子がjpeg, jpg, pngである必要あり)
- targetのバケットにファイルができていることを確認
- CloudWatchLogsからLambdaのログを確認
■注意点¶
イベント発生元バケットと、Lambdaでファイルを作成するバケットを同じにすると無限にLambda呼び出しが発生します。
いういうミスをしてしまった場合にとりあえず実行を停止させる手段としては、設定 > 同時実行 > 関数の同時実行 > 同時実行の予約 の値を0に設定することでとりあえず関数の起動がされないようにできます。
■備考¶
依存ライブラリである"sharp"は実行環境に依存するファイルを含みますので、Lambdaの実行環境に合わせてAmazonLinux2の環境で固めたzipをlayerとして登録しています。
今回は依存ライブラリ"sharp"の参照をLayerで解決しましたが、基本的にはLambdaのビルド時に依存ライブラリもパッケージングしてデプロイします。
SAM(ServlerlessApplicationModel)を利用してLambdaの開発を行うことで簡単に↑のようなことができます。
Lambdaのパラメータ「event」に渡されるデータの構造は、連携元(呼び元)のサービスによって異なります。
今回はS3の更新イベントなので下記URLに記載のような構造でデータが渡されます。
今回利用したLayerは私のAWSアカウント上で作成したものを下記URLの手順で他アカウントに公開したものです。
aws lambda add-layer-version-permission --layer-name sharp-layer --statement-id xaccount \ --action lambda:GetLayerVersion --principal '*' --version-number 1 --output text