AWS

CFNカスタムリソースでLambdaレイヤーパッケージを準備する – Python版

スポンサーリンク
CloudFormationカスタムリソースでLambdaレイヤーパッケージを準備する - Python版 AWS
スポンサーリンク
スポンサーリンク

CloudFormationカスタムリソースを使って、Python用のLambdaレイヤーパッケージを自動的に作成・配置する

以下のページでLambdaレイヤーの作成方法について取り上げました。

上記のページでは、Lambdaレイヤー本体はCloudFormationで作成しますが、Lambdaレイヤー用のパッケージは事前に手動で作成していました。

今回はCloudFormationカスタムリソースを使用して、このパッケージの準備を自動化することを考えます。
具体的には、パッケージを作成し、S3バケットに設置するという処理を、カスタムリソースで自動化します。

なお対象とするランタイム環境はPython3.8で、pipを使用します。

構築する環境

Diagram of preparing Lambda Layer Package with CloudFormation Custom Resource - Python Version

2つのLambda関数を作成します。

1つ目の関数でLambdaレイヤーパッケージを作成し、S3バケットに設置します。
この関数がCloudFormationスタック作成時に自動的に実行されるように、CloudFormationカスタムリソースに関数を関連づけます。
pipを使ってレイヤーに含めるライブラリを用意しますが、SSMパラメータストアに登録されている値を参照してインストールします。

2つ目の関数はLambdaレイヤーの確認用として使用します。
Function URLを有効化します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/064 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

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カスタムリソースとして実装します。
詳細につきましては、以下のページをご確認ください。

実行するコードの内容ですが、以下の通りです。

  1. CloudFormationテンプレートで定義した環境変数を、os.environにアクセスして取得する。
  2. Boto3でSSMパラメータストアの値を取得して、requirements.txtを作成する。
  3. pipでrequirements.txtを指定して、ライブラリを一括インストールする。
  4. インストールしたライブラリをshutil.make_archiveでZIPファイル化する。
  5. 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カスタムリソースを確認します。

CloudFormation Stack.

Lambda関数とカスタムリソース本体が正常に作成されていることがわかります。

次にSSMパラメータストアに保存されている値を確認します。

SSM Parameter Store.

requirements.txtの内容が保存されていることがわかります。

動作確認

準備が整いましたので、実際の動作を確認します。

Lambda関数の実行結果

まずCloudFormationカスタムリソースに関連づけたLambda関数の実行結果を、CloudWatch Logsのロググループで確認します。

Result of CloudFormation Custom Resource 1.

ログから、SSMパラメータストアに保存されていたrequirements.txtの内容に従って、ライブラリがインストールされていることがわかります。
つまりLambdaレイヤーパッケージが正常に作成されたということです。

そしてCloudFormationカスタムリソースとして、関数が「SUCCESS」を返していることもわかります。
つまり関数はカスタムリソースとして正常に動作したということです。

S3バケット

次にS3バケットにアクセスして、Lambdaレイヤーパッケージの設置状況を確認します。

Result of CloudFormation Custom Resource 2.

確かにS3バケットにLambdaレイヤーパッケージ(layer.zip)が設置されています。

Lambdaレイヤー

Lambdaレイヤーの作成状況を確認します。

Lambda Layer.

CloudFormationテンプレートで指定した通りに、アーキテクチャやランタイムが設定されています。
先述のS3バケット上のZIPファイルを参照して、このレイヤーが作成されているはずです。

確認用Lambda関数

確認用Lambda関数の作成状況を確認します。

Lambda Function 1.

Lambdaレイヤーが関連づいていることがわかります。

以下がレイヤーの詳細です。

Lambda Function 2.

先述のLambdaレイヤーが関連づいていることがわかります。

準備が整いましたので、この関数のFunction URLにアクセスします。
Function URLに関する詳細は、以下のページをご確認ください。

Result of Lambda Function with Lambda Layer.

GoogleニュースのRSS情報を取得できました。
この関数に関連づいているLambdaレイヤーに含まれているrequestsおよびBeautifulSoupが使えていることがわかります。

以上のことから、CloudFormationカスタムリソースを使用することによって、Python用のLambdaレイヤーパッケージを、自動的に用意できたことが確認できました。

まとめ

Python用のLambdaレイヤーパッケージを作成し、S3バケットに設置するという処理を、カスタムリソースで自動化する方法を確認しました。

タイトルとURLをコピーしました