S3 にアップロードされた画像ファイルから Lambda でサムネイル画像を生成してみる
概要
記事一覧はこちらです。
S3 にアップロードした画像ファイルから Lambda でサムネイル画像を生成してみます。
- アップロードする画像ファイルのフォーマットは JPEG とする。
- サムネイル画像のフォーマットも JPEG とする。サイズは幅320 x 高さ180 とする。
- 画像ファイルをアップロードする S3 Bucket は ksbysample-upload-bucket、サムネイル画像を置く S3 Bucket は ksbysample-resize-bucket とする。
- サムネイル画像のファイル名は <オリジナルの画像ファイル名>_thumb.jpg とする。
よく聞くパターンなので簡単だと思っていましたが、結構苦労しました。。。
参照したサイト・書籍
AWS Lambdaで画像ファイル加工~環境構築から実行確認まで~ 手順紹介
https://business.ntt-east.co.jp/content/cloudsolution/column-try-17.htmlLambda trigger on existing s3 bucket
https://forum.serverless.com/t/lambda-trigger-on-existing-s3-bucket/6056Using existing buckets
https://www.serverless.com/framework/docs/providers/aws/events/s3#using-existing-bucketsHow many records can be in S3 put() event lambda trigger?
https://stackoverflow.com/questions/40765699/how-many-records-can-be-in-s3-put-event-lambda-triggerSample Amazon S3 function code
https://docs.aws.amazon.com/lambda/latest/dg/with-s3-example-deployment-pkg.htmlPythonで文字列を置換(replace, translate, re.sub, re.subn)
https://note.nkmk.me/python-str-replace-translate-re-sub/Pythonでパス文字列からファイル名・フォルダ名・拡張子を取得、結合
https://note.nkmk.me/python-os-basename-dirname-split-splitext/AWS Lambdaで運用した実績から得られた、serverless frameworkのオススメ設定とプラグインの知見
https://tech.ga-tech.co.jp/entry/2018/12/12/120000Serverless Python Requirements
https://www.serverless.com/plugins/serverless-python-requirements/Why can't Python import Image from PIL?
https://stackoverflow.com/questions/26505958/why-cant-python-import-image-from-pil/31728305AWS LambdaでPython Pillowを使うための手順
https://qiita.com/ryasuna/items/9051cfdc0576134bb46cAWS Lambda で Pillow を使おうとしたらハマった
https://michimani.net/post/aws-use-pillow-in-lambda/amazonlinux
https://hub.docker.com/_/amazonlinuxlambci / docker-lambda
https://github.com/lambci/docker-lambda- しかも Lambda 実行用の Docker Image もあるとは!
AWS Lambda runtimes
https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.htmlWebP変換 & 画像キャッシュサービスをサーバレスで構築する - Feed re:Architect vol.1 -
https://buildersbox.corp-sansan.com/entry/2019/06/06/124752AWS LambdaでTensorFlow 2.0を使った画像分類
https://tech.unifa-e.com/entry/2019/09/17/085400- 今回の記事とは関係ありませんが、いろいろ調べている時に見かけて良い参考になりそうだったのでメモしておきます。
目次
- resize-image-app-project プロジェクトを作成する
- Serverless Framework で resize-service サブプロジェクトを作成する
- S3 Bucket を作成してイベント発生時に Lambda が呼び出されるよう serverless.yml を記述する
- S3 Bucket にファイルをアップロードした時の event の内容を確認する
- サムネイル画像を生成するよう handler.py の resize 関数を実装する
- serverless-python-requirements プラグインをインストールする
- deploy する
- 画像をアップロードしてサムネイル画像を生成してみる
- 最後に
手順
resize-image-app-project プロジェクトを作成する
以下の手順で share-s3bucket-with-multi-servces プロジェクトを作成します。具体的な手順は IntelliJ IDEA+Node.js+npm+serverless framework+Python の組み合わせで開発環境を構築して AWS Lambda を作成してみる 参照。
- resize-image-app-project の Empty Project を作成する。
- Python の仮想環境を作成する。
- Serverless Framework をローカルインストールする。
- .envrc を作成する。
Serverless Framework で resize-service サブプロジェクトを作成する
プロジェクトのルートディレクトリの下で npx sls create --template aws-python3 --path resize-service
を実行して resize-service サブプロジェクトを作成します。
自動生成された serverless.yml に対し、以下の設定を追加します。
- リソースが東京リージョン(ap-northeast-1)に生成されるようにする。
S3 Bucket を作成してイベント発生時に Lambda が呼び出されるよう serverless.yml を記述する
2つの S3 Bucket ksbysample-upload-bucket
、ksbysample-resize-bucket
を作成して、ksbysample-upload-bucket
にファイルがアップロードされた時に Lambda が呼び出されるよう serverless.yml に以下の記述を追加します。handler.py の関数名も resize に変更します。
service: resize-service provider: name: aws runtime: python3.8 stage: dev region: ap-northeast-1 iamRoleStatements: - Effect: "Allow" Action: - "s3:GetObject" Resource: - "arn:aws:s3:::ksbysample-upload-bucket/*" - Effect: "Allow" Action: - "s3:PutObject" Resource: - "arn:aws:s3:::ksbysample-resize-bucket/*" functions: resize: handler: handler.resize events: # Using existing buckets # https://www.serverless.com/framework/docs/providers/aws/events/s3#using-existing-buckets - s3: ksbysample-upload-bucket resources: Resources: KsbysampleResizeBucket: Type: AWS::S3::Bucket Properties: BucketName: ksbysample-resize-bucket
- events の下に
- s3: ksbysample-upload-bucket
を記述すると S3 Bucket が生成されます。resources に S3 Bucket の記述をする必要はありません。(新規作成するリソースは resources に記述するものと思い resources に S3 Bucket の記述をしてから- s3: ksbysample-upload-bucket
も書いて試しに deploy してみたら CREATE_FAILED が出てしばらく悩みました。。。) - もしイベント発生元の S3 Bucket を terraform 等で別に作成する場合には Using existing buckets を参考に設定すれば良さそうです。
ksbysample-resize-bucket
はイベント発生元ではないので resources の下に記述します。- この2つを書いただけでは S3 Bucket の GetObject、PutObject が出来ないので iamRoleStatements に必要な権限を記述します。
S3 Bucket にファイルをアップロードした時の event の内容を確認する
event がログに出力されるよう handler.py を以下のように変更してから、
import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def resize(event, context): logger.info(event)
deploy して S3 Bucket に sample.jpg をアップロードしてみると以下の json がログに出力されました(名前に principal が含まれているものと IP アドレスはマスクしています)。
{ 'Records': [ { 'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'ap-northeast-1', 'eventTime': '2020-06-10T00:45:25.995Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': { 'principalId': 'AWS:XXXXXXXXXXXXXXXXXXXXX' }, 'requestParameters': { 'sourceIPAddress': 'xxx.xxx.xxx.xxx' }, 'responseElements': { 'x-amz-request-id': '11F4BEB2A6E4D924', 'x-amz-id-2': 'k2RJ6yeU8sk5tRp7ntfyM8QDmR58rF1TIdacJimdExhLOOSQ3N6pOtIAWXyk6pGjTeBGOBx995ELqaK53zhdAo6Ky/PZb08e' }, 's3': { 's3SchemaVersion': '1.0', 'configurationId': '66e09001-2a37-4900-9a98-06da126cc896', 'bucket': { 'name': 'ksbysample-upload-bucket', 'ownerIdentity': { 'principalId': 'XXXXXXXXXXXXXX' }, 'arn': 'arn:aws:s3:::ksbysample-upload-bucket' }, 'object': { 'key': 'sample.jpg', 'size': 669286, 'eTag': 'febe0aaf4d817457776b6be293973442', 'sequencer': '005EE02D28714EE2DC' } } } ] }
Records が配列になっているので複数ファイルをアップロードしたら複数ここに入ってくるのかな?と思い、今度は sample.jpg、sample2.jpg の2つのファイルをアップロードしてみると、1 ファイルずつ event が発生しています。
stackoverflow に How many records can be in S3 put() event lambda trigger? があり、これを読むと 1 ファイル 1 event のようです。
サムネイル画像を生成するよう handler.py の resize 関数を実装する
Sample Amazon S3 function code の Python 3 のサンプルを参考にして実装します。
import logging import os import re import uuid from urllib.parse import unquote_plus import boto3 from PIL import Image thumbnail_size = 320, 180 logger = logging.getLogger() logger.setLevel(logging.INFO) s3_client = boto3.client('s3') def resize_image(image_path, resized_path): with Image.open(image_path) as image: image.thumbnail(thumbnail_size) image.save(resized_path) def resize(event, context): for record in event['Records']: bucket = record['s3']['bucket']['name'] key = unquote_plus(record['s3']['object']['key']) # ディレクトリの場合には何もしない if key.endswith('/'): return tmpkey = key.replace('/', '') download_path = '/tmp/{}{}'.format(uuid.uuid4(), tmpkey) resized_path = '/tmp/resized-{}'.format(tmpkey) filename = key.split('/')[-1] dirname = re.sub(filename + '$', '', key) basename, ext = os.path.splitext(filename) resized_key = '{}{}_thumb{}'.format(dirname, basename, ext) s3_client.download_file(bucket, key, download_path) resize_image(download_path, resized_path) s3_client.upload_file(resized_path, "ksbysample-resize-bucket", resized_key) logger.info('サムネイルを生成しました({})'.format(resized_key))
IDEA の Terminal を起動して boto3、Pillow をインストールします。IDEA の赤波下線を消したり補完を効かせるために入れているもので、deploy する時のパッケージは serverless-python-requirements プラグインを利用して収集します。
pip install boto3
pip install Pillow
serverless-python-requirements プラグインをインストールする
外部ライブラリを利用しているので serverless-python-requirements プラグインをインストールして自動でパッケージしてくれるようにします。
特に今回使用している Pillow は OS 依存のバイナリがあるそうなので、作業している Windows にインストールしたものではなく Docker を利用して Amazon Linux 2 上でライブラリのファイルをインストールする必要があります。AWS Lambda が動作している OS は AWS Lambda runtimes で確認できます。
serverless-python-requirements プラグインのサイトではインストールコマンドは sls plugin install -n serverless-python-requirements
と書いてありますが、今回作成しているプロジェクトでは package.json と serverless.xml の位置が異なるので npm install --save-dev serverless-python-requirements
でインストールして serverless.yml には自分で設定を追加することにします。
npm install --save-dev serverless-python-requirements
でインストールしてから、
serverless.yml を以下のように変更します。
service: resize-service plugins: - serverless-python-requirements custom: pythonRequirements: dockerizePip: true provider: name: aws runtime: python3.8 ..........
- plugins を追加して serverless-python-requirements を記述します。
- custom を追加して pythonRequirements を記述します。Docker を利用して deploy する外部ライブラリを収集するので
dockerizePip: true
を設定します。
IDEA の Terminal で pip freeze > requirements.txt
コマンドを実行して resize-service ディレクトリの下に requirements.txt を作成します。
requirements.txt には以下のように出力されましたが、
boto3==1.13.26 botocore==1.16.26 docutils==0.15.2 jmespath==0.10.0 Pillow==7.1.2 python-dateutil==2.8.1 s3transfer==0.3.3 six==1.15.0 urllib3==1.25.9
Pillow 以外は不要なので削除します。
Pillow==7.1.2
deploy する
最初に serverless-python-requirements プラグインが利用する Docker コンテナが C:\Users\<ユーザ名>\AppData\Local\UnitedIncome\
の下のファイルを参照するので、アクセスできるよう Docker の設定に追加します。追加しなくても delploy 時に追加するか聞かれるのですが、ランダム文字列を含むパスが追加されるので上位のディレクトリを追加しておくことにします。
npx sls deploy -v
コマンドを実行して deploy します。
最初に lambci/lambda:build-python3.8
の Docker Image をダウンロードして requirements.txt を参照して外部ライブラリをインストールしているのが分かります。
deploy が完了すると AWS マネジメントコンソールから ksbysample-upload-bucket
、ksbysample-resize-bucket
の2つの S3 Bucket が作成されていることが確認できました。
画像をアップロードしてサムネイル画像を生成してみる
ksbysample-upload-bucket に sample.jpg をアップロードすると、
ksbysample-resize-bucket に sample_thumb.jpg が生成されていました!
S3 Bucket 内の画像ファイルを削除してから npx sls remove -v
で delploy したリソース一式を削除します。
最後に
Serverless Framework と serverless-python-requirements プラグインがあったから何とか動かすことが出来た感が強いです。serverless-python-requirements プラグインなしで Python で Lambda 書くなんて考えられないんじゃないかな、と正直思いました。
履歴
2020/06/10
初版発行。