はじめに
『S3にオブジェクトが作成されると、EventBridgeを利用して後続のStepFunctionsを起動して、、、』のような案件を経験して、完全に『S3からのイベント通知の際はEventBridgeだ!』という凝り固まった偏見を持っていました。
全然そんなことはなく、S3のイベント通知にはLambda関数
,SNSトピック
,SQS キュー
の方法がありますので、凝り固まらないように『S3にオブジェクトが生成されるとイベント通知を行い、Lambdaが実行される』構築を行っていきます。
構成図
ハンズオン
構築の流れ
1.Lambda作成
2.S3作成
上記の順番で構築を行なっていきます。
最終的には、S3からのイベント通知を受けて、Lambdaのログ(CloudWatchLogs)が出力されれば検証
完了とします。
下記画像のようにBUCKET NAME
とOBJECT NAME
、CONTENT TYPE
を取得して、ログに出力します。
1.Lambda作成
コード参考:AWS Lambda デベロッパーガイド : チュートリアル: Amazon S3 トリガーを使用して Lambda 関数を呼び出す
上記URLを参考にして、下記S3からのイベント通知の必要な情報を取得します。
今回は通知より、バケット名とオブジェクト名を取得しています。
ついでにバケット名とオブジェクト名を利用して、S3バケットのコンテンツタイプを取得もしています。
Type: "AWS::Lambda::Permission"
を記述することで、S3からのイベント通知からLambdaをトリガーするようにしています。
下記記載方法だとSourceArn
の対象がarn:${AWS::Partition}:s3:::*
と、ワイルドカードを利用しているので、どのS3であっても通知を設定することでLambdaを呼び出せるような状態です(ワイルドカード部分を具体的なS3バケット名にすることで限定的にすることが可能です。)
AWSTemplateFormatVersion: '2010-09-09'
Description:
Lambda Create
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Lambda Configuration"
Parameters:
- FunctionName
- Description
- Handler
- MemorySize
- Runtime
- Timeout
- TagsName
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
FunctionName:
Type: String
Default: "cfn-lmd-inamura"
Description:
Type: String
Default: "cfn-lmd-inamura"
Handler:
Type: String
Default: "index.lambda_handler"
MemorySize:
Type: String
Default: "128"
Runtime:
Type: String
Default: "python3.9"
Timeout:
Type: String
Default: "10"
TagsName:
Type: String
Description: UserName
Default: "inamura"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
Lambda:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: |
import json
import urllib.parse
import boto3
print('Loading function')
s3 = boto3.client('s3')
def lambda_handler(event, context):
bucket = urllib.parse.unquote_plus(event['Records'][0]['s3']['bucket']['name'], encoding='utf-8')
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
print("BUCKET NAME:" + bucket)
print("OBJECT NAME:" + key)
# Get the object from the event and show its content type
try:
response = s3.get_object(Bucket=bucket, Key=key)
print("CONTENT TYPE: " + response['ContentType'])
return response['ContentType']
except Exception as e:
print(e)
print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
raise e
Description: !Ref Description
FunctionName: !Ref FunctionName
Handler: !Ref Handler
MemorySize: !Ref MemorySize
Runtime: !Ref Runtime
Timeout: !Ref Timeout
Role: !GetAtt LambdaRole.Arn
Tags:
- Key: "User"
Value: !Ref TagsName
LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${FunctionName}-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "sts:AssumeRole"
Principal:
Service: lambda.amazonaws.com
Policies:
- PolicyName: !Sub "${FunctionName}-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "logs:CreateLogGroup"
Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"
- Effect: "Allow"
Action:
- "s3:*"
Resource: !Sub "arn:${AWS::Partition}:s3:::*"
TriggerLambdaPermission:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt Lambda.Arn
Principal: "s3.amazonaws.com"
SourceArn: !Sub "arn:${AWS::Partition}:s3:::*"
# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#
Outputs:
LambdaArn:
Value: !GetAtt Lambda.Arn
Export:
Name: !Sub "${FunctionName}-arn"
LambdaName:
Value: !Ref FunctionName
Export:
Name: !Sub "${FunctionName}-name"
2.S3作成
NotificationConfiguration
部分でLambdaに対して通知する条件を記載しています。
今回通知するための条件としてprefix
がinfra
の場合、且つsuffix
がzip
の場合、Function
として1.Lambda作成
で構築したLambdaを呼び出します。
S3のバケット名は全世界でユニークのため、cfn-s3-20221217-inamura
部分は各自修正ください
※現在既にcfn-s3-20221217-inamuraは削除されております
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation to create S3 Bucket
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "S3 Configuration"
Parameters:
- S3BucketName
- AccessControl
- BlockPublicAcls
- BlockPublicPolicy
- IgnorePublicAcls
- RestrictPublicBuckets
- ExpirationInDays
- EventBridgeConfiguration
- Prefix
- Suffix
- TagsName
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
S3BucketName:
Type: String
Default: "cfn-s3-20221217-inamura"
Description: Type of this BacketName.
VersioningConfiguration:
Type: String
Default: "Enabled"
Description: VersioningConfiguration.
AccessControl:
Type: String
Description: AccessControl.
Default: "Private"
AllowedValues: [ "Private", "PublicRead", "PublicReadWrite", "AuthenticatedRead", "LogDeliveryWrite", "BucketOwnerRead", "BucketOwnerFullControl", "AwsExecRead" ]
BlockPublicAcls:
Type: String
Description: BlockPublicAcls.
Default: "True"
AllowedValues: [ "True", "False" ]
BlockPublicPolicy:
Type: String
Description: BlockPublicPolicy.
Default: "True"
AllowedValues: [ "True", "False" ]
IgnorePublicAcls:
Type: String
Description: IgnorePublicAcls.
Default: "True"
AllowedValues: [ "True", "False" ]
RestrictPublicBuckets:
Type: String
Description: RestrictPublicBuckets.
Default: "True"
AllowedValues: [ "True", "False" ]
ExpirationInDays:
Type: String
Description: Lifecycle Days.
Default: "7"
Prefix:
Type: String
Description: Lambdafunction Trigger Prefix.
Default: "infra"
Suffix:
Type: String
Description: Lambdafunction Trigger Suffix
Default: "zip"
TagsName:
Type: String
Description: UserName
Default: "inamura"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
VersioningConfiguration:
Status: !Ref VersioningConfiguration
AccessControl: !Ref AccessControl
PublicAccessBlockConfiguration:
BlockPublicAcls: !Ref BlockPublicAcls
BlockPublicPolicy: !Ref BlockPublicPolicy
IgnorePublicAcls: !Ref IgnorePublicAcls
RestrictPublicBuckets: !Ref RestrictPublicBuckets
LifecycleConfiguration:
Rules:
- Id: LifeCycleRule
Status: Enabled
ExpirationInDays: !Ref ExpirationInDays
NotificationConfiguration:
LambdaConfigurations:
- Event: "s3:ObjectCreated:*"
Filter:
S3Key:
Rules:
- Name: prefix
Value: !Ref Prefix
- Name: suffix
Value: !Ref Suffix
Function: !ImportValue cfn-lmd-inamura-arn
Tags:
- Key: "User"
Value: !Ref TagsName
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
S3BucketName:
Value: !Ref S3Bucket
Export:
Name: cfn-s3-BucketName
挙動の確認
①S3のイベント通知
を確認する
フィルターとしてinfra
、zip
が条件として付与されて、送信先もLambda
となっています。
編集
を押下するこで、より詳細に(プレフィックス、サフィックスの確認、イベントタイプ等)
②infra.zip
を保存する
Lambdaのログストリームでの画面から、プレフィックス:infra
で、サフィックス:zip
の場合でないと、後続のLambdaに通知は行われていないようです。
③LambdaのCloudWatchLogsを確認する
Lambdaのログに取得しようと考えていたBUCKET NAME
とOBJECT NAME
、CONTENT TYPE
が出力できました。
さいごに
もとより多くない知識なので、『これをするには、これだ!』と決めつけず、ベストプラクティスや自分で考えて構築した無駄とも思える量を携えながら、構築できるものを増やしていきたいと思います。