CFNカスタムリソースを使ってLambdaレイヤーパッケージを準備する – 一般ファイル版
以下のページで、Python用Lambdaレイヤーのパッケージを自動的に作成する方法をご紹介しました。
今回は言語に依らない一般的なファイルをレイヤー化して、関数から参照できるようにすることを考えます。
例えば証明書ファイル等を想定しています。
構築する環境
基本的には、冒頭にご紹介したページと同様の構成です。
異なる点は2点です。
1点目はSSM Parameter Storeに保存する文字列です。
冒頭のページでは、インストールするPythonパッケージの名前のリストを登録していました。
今回の構成では、Lambdaレイヤーに含めるファイルのURLのリストを登録します。
2点目はカスタムリソースに関連づけるLambda関数の内容です。
冒頭のページでは、Python用パッケージ管理ツールpipでパッケージをダウンロードしました。
今回の構成では、urllib.request.urlopenを使用して、ファイルをダウンロードします。
なお関数のランタイム環境はPython3.8とします。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/130
テンプレートファイルのポイント解説
SSMパラメータストア
Resources:
UrlsParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Ref Prefix
Type: String
Value: |
https://data.nasa.gov/resource/gvk9-iz74.json
Code language: YAML (yaml)
ダウンロードするファイルのURLのリストを登録します。
今回はサンプルのファイルとして、NASAが公開しているJSONファイルを対象とします。
CloudFormationカスタムリソース
Lambda関数
まずカスタムリソースで実行するLambda関数を確認します。
Resources:
Function1:
Type: AWS::Lambda::Function
Properties:
Architectures:
- !Ref Architecture
Environment:
Variables:
LAYER_PACKAGE: !Ref LayerPackage
REGION: !Ref AWS::Region
URLS_PARAMETER: !Ref UrlsParameter
S3_BUCKET: !Ref CodeS3Bucket
S3_BUCKET_FOLDER: !Ref Prefix
Code:
ZipFile: |
import boto3
import cfnresponse
import os
import shutil
import subprocess
import urllib
layer_package = os.environ['LAYER_PACKAGE']
region = os.environ['REGION']
urls_parameter = os.environ['URLS_PARAMETER']
s3_bucket = os.environ['S3_BUCKET']
s3_bucket_folder = os.environ['S3_BUCKET_FOLDER']
CREATE = 'Create'
response_data = {}
work_dir = '/tmp'
package_dir = 'python'
package_dir_path = os.path.join(work_dir, package_dir)
layer_package_path = os.path.join(
work_dir,
layer_package
)
ssm_client = boto3.client('ssm', region_name=region)
s3_client = boto3.client('s3', region_name=region)
def lambda_handler(event, context):
try:
if event['RequestType'] == CREATE:
ssm_response = ssm_client.get_parameter(Name=urls_parameter)
urls = ssm_response['Parameter']['Value']
result = subprocess.run(
['mkdir', package_dir_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
for url in urls.splitlines():
print(url)
file_name = os.path.basename(url)
download_path = os.path.join(package_dir_path, file_name)
data = urllib.request.urlopen(url).read()
with open(download_path, mode='wb') as f:
f.write(data)
shutil.make_archive(
os.path.splitext(layer_package_path)[0],
format='zip',
root_dir=work_dir,
base_dir=package_dir
)
s3_client.upload_file(
layer_package_path,
s3_bucket,
os.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)
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関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。
cfnresponseモジュールを使用して、関数をLambda-backedカスタムリソースとして実装します。
詳細につきましては、以下のページをご確認ください。
実行するコードの内容ですが、以下の通りです。
- os.environにアクセスして、CloudFormationテンプレートで定義した環境変数を取得する。
- Boto3でSSMパラメータストアにアクセスして、URLのリストを取得する。
- urllib.request.urlopenでファイルをダウンロード後、ファイルに書き込む。
- インストールしたライブラリを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/${UrlsParameter}"
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${CodeS3Bucket}/*"
Code language: YAML (yaml)
AWS管理ポリシーであるAWSLambdaBasicExecutionRoleに加えて、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
Environment:
Variables:
DIR_PATH: /opt/python
FILE_NAME: gvk9-iz74.json
Code:
ZipFile: |
import json
import os
dir_path = os.environ['DIR_PATH']
file_name = os.environ['FILE_NAME']
def lambda_handler(event, context):
file_path = os.path.join(dir_path, file_name)
with open(file_path, 'r') as f:
json_data = json.load(f)
print(json_data)
return {
'statusCode': 200,
'body': json.dumps(json_data, indent=2)
}
FunctionName: !Sub "${Prefix}-function2"
Handler: !Ref Handler
Layers:
- !Ref LambdaLayer
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole2.Arn
Timeout: !Ref Timeout
FunctionUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
TargetFunctionArn: !GetAtt Function2.Arn
FunctionUrlPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunctionUrl
FunctionName: !GetAtt Function2.Arn
FunctionUrlAuthType: NONE
Principal: "*"
FunctionRole2:
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
Code language: YAML (yaml)
確認用の関数ですから、特別な設定は行いません。
Layersプロパティで、先述のLambdaレイヤーを指定します。
レイヤーに含まれているJSONファイルを開いて、内容を返します。
Lambdaレイヤーのコンテンツの配置場所がポイントです。
Lambda 関数にレイヤーを含めると、Lambda は関数実行環境で/optディレクトリにレイヤーコンテンツを抽出します。
関数からレイヤーコンテンツにアクセスする
この関数の呼び出し方法ですが、Function URLを作成します。
Function URLの詳細につきましては、以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- Lambda関数1:fa-130-function1
- Lambda関数2:fa-130-function2
- Lambda関数2のFunction URL:https://24ugu7yzkz6jl3lqrskebwwpoa0gnymj.lambda-url.ap-northeast-1.on.aws
- SSMパラメータストアのパラメータ:fa-130
- Lambdaレイヤー用パッケージを保存するS3バケットおよびフォルダ:awstut-bucket/fa-130
AWS Management Consoleから各リソースを確認します。
まずCloudFormationカスタムリソースを確認します。
Lambda関数とカスタムリソース本体が正常に作成されていることがわかります。
次にSSMパラメータストアに保存されている値を確認します。
ダウンロードし、Lambdaレイヤーに含めるファイルのURLが登録されています。
動作確認
準備が整いましたので、実際の動作を確認します。
Lambda関数の実行結果
まずCloudFormationカスタムリソースに関連づけたLambda関数の実行結果を、CloudWatch Logsのロググループで確認します。
CloudFormationカスタムリソースによって、本関数が実行されたことがわかります。
そしてファイルをダウンロードしたことも確認できます。
つまりLambdaレイヤーパッケージが正常に作成されたということです。
S3バケット
次にS3バケットにアクセスして、Lambdaレイヤーパッケージの設置状況を確認します。
確かにS3バケットにLambdaレイヤーパッケージ(layer.zip)が設置されています。
Lambdaレイヤー
Lambdaレイヤーの作成状況を確認します。
CloudFormationテンプレートで指定した通りに、アーキテクチャやランタイムが設定されています。
先述のS3バケット上のZIPファイルを参照して、このレイヤーが作成されているはずです。
確認用Lambda関数
確認用Lambda関数の作成状況を確認します。
先述のLambdaレイヤーが関連づいていることがわかります。
準備が整いましたので、この関数のFunction URLにアクセスします。
$ curl https://24ugu7yzkz6jl3lqrskebwwpoa0gnymj.lambda-url.ap-northeast-1.on.aws/
[
{
"center": "Kennedy Space Center",
"center_search_status": "Public",
"facility": "Control Room 2/1726/HGR-S ",
"occupied": "1957-01-01T00:00:00.000",
"record_date": "1996-03-01T00:00:00.000",
"last_update": "2015-06-22T00:00:00.000",
"country": "US",
"contact": "Sheryl Chaffee",
"phone": "321-867-8047",
"location": {
"latitude": "28.538331",
"longitude": "-81.378879",
"human_address": "{\"address\": \"\", \"city\": \"\", \"state\": \"\", \"zip\": \"32899\"}"
},
"city": "Kennedy Space Center",
"state": "FL",
"zipcode": "32899",
":@computed_region_bigw_e76g": "173",
":@computed_region_cbhk_fwbd": "30",
":@computed_region_nnqa_25f4": "1078"
},
...
]
Code language: Bash (bash)
JSONファイルの中身が返ってきました。
Lambdaレイヤーにこのファイルが含まれており、関数からこのファイルにアクセスできることがわかります。
まとめ
CloudFormationカスタムリソースを使用することによって、一般ファイルを含むLambdaレイヤー作成を自動化することができました。