【CloudFormation/S3/Lambda】S3へのファイルアップロードをトリガーにLambdaを実行する<デプロイ編>
1.はじめに
今回は実装編で作成したLambda関数をAWS環境にデプロイします。
加えて、各リソースをテンプレート化して、CloudFormationでまとめてデプロ
イします。
本記事では実装編の内容を前提としていますので、まだ見ていない方は以下記事にも目を通していただけると嬉しいです。
この記事は実装編とデプロイ編の2部構成となっています。
- 実装編:PythonでのLambda関数実装が中心
- デプロイ編:「template.yaml」の記述方法・デプロイ後の動作確認方法が中心
今回はデプロイ編ということでtemplate.yamlの記述方法やデプロイ時の動作確認方法の説明をします。
本記事で実装したものは以下リポジトリに上げています。
2.準備
本記事では以下を前提とします。
- AWS環境があることを前提とします
- 実装編で作成したLambda関数が実装済みであること
- AWS SAMの環境構築を終えていること
- AWS SAM CLI利用の準備
■ ディレクトリ構成
本記事では以下のディレクトリ構成で進めます。
親ディレクトリ
┣ samconfig.toml ← デプロイ編で作成
┣ template.yaml ← デプロイ編で作成
┣ common_layer
┃ ┗ requirements.txt ← 実装編で作成
┗ s3_lambda_functions
┗ lambda_function.py ← 実装編で作成
3.template.yamlの作成
今回は「template.yaml」を元にCloudFormationから各リソースを作成します。
作成するリソースとしては以下の通りです。
作成するリソース | 説明 |
---|---|
S3バケット | ファイル格納先となるS3バケットです |
Lambda関数 | S3へのファイル格納をトリガーとして実行する関数 S3に格納されたファイル内容を取得します |
作成する「template.yaml」全体としては以下の通りです。
template.yamlの全体
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: S3 Lambda Application
# SAM テンプレートのバージョンを指定し、サーバーレスアプリケーションを定義
Globals:
Function:
Timeout: 5 # Lambda 関数のデフォルトのタイムアウト(5秒)
MemorySize: 128 # デフォルトのメモリサイズ(128MB)
Runtime: python3.11 # Python 3.11 をランタイムとして使用
Architectures:
- arm64 # Lambda 関数を ARM64 アーキテクチャで実行
LoggingConfig:
LogFormat: JSON # ログフォーマットを JSON 形式に設定
ApplicationLogLevel: INFO # ログレベルを INFO に設定
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: S3Application # Powertools のサービス名を指定
AWS_REGION_NAME: ap-northeast-1 # 使用する AWS リージョン
# パラメータセクション:デプロイ時に渡されるカスタムパラメータ
Parameters:
OverridesParam:
Type: String # パラメータの型を String に設定
Description: OverridesParam # パラメータの説明
# リソースセクション:Lambda 関数、S3 バケット、レイヤーなどの AWS リソースを定義
Resources:
# 共通の Lambda Layer を定義
CommonLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: CommonLayer # レイヤーの名前を定義
Description: Common Layer # レイヤーの説明
ContentUri: common_layer/ # レイヤーのソースコードの場所
CompatibleRuntimes:
- python3.11 # Python 3.11 ランタイムに対応
RetentionPolicy: Delete # 古いレイヤーバージョンを自動削除
Metadata:
BuildMethod: python3.11 # SAM によるビルド設定
BuildArchitecture: arm64 # ARM64 アーキテクチャでビルド
# S3 と連携する Lambda 関数を定義
S3LambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda_function.lambda_handler # Lambda 関数のエントリーポイント(ハンドラ)
CodeUri: src/s3_lambda_functions/ # 関数コードのパス
Description: S3 Lambda function # 関数の説明
Layers:
- !Ref CommonLayer # 共通レイヤーを関数に適用
Policies:
- Statement: # S3 バケットへのアクセス許可を設定
Effect: Allow
Action: # 許可されるアクション
- s3:ListBucket # バケットのリストを取得
- s3:GetObject # オブジェクトの取得を許可
Resource: # 許可するリソース(S3 バケット)
- !Sub "arn:aws:s3:::lambda-bucket-${OverridesParam}"
- !Sub "arn:aws:s3:::lambda-bucket-${OverridesParam}/*"
Events:
S3Event: # S3 イベントトリガーを設定
Type: S3 # S3 イベントタイプ
Properties:
Bucket: !Ref S3LambdaBucket # イベントがトリガーされるバケットを指定
Events: s3:ObjectCreated:* # オブジェクト作成時にトリガー
Filter: # イベントフィルター(特定のプレフィックスに限定)
S3Key:
Rules:
- Name: prefix
Value: public/ # "public/" プレフィックスで始まるオブジェクトのみ対象
Tags:
Name: "s3-lambda-function" # Lambda 関数に "s3-lambda-function" タグを追加
Timeout: 300 # タイムアウト(300秒)
# S3 が Lambda 関数をトリガーするための権限を設定
LambdaInvokePermissionForS3:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt S3LambdaFunction.Arn # Lambda 関数の ARN を取得
Action: "lambda:InvokeFunction" # 関数の実行権限を付与
Principal: "s3.amazonaws.com" # 実行元を S3 に制限
SourceArn: !GetAtt S3LambdaBucket.Arn # 実行権限を与える S3 バケットの ARN
# S3 バケットを定義
S3LambdaBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "lambda-bucket-${OverridesParam}" # パラメータに基づいたバケット名を指定
■ 手順1:Globalsの定義
それではリソース作成のための「template.yaml」を作成します。
まず、「Globals」を定義します。
「Globals」は、テンプレート内で定義されるリソースへの共通設定を定義できます。
これにより、Lambda関数が複数あった場合でも、共通の設定を一括して適用することができます。
Globals:
Function:
Timeout: 5
MemorySize: 128
Runtime: python3.11
Architectures:
- arm64
LoggingConfig:
LogFormat: JSON
ApplicationLogLevel: INFO
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: S3Application
AWS_REGION_NAME: ap-northeast-1
上位階層 | 項目 | 説明 | 設定値 |
---|---|---|---|
Function | Lambda関数の共通設定 | ||
Timeout | Lambda関数の最大実行時間(秒) | 5秒に設定 | |
MemorySize | Lambda関数に割り当てるメモリサイズ(MB) | 128MBに設定。 | |
Runtime | 使用するランタイム | python3.11を指定 | |
Architectures | Lambda関数のアーキテクチャ | arm64を指定 | |
LoggingConfig | Lambda関数のログに関わる設定 | ||
LogFormat | CloudWatchに出力されるログ形式 | JSON形式に指定 | |
ApplicationLogLevel | ログの出力レベル | INFOで指定 | |
Environment | 環境設定 | ||
POWERTOOLS_SERVICE_NAME | Lambdaのサービス名 | S3Application | |
AWS_REGION_NAME | AWSのリージョン | ap-northeast-1を指定 |
■ 手順2:Parametersの定義
「Parameters」を定義します。
「Parameters」は、テンプレートにカスタムパラメータを設定することができます。
カスタムパラメータは「samconfig.toml」から参照させることができます。
Parameters:
OverridesParam:
Type: String
Description: OverridesParam
上位階層 | 項目 | 説明 | 設定値 |
---|---|---|---|
Parameters | 変数として定義するパラメータ群 | ||
OverridesParam | 文字列型のパラメータとして定義しています 今回はS3バケット名の生成に使用しています 「samconfig.toml」から設定を呼び出します |
Type: String |
■ 手順3:Resourcesの定義
「Resources」を定義します。
「Resources」は、AWSのリソース(Lambda関数・S3バケット等)を定義できます。
CommonLayer
「CommonLayer」ではLambda関数で使用する共通ライブラリを定義します。
実装編で「common_layer」配下に「requirements.txt」を作成しましたが、
Lambda関数がデプロイされた際に「common_layer」配下の「requirements.txt」からライブラリをインストールさせるために下記リソースを定義しています。
CommonLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: CommonLayer
Description: Common Layer
ContentUri: common_layer/
CompatibleRuntimes:
- python3.11
RetentionPolicy: Delete
Metadata:
BuildMethod: python3.11
BuildArchitecture: arm64
上位階層 | 項目 | 説明 | 設定値 |
---|---|---|---|
Type | Lambda関数での共通ライブラリをレイヤーとして利用 | AWS::Serverless::LayerVersion | |
Properties | |||
LayerName | レイヤーの名前 | CommonLayer | |
ContentUri | レイヤーに含めるファイルがあるディレクトリを指定 | common_layer/ | |
CompatibleRuntimes | 互換性のあるランタイムを指定 | python3.11 | |
RetentionPolicy | 削除ポリシー Deleteの場合、リソース削除時に削除されます |
Delete |
S3LambdaFunction
実装編で作成したS3からのトリガーを受けて実行されるLambda関数の定義です。
S3LambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda_function.lambda_handler
CodeUri: src/s3_lambda_functions/
Description: S3 Lambda function
Layers:
- !Ref CommonLayer
Policies:
- Statement:
Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource:
- !Sub "arn:aws:s3:::lambda-bucket-${OverridesParam}"
- !Sub "arn:aws:s3:::lambda-bucket-${OverridesParam}/*"
Events:
S3Event:
Type: S3
Properties:
Bucket: !Ref S3LambdaBucket
Events: s3:ObjectCreated:*
Filter:
S3Key:
Rules:
- Name: prefix
Value: public/
Tags:
Name: "s3-lambda-function"
Timeout: 300
上位階層 | 項目 | 説明 | 設定値 |
---|---|---|---|
Type | Lambda関数を定義 | AWS::Serverless::Function | |
Properties | |||
Handler | Lambda関数のエントリポイント | lambda_function.lambda_handler 「ファイルパス.関数名」で指定します。 |
|
CodeUri | Lambda関数のコードがある場所 | 「src/s3_lambda_functions/」を指定 | |
Layers | 先ほど定義した CommonLayer を参照して使用します | !Ref CommonLayer | |
Policies | Lambda関数のポリシー | ||
Statement | S3の「ListBucket」と「GetObject」の権限を付与 設定名のS3バケットにアクセスできる |
Effect: Allow Action:「ListBucket」と「GetObject」 Resource:!Sub "arn:aws:s3:::lambda-bucket-${OverridesParam}" |
|
Events | Lamnda関数の実行条件 | ||
S3Event | S3へのファイル格納をトリガーとして設定 ※「S3バケット/public/」配下へのファイル格納をトリガー対象としています |
Events: s3:ObjectCreated:* Rules: - Name: prefix Value: public/ |
LambdaInvokePermissionForS3
S3がLambda関数を呼び出すための権限設定です。
LambdaInvokePermissionForS3:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt S3LambdaFunction.Arn
Action: "lambda:InvokeFunction"
Principal: "s3.amazonaws.com"
SourceArn: !GetAtt S3LambdaBucket.Arn
上位階層 | 項目 | 説明 | 設定値 |
---|---|---|---|
Type | S3が指定のLambda関数を呼び出すための権限を設定 | AWS::Lambda::Permission | |
Properties | |||
FunctionName | S3LambdaFunctionのARNを取得し指定 | !GetAtt S3LambdaFunction.Arn | |
Action | Lambda関数の実行を許可 | lambda:InvokeFunction | |
Principal | s3.amazonaws.comが呼び出し元であることを指定 | s3.amazonaws.com | |
SourceArn | S3LambdaBucketのARNを指定し、特定のバケットからのイベントのみを許可 | !GetAtt S3LambdaBucket.Arn |
S3LambdaBucket
Lambda関数で使用するS3バケットです。
S3LambdaBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "lambda-bucket-${OverridesParam}"
上位階層 | 項目 | 説明 | 設定値 |
---|---|---|---|
Type | S3バケットを定義 | AWS::S3::Bucket | |
Properties | |||
BucketName | バケット名 ${OverridesParam}パラメータを使って動的に設定 |
BucketName: !Sub "lambda-bucket-${OverridesParam}" |
4.samconfig.tomlの作成
続いて「samconfig.toml」を作成します。
「samconfig.toml」はAWS SAM (Serverless Application Model) を使用する際に、SAM CLI コマンドの設定を管理するためのファイルです。
このファイルにより、デプロイ・ビルド時のオプションや設定を明示的に指定することができます。
主な用途としては以下が挙げられます。
- デプロイ設定の自動化:「samconfig.toml」にデプロイ設定を保存しておくことで、簡単に同じ設定を使ってデプロイを行えます。
- 環境ごとの設定:開発環境・本番環境など、異なる環境に応じた設定を分けて管理できます。
作成する「samconfig.toml」全体としては以下の通りです。
samconfig.tomlの全体
# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1
[default]
[default.global.parameters]
stack_name = "s3-application"
[default.build.parameters]
cached = true
parallel = true
[default.validate.parameters]
lint = true
[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
parameter_overrides = ["OverridesParam='20240919'"]
[default.package.parameters]
resolve_s3 = true
[default.sync.parameters]
watch = true
[default.local_start_api.parameters]
warm_containers = "EAGER"
[default.local_start_lambda.parameters]
warm_containers = "EAGER"
■ S3バケット名のためのパラメータをsamconfig.tomlで定義
samconfig.tomlの説明は、「S3バケット名のためのパラメータを定義」部分のみ行います。
template.yaml上でS3バケット名に「lambda-bucket-${OverridesParam}」と定義しています。
この「${OverridesParam}」の部分はsamconfig.tomlの以下部分から呼び出されます。
parameter_overrides = ["OverridesParam='20240919'"]
したがって上記の場合、S3バケット名は「lambda-bucket-20240919」となります。
5.ビルド&デプロイ
それでは、実装物のビルドとデプロイを行います。
※AWS SAM CLIが使用できることを前提としています
以下のコマンドを実行することでビルドとAWS環境へのデプロイができます。
$ sam build
$ sam deploy --config-file samconfig.toml
デプロイが正常終了したことを確認します。
AWSのマネジメントコンソールを開き以下画面に遷移します。
- CloudFormation → スタック → s3-application
ステータスが「CREATE_COMPLETE」となっていることを確認します。
また、リソースタブでtemplate.yaml上で定義した各リソースの確認ができます。
※スタックを削除したい場合は以下コマンドでスタックを削除できます。
aws cloudformation delete-stack --stack-name s3-lambda-app
6.動作確認
Lambda関数の動作確認を行います。
以下が確認できたら成功です。
- S3バケット(lambda-bucket-20240919)に「public」フォルダを作成する
- 「public」フォルダの中にファイルをアップロードする
- Lambda関数が実行されることを確認する
- CloudWatchのログにファイル情報が出力されることを確認する
■ S3バケットに「public」フォルダを作成する
S3バケット(lambda-bucket-20240919)に「public」フォルダを作成します。
AWSのマネジメントコンソールを開き以下画面に遷移します。
- Amazon S3 → バケット → lambda-bucket-20240919
「フォルダの作成」から「public」フォルダを作成します。
「public」フォルダを作成する理由は、template.yaml上にLambda関数実行のトリガーとして、「public」フォルダを指定しているためです。
Events:
S3Event:
Type: S3
Properties:
Bucket: !Ref S3LambdaBucket
Events: s3:ObjectCreated:*
Filter:
S3Key:
Rules:
- Name: prefix
Value: public/ #
■ 「public」フォルダにファイルをアップロードする
「public」フォルダの中にファイルをアップロードします。
今回はテキストファイルをアップロードしてみます。
■ Lambda関数が実行されることを確認する
Lambda関数が実行されることを確認します。
AWSのマネジメントコンソールを開き以下画面に遷移します。
- Lambda → 関数 → s3-application-S3LambdaFunction-XXXXXX
- 「モニタリング」タブ → 「CloudWatch ログを表示」をクリック
以下のようにログストリームが出力されていればLambda関数が実行されています。
■ CloudWatchのログにファイル情報が出力されることを確認する
CloudWatchのログにファイル情報が出力されることを確認します。
対象のログストリームをクリックすると、以下画面が表示されます。
赤枠部分がLambda関数の「logger.info()」によって出力されたログとなります。
7.ログを見てみる
ソースコード上の各loggerで出力されたログを確認します。
ソースコードの全体
import json
from urllib.parse import unquote_plus
import boto3
from aws_lambda_powertools import Logger
logger = Logger()
def lambda_handler(event, context):
logger.info(json.dumps(event))
# バケット名
bucket_name = event["Records"][0]["s3"]["bucket"]["name"]
logger.info(bucket_name)
# オブジェクトキー
object_key = unquote_plus(event["Records"][0]["s3"]["object"]["key"])
logger.info(object_key)
# ETag
etag = event["Records"][0]["s3"]["object"]["eTag"]
logger.info(etag)
# サイズ
size = event["Records"][0]["s3"]["object"]["size"]
logger.info(size)
# S3にアップロードされたファイル内容を取得
s3_client = boto3.client("s3")
response = s3_client.get_object(Bucket=bucket_name, Key=object_key)
logger.info(response)
file_content = response["Body"].read()
logger.info(file_content)
■ eventログ:logger.info(json.dumps(event))
はじめにeventログを確認します。
ソースコード上は以下部分です。
def lambda_handler(event, context):
logger.info(json.dumps(event))
eventログを見ると以下のようなJSON形式で出力されていることが分かります。
これは、template.yaml上で「LogFormat: JSON」と指定しているためです。
eventの中身全体
{
"level": "INFO",
"location": "lambda_handler:11",
"message": {
"Records": [
{
"eventVersion": "2.1",
"eventSource": "aws:s3",
"awsRegion": "ap-northeast-1",
"eventTime": "2024-09-19T15:23:44.430Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "ダミー"
},
"requestParameters": {
"sourceIPAddress": "ダミー"
},
"responseElements": {
"x-amz-request-id": "ダミー"
"x-amz-id-2": "ダミー"
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "ダミー",
"bucket": {
"name": "lambda-bucket-20240919",
"ownerIdentity": {
"principalId": "ダミー"
},
"arn": "arn:aws:s3:::lambda-bucket-20240919"
},
"object": {
"key": "public/test.txt",
"size": 23,
"eTag": "qawsedrftghyujikolp123456789",
"sequencer": "0123456789ABCDEFG"
}
}
}
]
},
"timestamp": "2024-09-19 15:23:46,659+0000",
"service": "S3Application",
"xray_trace_id": "ダミー"
}
また、「s3」部分には、バケット名やオブジェクトキーなども出力されていることが分かります。
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "ダミー",
"bucket": {
"name": "lambda-bucket-20240919",
"ownerIdentity": {
"principalId": "ダミー"
},
"arn": "arn:aws:s3:::lambda-bucket-20240919"
},
"object": {
"key": "public/test.txt",
"size": 23,
"eTag": "qawsedrftghyujikolp123456789",
"sequencer": "0123456789ABCDEFG"
}
}
そして、S3ログの各要素には以下のようにアクセスできます。
# バケット名
bucket_name = event["Records"][0]["s3"]["bucket"]["name"]
logger.info(bucket_name)
# オブジェクトキー
object_key = unquote_plus(event["Records"][0]["s3"]["object"]["key"])
logger.info(object_key)
# ETag
etag = event["Records"][0]["s3"]["object"]["eTag"]
logger.info(etag)
# サイズ
size = event["Records"][0]["s3"]["object"]["size"]
logger.info(size)
■ ファイル内容ログ
最後にboto3で取得したファイル内容ログを確認します。
ソースコード上は以下部分です。
s3_client = boto3.client("s3")
response = s3_client.get_object(Bucket=bucket_name, Key=object_key)
logger.info(response)
file_content = response["Body"].read()
logger.info(file_content)
まず「logger.info(response)」を確認します。
responseの中身全体
{
"level": "INFO",
"location": "lambda_handler:32",
"message": {
"ResponseMetadata": {
"RequestId": "ダミー",
"HostId": "ダミー",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"x-amz-id-2": "ダミー",
"x-amz-request-id": "ダミー",
"date": "Thu, 19 Sep 2024 16:18:25 GMT",
"last-modified": "Thu, 19 Sep 2024 16:18:21 GMT",
"etag": "\"qawsedrftghyujikolp123456789\"",
"x-amz-server-side-encryption": "AES256",
"accept-ranges": "bytes",
"content-type": "text/plain",
"server": "AmazonS3",
"content-length": "23"
},
"RetryAttempts": 0
},
"AcceptRanges": "bytes",
"LastModified": "2024-09-19 16:18:21+00:00",
"ContentLength": 23,
"ETag": "\"qawsedrftghyujikolp123456789\"",
"ContentType": "text/plain",
"ServerSideEncryption": "AES256",
"Metadata": {},
"Body": "<botocore.response.StreamingBody object at 0xffff813ab820>"
},
"timestamp": "2024-09-19 16:18:24,746+0000",
"service": "S3Application",
"xray_trace_id": "ダミー"
}
「Body」部分を見ると、StreamingBodyオブジェクトが取得されていることが分かります。
このオブジェクトは、ファイルのデータをストリームとして読み出せるようにしています。
"Body": "<botocore.response.StreamingBody object at 0xffff813ab820>"
そして、「Body」を以下のように参照しログを出力すると、
file_content = response["Body"].read()
logger.info(file_content)
以下のようにファイル内容(Hello World!)が出力されていることが分かります。
{
"level": "INFO",
"location": "lambda_handler:34",
"message": "b'Hello World!'",
"timestamp": "2024-09-19 16:18:24,747+0000",
"service": "S3Application",
"xray_trace_id": "ダミー"
}
8.おわりに
今回のデプロイ編では、実装編の成果物をAWS環境にデプロイする手順について説明しました。
また、S3にファイルをアップロードすることでLambda関数がトリガーされ、CloudWatchログを見ながらLambda関数の処理結果を確認しました。
実装編とデプロイ編を合わせて以下一連の流れを説明していますので、
見ていない方は実装編も見ていただけると幸いです。
- Lambda関数の実装方法
- SAMを用いたAWSリソースの自動デプロイ
- S3イベントによる自動処理の一連のフローを実装する方法
付録
スタックを削除する際、S3バケットが空でないと削除できません。
以下コマンドでS3バケット内を再帰的に削除できます。
aws s3 rm s3://バケット名 --recursive
S3バケットを空にした上で以下コマンドでスタックの削除ができます。
aws cloudformation delete-stack --stack-name スタック名
以下コマンドでスタックのステータスの確認ができます。
aws cloudformation describe-stacks --stack-name スタック名
削除に成功した場合、以下のようにメッセージが表示されます。
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id s3-application does not exist
Discussion