外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置する(前編)
概要
記事一覧はこちらです。
前々々回、前々回、前回の記事で作成した resize-image-app-project プロジェクト をベースに別プロジェクトを作成して、外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置するサンプルを作成します。
長いので2回に分けます。
尚、動作させてみて分かりましたが、Lambda Layer を更新した場合、利用している Lambda 関数の方も deploy し直さないと新しいバージョンの Lambda Layer を利用してくれません。利用する Lambda Layer は latest ではなくバージョン固定で指定されていて(マネジメントコンソールで AWS Lambda の関数を見るとどのバージョンを利用しているのか表示されます)、Lambda Layer を更新しても Lambda 関数が参照する Layer のバージョンは変わらないからです。
Pillow のようなパッケージなら delploy 時間を短縮するために Lambda Layer に配置するのはありだと思いますが、ちょっとした独自の共有モジュール程度なら serverless-package-external でいいんじゃないかなという気がします。
参照したサイト・書籍
AWS - Layers
https://www.serverless.com/framework/docs/providers/aws/guide/layers/Serverless Python Requirements
https://www.serverless.com/plugins/serverless-python-requirements/- 「Lambda Layer」の記述を参照しました。
Getting started with AWS Lambda Layers for Python
https://medium.com/@adhorn/getting-started-with-aws-lambda-layers-for-python-6e10b1f9a5dHow to publish and use AWS Lambda Layers with the Serverless Framework
https://www.serverless.com/blog/publish-aws-lambda-layers-serverless-framework/Reference CloudFormation Outputs
https://www.serverless.com/framework/docs/providers/aws/guide/variables#reference-cloudformation-outputs
目次
- python-lambda-layer-project プロジェクトを作成する
- resize-image-app-project プロジェクトから resize_service、tests ディレクトリをコピーし、boto3、Pillow、moto をインストールする
- 外部パッケージの Pillow を配置する Lambda Layer 用の shared_package_layer サブプロジェクトを作成する
- 独自モジュール(.py ファイル)を配置する Lambda Layer 用の my_module_layer サブプロジェクトを作成する
- resize_service ディレクトリ内のファイルを変更する
- 後編に続く
手順
python-lambda-layer-project プロジェクトを作成する
以下の手順で python-lambda-layer-project プロジェクトを作成します。具体的な手順は IntelliJ IDEA+Node.js+npm+serverless framework+Python の組み合わせで開発環境を構築して AWS Lambda を作成してみる 参照。
- python-lambda-layer-project の Empty Project を作成する。
- Python の仮想環境を作成する。
- Serverless Framework をローカルインストールする。
- .envrc を作成する。
npm install --save-dev serverless-python-requirements
resize-image-app-project プロジェクトから resize_service、tests ディレクトリをコピーし、boto3、Pillow、moto をインストールする
resize-image-app-project プロジェクトから resize_service、tests ディレクトリをコピーします。resize_service の下の .serverless、pycache ディレクトリは削除します。
IDEA の Terminal を起動して boto3、Pillow、moto をインストールします。
pip install boto3
pip install Pillow
pip install moto
コマンドラインと IntelliJ IDEA からユニットテストが成功することを確認します。
deploy が成功することも確認します(キャプチャは省略)。
外部パッケージの Pillow を配置する Lambda Layer 用の shared_package_layer サブプロジェクトを作成する
まずは外部パッケージの Pillow を Lambda Layer に配置します。ポイントは、Serverless Framework のドキュメントの AWS - Layers の設定は一切使わず、serverless-python-requirements プラグインのドキュメント の「Lambda Layer」に記載されている設定を使う点です。これ、分からなくて結構悩みました。。。
プロジェクトのルートディレクトリの下で npx sls create --template aws-python3 --path shared_package_layer
を実行して shared_package_layer サブプロジェクトを作成します。
serverless.yml を以下の内容に変更します。
service: shared-package-layer plugins: - serverless-python-requirements custom: pythonRequirements: dockerizePip: true # Lambda Layer の定義は pythonRequirements の下に記述する layer: name: shared-package-layer description: 共通パッケージ用 Lambda Layer provider: name: aws runtime: python3.8 stage: dev region: ap-northeast-1 resources: Outputs: # 他の Stack から Lambda Layer を参照できるようにする # Value に記載している "PythonRequirementsLambdaLayer" はこの文字列固定である SharedPackageLayer: Value: Ref: PythonRequirementsLambdaLayer
以下のファイルを削除・移動します。
- handler.py は不要なので削除します。
- resize_service/requirements.txt を shared_package_layer ディレクトリの下に移動します。
ここまででディレクトリ構成は以下のようになります。
deploy します。
マネジメントコンソールで確認すると Lambda Layer が作成されており(バージョンが 1 でないのは何度も試しているからです)、
「ダウンロード」ボタンをクリックして zip ファイルの中身を確認すると、
python ディレクトリの下に Pillow のパッケージが入っていました。
独自モジュール(.py ファイル)を配置する Lambda Layer 用の my_module_layer サブプロジェクトを作成する
外部パッケージだけでなく自分で作成したモジュール(.py ファイル)も Lambda Layer に配置してみます。ポイントは以下の2点です。
- こちらは Serverless Framework のドキュメントの AWS - Layers の設定を使用する。
- python ディレクトリの下に配置する。
プロジェクトのルートディレクトリの下で npx sls create --template aws-python3 --path my_module_layer
を実行して my_module_layer サブプロジェクトを作成します。
serverless.yml を以下の内容に変更します。
service: my-module-layer provider: name: aws runtime: python3.8 stage: dev region: ap-northeast-1 package: include: - ./python/** layers: # こちらの名称には "Layer" は付けない # "my-module-layer" の前半の "my-module" だけ取り出して MyModule にする MyModule: path: . name: my-module-layer description: 独自モジュール用 Lambda Layer compatibleRuntimes: - python3.8 resources: Outputs: # How to publish and use AWS Lambda Layers with the Serverless Framework # https://www.serverless.com/blog/publish-aws-lambda-layers-serverless-framework/ # # こちらの名称には "Layer" を付ける # "my-module-layer" から MyModuleLayer にする # 他の Stack から参照する時のキーになる MyModuleLayer: Value: # layers に書いた "MyModule" + "LambdaLayer" の文字列で参照する Ref: MyModuleLambdaLayer
handler.py は不要なので削除し、my_module_layer ディレクトリの下に python ディレクトリを作成します。
独自モジュール(.py ファイル)は my_module_layer/python の下に image_lib ディレクトリを作成し、その下に resize_utils.py を作成して、resize_service/handler.py の中に定義していた resize_image 関数を持ってくることにします。
my_module_layer/python/image_lib/resize_utils.py に以下の内容を記述します。
from PIL import Image thumbnail_size = 320, 180 def resize_image(image_path, resized_path): with Image.open(image_path) as image: image.thumbnail(thumbnail_size) image.save(resized_path)
ここまででディレクトリ構成は以下のようになります。
deploy します。
マネジメントコンソールで確認すると Lambda Layer が作成されており(バージョンが 1 でないのは何度も試しているからです)、
「ダウンロード」ボタンをクリックして zip ファイルの中身を確認すると、python ディレクトリの下に image_lib/resize_utils.py が入っていました。
resize_service ディレクトリ内のファイルを変更する
serverless.yml の以下の点を変更します。
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 layers: # Stack名はマネジメントコンソールの CloudFormation > スタック で確認する - ${cf:shared-package-layer-dev.SharedPackageLayer} - ${cf:my-module-layer-dev.MyModuleLayer} resources: Resources: KsbysampleResizeBucket: Type: AWS::S3::Bucket Properties: BucketName: ksbysample-resize-bucket
- serverless-python-requirements プラグインを使用しないので plugins、custom の定義を削除します。
- functions - resize の下に layers を追加し、以下の2行を追加します。他の Stack の Outputs を参照する方法は Reference CloudFormation Outputs に記載があります。
- ${cf:shared-package-layer.SharedPackageLayer}
- ${cf:my-module-layer.MyModuleLayer}
handler.py の以下の点を変更します。
import logging import os import re import uuid from urllib.parse import unquote_plus import boto3 from image_lib import resize_utils logger = logging.getLogger() logger.setLevel(logging.INFO) def resize(event, context): s3_client = boto3.client('s3') 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_utils.resize_image(download_path, resized_path) s3_client.upload_file(resized_path, "ksbysample-resize-bucket", resized_key) logger.info('サムネイルを生成しました({})'.format(resized_key))
- my_module_layer/python/image_lib/resize_utils.py に移動した resize_image 関数を削除します。
- 以下の記述も削除します。
from PIL import Image
thumbnail_size = 320, 180
- 以下の記述を追加します。
from image_lib import resize_utils
resize_image(download_path, resized_path)
→resize_utils.resize_image(download_path, resized_path)
に変更します。
後編に続く
履歴
2020/06/20
初版発行。