はじめに
今回もCloudFormationを利用して、S3にオブジェクトを保存するとEventBridgeが検知をして後続のLambdaをターゲットにし、LambdaからSNSを自分宛に送信するような構築をしていきます。
S3からオブジェクト通知でSNSという経路もありますが、自分のリソース理解の意味も含め構築をしていきます。
構成図
ハンズオン
構築の流れ
1.S3作成
2.SNS作成
3.Lambda作成
4.EventBridge作成
上記の順番で構築を行なっていきます。
最終的にS3にオブジェクトを保存すると、SNSで登録した宛先にメールが届きます。
1.S3作成
10回ほどS3にオブジェクトを保存してEventBridgeに通知されないことを不審に思い、公式ドキュメントを再読。
NotificationConfiguration
の記述をすることで、後続のEventBridge
に通知を送ることが出来るようになることを知りました。
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
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
S3BucketName:
Type: String
Default: "cfn-s3-XXXXXXXX"
Description: Type of this BacketName.
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"
EventBridgeConfiguration:
Type: String
Description: EventBridgeConfiguration.
Default: "true"
AllowedValues: [ "true", "false" ]
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
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:
EventBridgeConfiguration:
EventBridgeEnabled: !Ref EventBridgeConfiguration
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
S3BucketName:
Value: !Ref S3Bucket
Export:
Name: !Ref S3BucketName
### 2.SNS作成
2.1 CloudFormationでSNSトピック及びサブスクリプションを作成する
自身のメールアドレスにSNSから通知がるように設定をします
AWSTemplateFormatVersion: "2010-09-09"
Description: SNS Create
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "SNS Configuration"
Parameters:
- TopicName
- Endpoint
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
TopicName:
Type: String
Default: "cfn-sns-topic-inamura"
Endpoint:
Type: String
Default: "XXXXXXXXXX@gmail.com"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
SNSTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Ref TopicName
Subscription:
- Endpoint: !Ref Endpoint
Protocol: email
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
LambdaArn:
Value: !Ref SNSTopic
Export:
Name: !Sub "${TopicName}-arn"
2.2 上記設定後に、SNSから送られてきたメールのサブスクリプションを押下する
②画面が遷移して下記画面が表示されると、サブスクリプションが開始される
※上記青枠部分をクリックすると、サブスクリプションが解除される
3.Lambda作成
EventBridgeをトリガーにして起動するLambdaです。
通知内容を構成して、CDK(boto3)を利用してSNSでメールを送信します。
20回程度動かして連続して同じ内容を送信できないことを知りました。
その解決方法として件名部分にdatetime
を利用してユニークにしています。
今思えば折角くEventBridgeからの通知を受け取っているので、アップロード時間やオブジェクト名など、もっとスマートなやり方はあると思いますが、今回は検証ということで簡易な方法で済ましています。
AWSTemplateFormatVersion: '2010-09-09'
Description:
Lambda Create
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Lambda Configuration"
Parameters:
- FunctionName
- Description
- Handler
- MemorySize
- Runtime
- Timeout
# ------------------------------------------------------------#
# 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: "180"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
Lambda:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: |
import boto3
import os
import datetime
client = boto3.client('sns')
SNS = os.environ['SNS']
Subject = "【件名】SNS通知 "
Message = "S3にオブジェクトが作成されました"
def lambda_handler(event, context):
date = datetime.datetime.now()
d = date.strftime('%Y%m%d %H:%M:%S')
params = {
'TopicArn': SNS,
'Subject': Subject + str(d),
'Message': Message
}
client.publish(**params)
Description: !Ref Description
FunctionName: !Ref FunctionName
Handler: !Ref Handler
MemorySize: !Ref MemorySize
Runtime: !Ref Runtime
Timeout: !Ref Timeout
Environment:
Variables:
SNS: !ImportValue cfn-sns-topic-inamura-arn
TZ: "Asia/Tokyo"
Role: !GetAtt LambdaRole.Arn
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:
- "sns:Publish"
Resource: "*"
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
LambdaArn:
Value: !GetAtt Lambda.Arn
Export:
Name: !Sub "${FunctionName}-arn"
4.EventBridge作成
S3にオブジェクトが作成されたことをトリガーにして、ターゲットをLambdaにしているEventBridgeです。
30回程度S3にオブジェクトを作成した後、 Type: AWS::Lambda::Permission
が記載されていないことに気がつきました。
記載することでEventBridge
からLambda
に対してトリガーを設定することが出来ました。
AWSTemplateFormatVersion: "2010-09-09"
Description:
EventBridge gets s3 events and sends them to lambda
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Eventbridge Configuration"
Parameters:
- Name
- EventBusName
- State
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
EventBusName:
Type: String
Default: "default"
Name:
Type: String
Default: "cfn-SendLambdaS3Events-inamura"
State:
Type: String
Default: "ENABLED"
# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------#
Resources:
S3EventsRule:
Type: AWS::Events::Rule
Properties:
Description: "Get S3 events and send to Lambda"
EventBusName: !Ref EventBusName
Name: !Ref Name
State: !Ref State
EventPattern:
source:
- aws.s3
detail-type:
- Object Created
detail:
bucket:
name:
- !ImportValue cfn-s3-XXXXXXXX
Targets:
- Arn: !ImportValue cfn-lmd-inamura-arn
Id: "cfn-lmd-inamura"
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: cfn-lmd-inamura
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt 'S3EventsRule.Arn'
挙動の確認
①構築したS3
にオブジェクトを保存(アップロード)する
②SNSでサブスクリプションを開始したメールアドレスに通知がくる
③EventBridgeのメトリクスを確認する
④Lambdaのログを確認して【ERROR】が出力されていない
さいごに
知らないため設定をしていなかったり、理解が浅くて設定を間違っていたなどの問題も多かったですが、最終的に自分の想定したような動きを構築することが出来て概ね満足しました。
ただし途中でも書いたように、EventBridge
を利用しているのだから、Date
ではなくS3にアップロードした時間を利用して件名をつけるなど、もう少しスマートに書けた部分は多かったと、誰に有益なのか分からない反省もしています。
まだまだ構築していきたいものは多いので、手を動かして知見を増やしていければと思います。