SAMを用いて画像リサイズをやってみたので記事化しました。
社内で利用している画像リサイズ機能と似た要件で作成してみました。
SAMを用いることでローカルでの開発や確認ができたり、デプロイも容易に行うことができます。
また、どのAWSアカウントでも同様の機能を簡単にコマンドで構築することができます。
言語はTypeScriptで型周りの厳密性、tsconfigなどである程度ルールを設けることでより堅守な機能を構築しました。
構成
以下の図のような構成で作成していきます。
- Cloudfront (ヘッダーをつけてApi Gatewayへリクエストを送る)
- Api Gateway (アクセスするURLを生成、Lambdaと連携)
- Lambda (Api GatewayのURL PathからS3の画像を加工し返却する)
- Cloud Watch (Lambdaのイベントログ)
- S3 (画像)
要件としては以下のようなURLにアクセスするとします。
https://sam-resize-image/resize/320x320/{dirName}/{dirId}/{subDirName}/kosachan.jpg
アクセスすると320x320の画像にリサイズされて表示されます。
これはURLのPath Parametersを見て判断しています。左からリサイズタイプ、画像サイズ、そのあとはS3上の画像のキーが階層で繋がっています。
CloudWatchはデフォルトでLambdaのイベントログを収集してくれるので作業は不要です。
今回SAMで構築する部分は図に色の枠がついているApi GatewayとLambdaの2機能です。S3とCloudfrontに関しては作成手順が少ない且つ、運用時に滅多に変更がない箇所のため手動で作成しました。
開発
言語はNode v10で作成し、TypeScriptで開発をしました。
lambdaはNode v10をサポートしているためライブラリをインストールする際にはdocker環境やnodebrewでnodeバージョンを指定してインストールする必要があります。
※Macでは以下のように sharp
ライブラリをインストールします。
$ nodebrew exec v10.15.3 -- npm install --arch=x64 --platform=linux --target=10.15.0 sharp
localで開発するためにはS3をローカル環境で利用する必要があります。
localでS3を再現する方法としてはLocalStackを使うかMinioを利用します。
今回はLocalStackを利用し以下のdocker-compose.ymlを作成しました。
version: '3'
services:
localstack:
image: localstack/localstack
ports:
- 4567-4578:4567-4578
- 8060:8060
$docker-compose up
で起動し、バケットを作成し、画像を配置します。
- バケット作成
aws --endpoint=http://localhost:4572 s3api create-bucket sam-resize-image
- 画像配置
aws --endpoint=http://localhost:4572 s3 cp kosachan.jpg s3://sam-resize-image/sample/1/sub/kosachan.jpg
- 画像一覧確認
aws --endpoint=http://localhost:4572 s3 ls sam-resize-image/sample/1/sub
画像が作成できれば次はTypeScriptの環境を作ります。
$ npm install -D typescript @types/aws-lambda @types/sharp
インストールすることができればtsconfig.jsonを以下のように作成します。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"esModuleInterop": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"outDir": "./",
"rootDir": "./"
}
}
作成ができればトランスパイルするための対象のtsファイルを以下のように作成します。
envの設定が一部中途半端になっていますが、、。
ハマりポイントとしてはSAMの記法が難しくApi Gatewayとの連結部分でバイナリデータを扱う方法、書き方がわからずでドキュメントを見ながら少しずつ進めました。
上記の sam.yaml
から deployをします、以下のMakefileから make sam-deploy
でaws上でcloudformationのスタックを作り、LambdaとApi Gatewayにデプロイします。
sam-deploy:
tsc resize-image/app.ts; \
sam package \
--template-file sam.yaml \
--s3-bucket sam-resize-image \
--output-template-file packaged.yaml; \
sam deploy \
--template-file packaged.yaml \
--stack-name sam-resize-image \
--parameter-overrides Env=dev \
--no-fail-on-empty-changeset \
--capabilities CAPABILITY_NAMED_IAM;
ここまでできれば図のように作成ができたことが確認できます。
Api Gatewayのエンドポイントにcurlでリクエストを送り動作を確認します。
$ curl -X GET -H 'Accept: image/jpeg' "https://9ahogehoge.execute-api.ap-northeast-1.amazonaws.com/dev/trim/280x140/sample/1/sub/kosachan.jpg > kosachan.jpg
上記でトリミングされた画像を落としてこれたら成功です。
次にCloudfrontを作成します。
画像を表示するためにはheaderにAcceptの追加をしてリクエストし、 image/png,image/jpeg,image/gif,image/webp
のContent-Typeレスポンスを返す窓口が必要になるため、Cloudfrontを経由してApi Gatewayにアクセスをします。Api Gatewayで指定したステージ名のパスをCloudfrontのオリジンパスに指定することでドメインに内包することができます、またアプリケーションの要件によっては指定のパスにアクセスが来た際には直接s3やApi Gatewayへアクセスを振り分けることができます。
Cloudfront作成時に意図せぬ設定をして、CloudfrontのURLがApi GatewayのURLへリダイレクトされる現象が発生しましたが、キャッシュをしていたせいか、再作成すると正常に画像が表示されることが確認できました。
まとめ
以上でSAMを用いた画像リサイズを作ることができました、sharpライブラリがうまく入らなかったり、Cloudformationの記法が難しくなかなか進まなかったり、コンパイル後のTypeScriptのnode_module読み込みがうまくいかなかったり随所でつまづき時間をかけてしまいました。
今後やりたいこととしてはステージング回のデプロイの設定(中途半端なのでs3なども分ける)、Jestでのテスト、CI/CDでのデプロイなども試していければと思います。