はじめに
前回S3 イベント通知でLambdaを実行する構築ハンズオンで、S3イベント通知で同期的にLambdaを呼び出しをしてみましたが、実際には非同期で処理をすることの方が多いかもと思いまして、S3 → SQS → Lambda → SNS
という構築のハンズオンしていきます。
・S3 → EventBridge → Lambda
呼び出しのパターン
S3オブジェクトをトリガーにした zip展開するLambda構築ハンズオン
・S3 → Lambda
呼び出しのパターン
S3 イベント通知でLambdaを実行する構築ハンズオン
構成図
挙動について
1.S3 に プレフィックスinfra
で、サフィックスzip
のオブジェクトを保存する
2.上記の条件を満たしている場合、SQSへイベント通知が行われる
3.SQSをトリガーにLambdaがSQSのキューを取得し、内容からSNSへメールを成形して送信する
【注意】Lambdaが失敗しても、SQSからのキューが再度送られるので再度Lambdaが呼び出されます。構築は挙動の確認までを1セットで検証ください。
10日間 で AWS Lambda 関数を 28億回 実行した話
LambdaのリトライをAWS SQSを使ってやってみる
4.登録したメールアドレスに、Lambdaで成形された内容が通知される
ハンズオン
構築のながれ
1.SNS作成:登録したメールアドレスに、S3に保存したオブジェクト名・バケット名を送信する
2.SQS作成:特定のオブジェクトが保存された場合、キューを作成する
3.S3作成:オブジェクトを保存する、特定の条件の場合にイベント通知をおこうなう
4.Lambda作成:SQSからキューを取得し、キューの情報からメール内容を生成して、メール送信をする
1.SNS作成:登録したメールアドレスに、S3に保存したオブジェクト名・バケット名を送信する
1.1 SNSを構築する
Configで非準拠判定された場合に通知する、SNSトピックを構築します。
TopicPolicy
部分は、検証のため制限していません。
AWSTemplateFormatVersion: "2010-09-09"
Description: SNS Create
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "SNS Configuration"
Parameters:
- TopicName
- Endpoint
ParameterLabels:
TopicName:
default: "TopicName"
Endpoint:
default: "MailAddress"
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
TopicName:
Type: String
Default: "cfn-sns-topic-inamura"
Endpoint:
Type: String
Default: "XXXXXXXXXX@gmail.com"
TagsValueUserName:
Type: String
Default: "inamura"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
SNSTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Ref TopicName
Subscription:
- Endpoint: !Ref Endpoint
Protocol: email
Tags:
- Key: "User"
Value: !Ref TagsValueUserName
TopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
Topics:
- !Ref SNSTopic
PolicyDocument:
Id: !Ref SNSTopic
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS: "*"
Action: SNS:Publish
Resource: !Ref SNSTopic
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
SNSArn:
Value: !Ref SNSTopic
Export:
Name: !Sub "${TopicName}-arn"
SNSTopicName:
Value: !Ref TopicName
Export:
Name: !Ref TopicName
1.2 上記設定後に、SNSから送られてきたメールのサブスクリプションを押下する
②画面が遷移して下記画面が表示されると、サブスクリプションが開始される
※上記青枠部分をクリックすると、サブスクリプションが解除される
2.SQS作成:特定のオブジェクトが保存された場合、キューを作成する
S3のイベント通知を受領するSQSを構築します。
イベント通知の際に利用できるキューはFIFO
は対応していないため標準
での構築です。
FIFO
を利用することを考えたら、S3 → Lambda → SQS(FIFO) → Lambda
などの構成になるかと思います。
AWSTemplateFormatVersion: '2010-09-09'
Description: SQS Create
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "SQS Configuration"
Parameters:
- QueueName
- TagsValue
ParameterLabels:
QueueName:
default: "QueueName"
TagsValue:
default: "TagsValue"
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
QueueName:
Type: String
Default: "cfn-sqs-inamura"
TagsValue:
Type: String
Default: "inamura"
# ------------------------------------------------------------#
# SQS
# ------------------------------------------------------------#
Resources:
NotifySQS:
Type: AWS::SQS::Queue
Properties:
QueueName: !Ref QueueName
Tags:
- Key: "User"
Value: !Ref TagsValue
# ------------------------------------------------------------#
# SQS QueuePolicy
# ------------------------------------------------------------#
SQSPolicy:
Type: AWS::SQS::QueuePolicy
Properties:
Queues:
- !Ref NotifySQS
PolicyDocument:
Statement:
-
Action:
- "SQS:*"
Effect: "Allow"
Resource: !GetAtt NotifySQS.Arn
Principal:
AWS:
- "*"
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
NotifySQS:
Value: !GetAtt NotifySQS.QueueName
Export:
Name: !Sub "${QueueName}-queuename"
QueueArn:
Value: !GetAtt NotifySQS.Arn
Export:
Name: !Sub "${QueueName}-queuearn"
3.S3作成:オブジェクトを保存する、特定の条件の場合にイベント通知をおこうなう
NotificationConfiguration
部分でSQSに対して通知する条件を記載しています。
今回通知するための条件としてprefix
がinfra
の場合、且つsuffix
がzip
の場合、SQS
として2.SQS作成
で構築したSQSを呼び出します。
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:
QueueConfigurations:
- Event: "s3:ObjectCreated:*"
Filter:
S3Key:
Rules:
- Name: prefix
Value: !Ref Prefix
- Name: suffix
Value: !Ref Suffix
Queue: !ImportValue cfn-sqs-inamura-queuearn
Tags:
- Key: "User"
Value: !Ref TagsName
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
S3BucketName:
Value: !Ref S3Bucket
Export:
Name: cfn-s3-BucketName
4.Lambda作成:SQSからキューを取得し、キューの情報からメール内容を生成して、メール送信をする
Type: "AWS::Lambda::EventSourceMapping"
部分にSQS
からの通知を取得しにいくLambdaのトリガーとなる部分を記載します。
EventBridge
などではType: "AWS::Lambda::Permission"
でLambdaに他リソースからの許可を与えていましたが、SQS
に関してはSQS
に対してLambdaが通知を取得しにいくため記載の方法が異なります。
そのためLambdaにアタッチするRoleに、SQS
に対してのメッセージを取得を許可する下記記載の部分があります。
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 boto3
import os
import datetime
import urllib.parse
import json
print('Loading function')
sns_client = boto3.client('sns')
SNS = os.environ['SNS']
Subject = "【件名】SNS通知 "
Message = "S3にオブジェクトが作成されました"
def lambda_handler(event, context):
for record in event['Records']:
payload = record["body"]
events = json.loads(payload)
bucket = urllib.parse.unquote_plus(events['Records'][0]['s3']['bucket']['name'], encoding='utf-8')
key = urllib.parse.unquote_plus(events['Records'][0]['s3']['object']['key'], encoding='utf-8')
print("BUCKET NAME:" + str(bucket))
print("OBJECT NAME:" + str(key))
date = datetime.datetime.now()
d = date.strftime('%Y%m%d %H:%M:%S')
params = {
'TopicArn': SNS,
'Subject': Subject + str(d),
'Message': Message + "\n\n" + "S3バケット名 :" + str(bucket) + "\n" + "オブジェクト名:" + str(key)
}
sns_client.publish(**params)
Description: !Ref Description
FunctionName: !Ref FunctionName
Handler: !Ref Handler
MemorySize: !Ref MemorySize
Runtime: !Ref Runtime
Timeout: !Ref Timeout
Role: !GetAtt LambdaRole.Arn
Environment:
Variables:
SNS: !ImportValue cfn-sns-topic-inamura-arn
TZ: "Asia/Tokyo"
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:
- "sns:Publish"
Resource: "*"
- Effect: Allow
Action:
- sqs:DeleteMessage
- sqs:GetQueueAttributes
- sqs:ReceiveMessage
Resource:
- !ImportValue cfn-sqs-inamura-queuearn
EventSourceMapping:
Type: AWS::Lambda::EventSourceMapping
Properties:
Enabled: true
EventSourceArn: !ImportValue cfn-sqs-inamura-queuearn
FunctionName: !GetAtt Lambda.Arn
BatchSize: 1
# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#
Outputs:
LambdaArn:
Value: !GetAtt Lambda.Arn
Export:
Name: !Sub "${FunctionName}-arn"
LambdaName:
Value: !Ref FunctionName
Export:
Name: !Sub "${FunctionName}-name"
挙動の確認
①S3に プレフィックスinfra
で サフィックス zip
を満たしているオブジェクトを保存する
②SNS
で設定したメールアドレスにメールが送信される
③LambdaのCloudWatchLogsを確認する
さいごに
LambdaのトリガーをつくるのはType: "AWS::Lambda::Permission"
だと思い込んでいたところがあり、GUIで「LambdaのトリガーにSQSが反映されないなぁ。」なんて30分くらい待ちぼうけていたのは、AWSでの背後の動きがわかっていないことが如実にわかりました。
それとSQSからLambdaに対しての通知の受け取りも、逐一受け取ったEventから型を見ながら、なんとか取得できました。きっとスマートなやり方が世界にはあるはず。もっと勉強が必要だなぁと作ることを通して学ばされます。