1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

S3 イベント通知でSQSとLambdaを経由してSNS送信する構築ハンズオン

Posted at

はじめに

前回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に対して通知する条件を記載しています。
今回通知するための条件としてprefixinfraの場合、且つsuffixzipの場合、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から型を見ながら、なんとか取得できました。きっとスマートなやり方が世界にはあるはず。もっと勉強が必要だなぁと作ることを通して学ばされます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?