CloudFormationカスタムリソースを使って、Python用のLambdaレイヤーパッケージを自動的に作成・配置する
以下のページでLambdaレイヤーの作成方法について取り上げました。
上記のページでは、Lambdaレイヤー本体はCloudFormationで作成しますが、Lambdaレイヤー用のパッケージは事前に手動で作成していました。
今回はCloudFormationカスタムリソースを使用して、このパッケージの準備を自動化することを考えます。
具体的には、パッケージを作成し、S3バケットに設置するという処理を、カスタムリソースで自動化します。
なお対象とするランタイム環境はPython3.8で、pipを使用します。
構築する環境
2つのLambda関数を作成します。
1つ目の関数でLambdaレイヤーパッケージを作成し、S3バケットに設置します。
この関数がCloudFormationスタック作成時に自動的に実行されるように、CloudFormationカスタムリソースに関数を関連づけます。
pipを使ってレイヤーに含めるライブラリを用意しますが、SSMパラメータストアに登録されている値を参照してインストールします。
2つ目の関数はLambdaレイヤーの確認用として使用します。
Function URLを有効化します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/064
テンプレートファイルのポイント解説
SSMパラメータストア
Resources:
RequirementsParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Ref Prefix
Type: String
Value: |
requests
beautifulsoup4
Code language: YAML (yaml)
pipでインストールするライブラリをSSMパラメータストアに登録します。
後述しますが、この文字列からrequirements.txtを作成し、ライブラリを一括インストールします。
CloudFormationカスタムリソース
まずカスタムリソースで実行するLambda関数を確認します。
Resources:
Function1:
Type: AWS::Lambda::Function
Properties:
Architectures:
- !Ref Architecture
Environment:
Variables:
LAYER_PACKAGE: !Ref LayerPackage
REGION: !Ref AWS::Region
REQUIREMENTS_PARAMETER: !Ref RequirementsParameter
S3_BUCKET: !Ref CodeS3Bucket
S3_BUCKET_FOLDER: !Ref Prefix
Code:
ZipFile: |
...
EphemeralStorage:
Size: !Ref EphemeralStorageSize
FunctionName: !Sub "${Prefix}-function1"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole1.Arn
Timeout: !Ref Timeout
Code language: YAML (yaml)
ArchitectureおよびRuntimeプロパティがポイントです。
後述のLambdaレイヤーリソースで指定する値と合わせる必要があります。
今回は以下の通りに設定しました。
- Architecture:arm64
- Runtime:python3.8
EphemeralStorageおよびTimeoutプロパティも調整が必要なパラメータです。
Lambdaレイヤーに含めるライブラリの容量や数によっては、これらの値を大きめに設定する必要があります。
今回は以下の通りに設定しました。
- EphemeralStorage:512
- Timeout:300
Environmentプロパティで関数に渡すことができる環境変数を定義できます。
作成するパッケージのファイル名やSSMパラメータストアのパラメータ名、パッケージを設置するS3バケットに関する情報を渡します。
Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。
以下が実行するコードです。
import boto3
import cfnresponse
import os
import pip
import shutil
import subprocess
layer_package = os.environ['LAYER_PACKAGE']
region = os.environ['REGION']
requirements_parameter = os.environ['REQUIREMENTS_PARAMETER']
s3_bucket = os.environ['S3_BUCKET']
s3_bucket_folder = os.environ['S3_BUCKET_FOLDER']
CREATE = 'Create'
response_data = {}
work_dir = '/tmp'
requirements_file = 'requirements.txt'
package_dir = 'python'
requirements_path = os.path.join(work_dir, requirements_file)
package_dir_path = os.path.join(work_dir, package_dir)
layer_package_path = os.path.join(work_dir, layer_package)
def lambda_handler(event, context):
try:
if event['RequestType'] == CREATE:
ssm_client = boto3.client('ssm', region_name=region)
ssm_response = ssm_client.get_parameter(Name=requirements_parameter)
requirements = ssm_response['Parameter']['Value']
with open(requirements_path, 'w') as file_data:
print(requirements, file=file_data)
pip.main(['install', '-t', package_dir_path, '-r', requirements_path])
shutil.make_archive(
os.path.splitext(layer_package_path)[0],
format='zip',
root_dir=work_dir,
base_dir=package_dir
)
s3_resource = boto3.resource('s3')
bucket = s3_resource.Bucket(s3_bucket)
bucket.upload_file(
layer_package_path,
'/'.join([s3_bucket_folder, layer_package])
)
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
Code language: Python (python)
cfnresponseモジュールを使用して、関数をLambda-backedカスタムリソースとして実装します。
詳細につきましては、以下のページをご確認ください。
実行するコードの内容ですが、以下の通りです。
- CloudFormationテンプレートで定義した環境変数を、os.environにアクセスして取得する。
- Boto3でSSMパラメータストアの値を取得して、requirements.txtを作成する。
- pipでrequirements.txtを指定して、ライブラリを一括インストールする。
- インストールしたライブラリをshutil.make_archiveでZIPファイル化する。
- Boto3でZIPファイルをS3バケットにアップロードする。
ちなみに関数用のIAMロールは以下の通りです。
Resources:
FunctionRole1:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CreateLambdaLayerPackagePolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${RequirementsParameter}"
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${CodeS3Bucket}/*"
Code language: YAML (yaml)
AWS管理ポリシーであるAWSLambdaVPCAccessExecutionRoleに加えて、SSMパラメータストアからパラメータを取得する権限と、S3バケットにオブジェクトをアップロードする権限を付与します。
続いてCloudFormationカスタムリソース本体を確認します。
Resources:
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt Function1.Arn
Code language: YAML (yaml)
先述のLambda関数を指定します。
Lambdaレイヤー
Resources:
LambdaLayer:
Type: AWS::Lambda::LayerVersion
DependsOn:
- CustomResource
Properties:
CompatibleArchitectures:
- !Ref Architecture
CompatibleRuntimes:
- !Ref Runtime
Content:
S3Bucket: !Ref CodeS3Bucket
S3Key: !Ref LayerS3Key
Description: !Ref Prefix
LayerName: !Ref Prefix
Code language: YAML (yaml)
ポイントは3つあります。
1つ目はこのリソースが作成されるタイミングです。
先述のLambda関数が実行された後に、本リソースが作成される必要があります。
ですからDependsOnにカスタムリソースを指定します。
2つ目はCompatibleArchitecturesおよびCompatibleRuntimesプロパティです。
先述のLambda関数で指定したものと同一の値を設定します。
3つ目はContentプロパティです。
先述のLambda関数でアップロードしたZIPファイルを指定します。
(参照)確認用Lambda関数
Resources:
Function2:
Type: AWS::Lambda::Function
Properties:
Architectures:
- !Ref Architecture
Code:
ZipFile: |
import json
import requests
from bs4 import BeautifulSoup
url = 'https://news.google.com/rss'
def lambda_handler(event, context):
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
titles = [item.find('title').getText() for item in soup.find_all('item')]
return {
'statusCode': 200,
'body': json.dumps(titles, indent=2)
}
FunctionName: !Sub "${Prefix}-function2"
Handler: !Ref Handler
Layers:
- !Ref LambdaLayer
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole2.Arn
Code language: YAML (yaml)
確認用の関数ですから、特別な設定は行いません。
Layersプロパティで、先述のLambdaレイヤーを指定します。
import文でレイヤーに含まれるライブラリをインポートします。
この関数が正常に動作すれば、レイヤーパッケージ作成・Lambdaレイヤーリソース作成が正常に完了したことが確認できます。
コードの内容ですが、レイヤーに含まれるrequestsおよびBeautifulSoupを使用して、GoogleニュースのRSSを取得し、各アイテムのタイトルを返すという内容です。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- Lambda関数1:fa-064-function1
- Lambda関数2:fa-064-function2
- Lambda関数2のFunction URL:https://xljafu2utaoovuvrgvqcxtl2540uafya.lambda-url.ap-northeast-1.on.aws/
- SSMパラメータストアのパラメータ:fa-064
- Lambdaレイヤー用パッケージを保存するS3バケットおよびフォルダ:awstut-bucket/fa-064
AWS Management Consoleから各リソースを確認します。
まずCloudFormationカスタムリソースを確認します。
Lambda関数とカスタムリソース本体が正常に作成されていることがわかります。
次にSSMパラメータストアに保存されている値を確認します。
requirements.txtの内容が保存されていることがわかります。
動作確認
準備が整いましたので、実際の動作を確認します。
Lambda関数の実行結果
まずCloudFormationカスタムリソースに関連づけたLambda関数の実行結果を、CloudWatch Logsのロググループで確認します。
ログから、SSMパラメータストアに保存されていたrequirements.txtの内容に従って、ライブラリがインストールされていることがわかります。
つまりLambdaレイヤーパッケージが正常に作成されたということです。
そしてCloudFormationカスタムリソースとして、関数が「SUCCESS」を返していることもわかります。
つまり関数はカスタムリソースとして正常に動作したということです。
S3バケット
次にS3バケットにアクセスして、Lambdaレイヤーパッケージの設置状況を確認します。
確かにS3バケットにLambdaレイヤーパッケージ(layer.zip)が設置されています。
Lambdaレイヤー
Lambdaレイヤーの作成状況を確認します。
CloudFormationテンプレートで指定した通りに、アーキテクチャやランタイムが設定されています。
先述のS3バケット上のZIPファイルを参照して、このレイヤーが作成されているはずです。
確認用Lambda関数
確認用Lambda関数の作成状況を確認します。
Lambdaレイヤーが関連づいていることがわかります。
以下がレイヤーの詳細です。
先述のLambdaレイヤーが関連づいていることがわかります。
準備が整いましたので、この関数のFunction URLにアクセスします。
Function URLに関する詳細は、以下のページをご確認ください。
GoogleニュースのRSS情報を取得できました。
この関数に関連づいているLambdaレイヤーに含まれているrequestsおよびBeautifulSoupが使えていることがわかります。
以上のことから、CloudFormationカスタムリソースを使用することによって、Python用のLambdaレイヤーパッケージを、自動的に用意できたことが確認できました。
まとめ
Python用のLambdaレイヤーパッケージを作成し、S3バケットに設置するという処理を、カスタムリソースで自動化する方法を確認しました。