はじめに
Lambda Layerを使うと、複数のLambdaで共通して使用するライブラリとその他の依存関係をパッケージ化することができます。
今回はServerless Frameworkを利用してLayerをデプロイし、開発環境、Lambda環境で利用する方法を解説します。
Layerの基礎知識
押さえておくべきポイントは以下2つです。
Lmabda環境においてLayerは/opt
以下に展開される。
Lambda環境のPYTHONPATH
には/opt/python
が含まれている。
※ PYTHONPATHとは、Pythonが import 文で利用するモジュールを探す際のパスです。
以下のようなutils.py
を共通化する場合を想定します。
def hello_func():
return "Hello!"
上記のポイント2つを組み合わせ、Lambda内で/opt/python/utils.py
となるようにレイヤーを配置することで、このようにLambda内部からLayerを利用することができます。
from util import hello_func
ディレクトリ構成
今回はLambdaごとにディレクトリを分けた構造を想定しています。
ルートに置いたrequirements.txt
に記載したPythonパッケージとlayers/custom_packages/python
以下の自前のPythonモジュールをLayer化し、hello_api/api.py
から呼び出します。
開発環境
.
├── hello_api
│ ├── api.py
│ └── serverless.yml
├── layers
│ ├── custom_packages ←ここの名前はなんでも良い
│ │ └── python ← ここをpythonにすることで/opt/pythonに展開される
│ │ ├── __init__.py
│ │ └── utils.py
│ └── serverless.yml
└── requirements.txt
Lambda環境
.
└── api.py
デプロイ
自前のモジュールをLayer化する
Serverless frameworkにはLayerをデプロイする機能がデフォルトで備わっているため、これをそのまま利用します。
service: myService
provider:
name: aws
layers:
hello:
path: layer-dir # required, path to layer contents on disk
name: ${sls:stage}-layerName # optional, Deployed Lambda layer name
description: Description of what the lambda layer does # optional, Description to publish to AWS
compatibleRuntimes: # optional, a list of runtimes this layer is compatible with
- python3.8
compatibleArchitectures: # optional, a list of architectures this layer is compatible with
- x86_64
- arm64
licenseInfo: GPLv3 # optional, a string specifying license information
# allowedAccounts: # optional, a list of AWS account IDs allowed to access this layer.
# - '*'
# note: uncommenting this will give all AWS users access to this layer unconditionally.
retain: false # optional, false by default. If true, layer versions are not deleted as new ones are created
今回の例では、このようになります。
service: layers
provider:
name: aws
region: ap-northeast-1
runtime: python3.9
layers:
utils:
path: custom_packages
name: utils
compatibleRuntimes:
- python3.9
requirements.txt
をLayer化する
serverless-python-requirements
を使うことで、requirements.txt
に記述したライブラリを全てLayer化することができます。
custom:
pythonRequirements:
fileName: "../requirements.txt"
layer:
name: python-requirements
compatibleRuntimes:
- python3.9
plugins:
- serverless-python-requirements
Layerを利用する
開発環境でLayerを読み込む
pip
等でインストールしたPythonパッケージは通常通り読み込むことができます。
import flask
一方でutils.py
で定義するような自前のモジュールは、Pythonのインポートの制約を受けます。
今回のように全く別のディレクトリで定義したものを呼び出す際はPYTHONPATH
を追加する必要があります。
追加方法は環境によりますが、例えばDocker、venvではそれぞれこのようになります。
# Dockerfile
ENV PYTHONPATH "${PYTHONPATH}:/app/layers/custom_packages/python"
# venv/bin/activate
export PYTHONPATH="../../layers/custom_packages/python"
LambdaでLayerを読み込む
LambdaからLayerを利用する際は、読み込むLayerを指定する必要があります。
functions:
hello:
handler: handler.hello
layers:
- arn:aws:lambda:region:XXXXXX:layer:LayerName:Y # arnで指定するパターン
同一のserverless.yml
内部であれば、Serverless Frameworkの!Ref
を使って参照することができます。
この際、タイトルケース化したLayerの名前
+LambdaLayer
を使って指定します。
layers:
test: # 参照時のキーはここに依存し、testであればTestになる
path: layer
functions:
hello:
handler: handler.hello
layers:
- !Ref TestLambdaLayer # Test + LambdaLayer
しかし、今回はLayerとLambdaで別のserverless.yml
を使っており!Ref
では参照できません。
そこで、CludFormationのOutputs機能を利用し、Layer作成時に、Layerのarn
を別のCloudFormationのスタックから参照可能にします。
resources:
Outputs:
UtilsLayerExport:
Value:
Ref: UtilsLambdaLayer # Layerの名前に依存
Export:
Name: UtilsLambdaLayer # 自由に変えて良い
PythonRequirementsLambdaLayerExport:
Value:
Ref: PythonRequirementsLambdaLayer # 変更不可
Export:
Name: PythonRequirementsLambdaLayer # 自由に変えて良い
Outputした値を読み込むserverless.yml
はこのようになります。
functions:
api:
handler: wsgi_handler.handler
name: hello_api
layers:
- 'Fn::ImportValue': UtilsLambdaLayer # layers/serverless.ymlで指定したキー
- 'Fn::ImportValue': PythonRequirementsLambdaLayer # layers/serverless.ymlで指定したキー
まとめ
これにより、開発環境、Lambda環境のどちらでも共通の処理、ライブラリをまとめることができるようになります。
ここまでの要素をまとめたserverless.yml
は以下の様になります。
service: layers
provider:
name: aws
region: ap-northeast-1
runtime: python3.9
layers:
utils:
path: custom_packages
name: utils
compatibleRuntimes:
- python3.9
custom:
pythonRequirements:
fileName: "../requirements.txt"
layer:
name: python-requirements
compatibleRuntimes:
- python3.9
resources:
Outputs:
UtilsLayerExport:
Value:
Ref: UtilsLambdaLayer
Export:
Name: UtilsLambdaLayer
PythonRequirementsLambdaLayerExport:
Value:
Ref: PythonRequirementsLambdaLayer
Export:
Name: PythonRequirementsLambdaLayer
plugins:
- serverless-python-requirements
functions:
api:
handler: wsgi_handler.handler
name: hello_api
layers:
- 'Fn::ImportValue': UtilsLambdaLayer
- 'Fn::ImportValue': PythonRequirementsLambdaLayer