概要
現在 AWS Lambda で複数の関数を運用しているが、共通するライブラリやコードが存在しているため、Lambda レイヤーで共通部分をくくり出したい
Lambda 関数については AWS SAM で実装しているため、レイヤーも同様に実装・管理を行いたい
ただしレイヤーはどの関数も参照するため、どこかの関数と一緒にするよりも独立させ、個別に管理やデプロイを行いたい
このような要望があり、色々調べたのだが、まとまった方法が見つからなかったため、ここにまとめる
環境
- Python 3.11.1
- aws-cli/2.13.12
- SAM CLI, version 1.95.0
結論
いきなり結論から書くが、以下のような形で実装した
ファイル構成(レイヤー)
.
├─ python
| ├─ __init__.py
| ├─ utils_layer.py
│ └─ requirements.txt
├─ tests
| ├─ unit
| | ├─ __init__.py
| | └─ test_utils_layer.py
| ├─ __init__.py
│ └─ requirements.txt
├─ samconfig.toml
└─ template.yaml
README などは割愛
テンプレート(レイヤー)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Utils Layer
Resources:
UtilsLayer:
Type: AWS::Serverless::LayerVersion
Properties:
Description: Utils Layer
LayerName: utils_layer
ContentUri: 'python/'
RetentionPolicy: Retain
CompatibleRuntimes:
- python3.11
Metadata:
BuildMethod: python3.11
SSMLatestArn:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /lambda-layer/latest/${AWS::StackName}
Type: String
Value: !Ref UtilsLayer
Outputs:
Name:
Description: Stack Name.
Value: !Ref AWS::StackName
Export:
Name: !Ref AWS::StackName
UtilsLayerArn:
Description: Published UtilsLayer ARN.
Value: !Ref UtilsLayer
Export:
Name: !Sub ${AWS::StackName}-arn
テンプレート(関数)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Get Function
Parameters:
LayerArn:
Type: AWS::SSM::Parameter::Value<String>
Default: /lambda-layer/latest/utils-layer
Resources:
GetFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: get_function/
Handler: app.lambda_handler
Runtime: python3.11
Layers:
- !Ref LayerArn
Architectures:
- x86_64
必要な部分だけ抜粋
コード(関数)
import utils_layer as utils
def lambda_handler(event, context):
utils.test()
return {
"statusCode": 200,
"body": "OK"
}
解説
基本方針として、sam init で作成したテンプレートからなるべく少ない変更で実装を行う
まずレイヤーのファイル構成だが、共有コードや共有ライブラリを登録した requirements.txt を置くフォルダ名は python
固定
ここを別の名前にしてしまうと import しても読み込んでくれず、訳の分からない No module エラーに悩まされることになる
そして共有関数を記述するファイル名は init で作成された app.py のままでも問題ないのだが、import 時にファイル名を指定することになるため、わかりやすい名前に変更しておくほうが良い
続いてテンプレート
基本的には上記をベースに必要な要素を追加してやれば問題なく動くはず
解説が必要な部分としてはレイヤーの読み込み部分だろう
レイヤーの読み込みは関数側のテンプレートにレイヤーの ARN を指定することになるのだが、この ARN はバージョンを含める必要がある
そのため、今回はレイヤーのデプロイ時に最新バージョンを含めた ARN をパラメータストアに保存し、関数側でその値を参照する形を取っている
クロススタックの参照を使っても問題ない
その場合は以下のようになる
Layers:
- !ImportValue utils_layer_arn
ただし、上記のように直接指定する場合は1つ注意が必要
ImportValue にしろ、パラメータストアにしろ、デプロイは問題ないのだが、ローカル環境での実行の場合は値を取ってきてくれないのだ
なので、読み込んだ値は一旦パラメーターにデフォルトとして指定しておき、ローカル実行時にはパラメーターをオーバーライドできるようにしておくと使い勝手が良い
以下のようなコマンドで実行できる
sam local invoke --parameter-overrides LayerArn=arn:aws:lambda:region:XXXXXX:layer:utils_layer:XX
ここまでやってレイヤーをデプロイすると、関数から何食わぬ顔で import できるようになる
ただしここでも1つ注意が必要で、ライブラリはともかく、作成した関数の場合、ユニットテストなどを行ったときに No module エラーを食らう場合がある
なのでそういった場合は import をレイヤーを使う関数内で行うようにすると良い
def lambda_handler(event, context):
import utils_layer as utils
utils.test()
return {
"statusCode": 200,
"body": "OK"
}
見栄えが悪い?そうね……