Lambda + DynamoDB を利用したサーバレス構築ハンズオン

はじめに

普段DynamoDBをあまり使用しないので、CFnの練習がてら構築していきたいと思います。

学習に利用した教本は、AWS Lambda実践ガイド 第2版 (impress top gear) 著:大澤文孝
を利用しています。
GUI及び、SAMによる構築はありましたが、CFnでの構築はなかったので、書籍のエッセンスを使いつつ、自分なりに少しアーキテクトを変更して構築をしてみました。


構成図


ハンズオン

構築のながれ

1.S3作成:SNSメール雛形保存

2.SNS作成:メール通知のため構築

3.SQS作成:LambdaとLambdaを疎結合で構築

4.Lambda(sendmail)作成:メール未送信ユーザにメール通知の構築

5.DynamoDB作成:ユーザ名/アドレス/二重配信チェック DBの構築

6.Lambda(sendqueue)作成:DynamoDBに該当カラムを持つ場合 SQSを発行する構築

7.EventBridge作成:CronでLambda(sendqueue)とトリガーを構築


1.S3作成:SNSメール雛形保存

1.1.CFn で S3 構築

SNSで送信する通知内容を保存するためのバケットを作成

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
        - TagsName

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  S3BucketName:
    Type: String
    Default: "cfn-s3-20230102-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"
  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
    # #イベント通知 Lambda構築後コメントアウト解除
    #  NotificationConfiguration:
    #    LambdaConfigurations:
    #      - Event: "s3:ObjectCreated:*"
    #        Function: !ImportValue cfn-lmd-sendqueue-inamura-arn
    # #ここまで
      Tags:
        - Key: "User"
          Value: !Ref TagsName
# ------------------------------------------------------------#
#  Outputs
# ------------------------------------------------------------#
Outputs:
  S3BucketName:
    Value: !Ref S3Bucket
    Export:
      Name: cfn-s3-BucketName

1.2.S3 に SNSメール雛形保存

template-mail.txtとしてSNSで送信する通知内容を、S3へ保存する

メール配信:テスト

【テスト内容】新規ユーザが登録されました

コンソール画面からだと下記のような状態となる

2.SNS作成:メール通知のため構築

2.1 SNSを構築する

EventBridgeをトリガーにして、DynamoDBに登録したユーザにメール通知がされていない場合に、こちらの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

2.2 上記設定後に、SNSから送られてきたメールのサブスクリプションを押下する

①送られてきたメールの赤枠部分をクリックする

②画面が遷移して下記画面が表示されると、サブスクリプションが開始される

※上記青枠部分をクリックすると、サブスクリプションが解除される

3.SQS作成:LambdaとLambdaを疎結合で構築

・2つの処理をするLambdaの間を疎結合するために構築
・今回はDynamoDBとLambda側で二重配信チェックを含めたため、標準キューを利用
可視性タイムアウトLambdaのタイムアウトを10秒としているため30秒と設定
・10分ごとにCronされるため保持期間を9分と設定
・2回通知を失敗すると、DLQへ移行し、DLQのメッセージ保持期間は1時間と設定

AWSTemplateFormatVersion: '2010-09-09'
Description: SQS Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "SQS Configuration"
        Parameters:
        - QueueName
        - DeadLetterQueueName
        - TagsValue

    ParameterLabels:
      QueueName:
        default: "QueueName"
      DeadLetterQueueName:
        default: "DeadLetterQueueName"
      TagsValue:
        default: "TagsValue"

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  QueueName:
    Type: String
    Default: "cfn-sqs-inamura"
  DeadLetterQueueName:
    Type: String
    Default: "cfn-dlq-inamura"
  TagsValue:
    Type: String
    Default: "inamura"

# ------------------------------------------------------------#
#  SQS
# ------------------------------------------------------------#
Resources:
  NotifySQS:
    Type: AWS::SQS::Queue
    Properties: 
      QueueName: !Ref QueueName
      VisibilityTimeout: 30
      MessageRetentionPeriod: 540
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
        maxReceiveCount: 2
      Tags: 
        - Key: "User"
          Value: !Ref TagsValue

  DeadLetterQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Ref DeadLetterQueueName
      MessageRetentionPeriod: 3600
      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: 
                - "*"  
          - 
            Action: 
              - "SQS:*"
            Effect: "Allow"
            Resource: !GetAtt DeadLetterQueue.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"
  DeadLetterQueue:
    Value: !GetAtt DeadLetterQueue.QueueName
    Export:
      Name: !Sub "${DeadLetterQueueName}-queuename"
  DeadLetterQueueArn:
    Value: !GetAtt DeadLetterQueue.Arn
    Export:
      Name: !Sub "${DeadLetterQueueName}-queuearn"

4.Lambda(sendmail)作成:メール未送信ユーザにメール通知の構築

SQSからメッセージを取得
S3に保存しているtemplate-mail.txtより0行目2行目を取得してメールで必要な変数を取得する
DynamoDBsend1updateする。ただしReturnする値はアップデート前のものとすることで、既に送信したのか?していないのか?を判定
・上記判定をしてupdateする前の値が0であれば送っていないことになるのでSNSでメール通知する
・メール通知したのでカラムnotsend1にすることで、次回の判定から除かれるように更新
※ IAM部分のアカウトIDは XXXXXXXXXXXXとなっていますので、自身のアカウントIDを利用ください

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-sendmail-inamura"
  Description:
    Type: String
    Default: "cfn-lmd-sendmail-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 json
          import os
          import datetime

          sqs = boto3.resource('sqs')
          s3 = boto3.resource('s3')
          dynamodb = boto3.resource('dynamodb')
          table = dynamodb.Table('mailaddress')
          sns = boto3.client('sns')
          snsarn = os.environ['SNSARN']

          def lambda_handler(event, context):
            #SQSからメッセージ取得
            for rec in event['Records']:
              email = rec['body']
              bucket = rec['messageAttributes']['bucket']['stringValue']
              template = rec['messageAttributes']['template']['stringValue']
              username = rec['messageAttributes']['username']['stringValue']

              #S3メールテンプレートから本文生成
              obj = s3.Object(bucket, template)
              r = obj.get()
              maildata = r['Body'].read().decode('utf-8')
              data = maildata.split("\n", 3)
              subject =  data[0]
              body = data[2]

              #2重通知防止の仕組み
              #DynamoDB sendするので1をUpdate ただしReturnでUpdate前を返す
              r = table.update_item(
                Key = {
                  'email' : email
                  },
                  UpdateExpression = "set send=:val",
                  ExpressionAttributeValues = {
                    ':val' : 1
                  },
                  ReturnValues = 'UPDATED_OLD'
                  )

              #条件分岐 未送信(Updateする前の UPDATED_OLD が 0) の場合SNS送信 
              if r['Attributes']['send'] == 0:
                date = datetime.datetime.now()
                d = date.strftime('%Y%m%d %H:%M:%S')

                params = {
                'TopicArn': snsarn,
                'Subject': subject + str(d),
                'Message': body
                }
                sns.publish(**params)

                #DynamoDB sendしたので、notsendを1にUpdate
                r =table.update_item(
                  Key = {
                    'email' : email
                    },
                    UpdateExpression = "set notsend=:val",
                    ExpressionAttributeValues = {
                      ':val' : 1
                    }
                  )
              else:
                print("Resend Skip")

      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn
      Environment:
        Variables:
          TZ: "Asia/Tokyo"
          SNSARN: !ImportValue cfn-sns-topic-inamura-arn
      Tags:
        - Key: "User"
          Value: !Ref TagsName
      DeadLetterConfig:
        TargetArn: !ImportValue cfn-dlq-inamura-queuearn

  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:
                  - "sqs:*" 
                Resource: "arn:aws:sqs:ap-northeast-1:XXXXXXXXXXXX:*"

              - Effect: "Allow"
                Action:
                  - "s3:*" 
                Resource: "*"

              - Effect: "Allow"
                Action:
                  - "dynamodb:*" 
                Resource: "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:*"

              - Effect: "Allow"
                Action:
                  - "sns:Publish" 
                Resource: "*"

  EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties: 
      Enabled: true
      EventSourceArn: !ImportValue cfn-sqs-inamura-queuearn
      FunctionName: !GetAtt Lambda.Arn
      BatchSize: 10

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"
  LambdaName:
    Value: !Ref FunctionName
    Export:
      Name: !Sub "${FunctionName}-name"

5.DynamoDB作成:ユーザ名/アドレス/二重配信チェック DBの構築

5.1.CFnでDynamoDBを構築

AWSTemplateFormatVersion: '2010-09-09'
Description:
  DynamoDB Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "DynamoDB Configuration"
        Parameters:
        - TableName
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  TableName:
    Type: String
    Default: "mailaddress"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
  DynamoDB:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: !Ref TableName
      AttributeDefinitions:
        - AttributeName: email
          AttributeType: S
        - AttributeName: notsend
          AttributeType: N
      KeySchema:
        - AttributeName: email
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      GlobalSecondaryIndexes:
        - IndexName: notsend-index
          KeySchema:
            - AttributeName: notsend
              KeyType: HASH
          Projection:
            ProjectionType: ALL
          ProvisionedThroughput:
            ReadCapacityUnits: 1
            WriteCapacityUnits: 1
      Tags: 
        - Key: "username"
          Value: "inamura"

Outputs:
  DynamoDBArn:
    Value: !GetAtt DynamoDB.Arn
    Export:
      Name: !Sub "cfn-dynamodb-${TableName}"

5.2.DynamoDBに値を登録

DynamoDBに値を登録するためにAWS CLIを利用

5.2.1.jsonファイルを用意
{
  "mailaddress": [
      {
          "PutRequest": {
              "Item": {
                  "email": {"S":"XXXXXXXXXX@gmail.com"},
                  "username": {"S":"InamuraTeppei"},
                  "notsend": {"N":"0"},
                  "send": {"N":"1"}
              }
          }
      }
  ]
}

5.2.2.AWS CLIを利用してデータをインポート

・下記コマンドをターミナルから入力してDynamoDBのテーブルへ反映させる

aws dynamodb batch-write-item --request-items file://mailaddress.json

5.2.3.マネジメントコンソールから確認

DynamoDB > 左ペイン:テーブル の 項目を探索 > 確認したいテーブルを選択

6.Lambda(sendqueue)作成:DynamoDBに該当カラムを持つ場合 SQSを発行する構築

・後続手順でEventBridgeを構築した後にコメントアウトしているパーミッション(AWS::Lambda::Permission)部分を修正する
DynamoDBのテーブルから notsendが 0 のアイテムを取得
・取得したアイテムのsendを 0 に更新
notsendが 0 であれば、一度送っていてもSQSで後続のLambda(sendmail)へ値を渡し、後続Lambdaでsendしているかどうか?をチェックする仕組み
SQSにメッセージとして登録
※ IAM部分のアカウトIDは XXXXXXXXXXXXとなっていますので、自身のアカウントIDを利用ください

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-sendqueue-inamura"
  Description:
    Type: String
    Default: "cfn-lmd--sendqueue-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
    Default: "inamura"
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import boto3
          import urllib.parse
          import json
          from boto3.dynamodb.conditions import Key, Attr
          import os

          BUCKET = os.environ['S3']
          TEMPLATE = os.environ['TEMPLATE']

          def lambda_handler(event, context):
            print('Loading function')

            #DynamoDB boto3操作 Tableオブジェクト取得
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('mailaddress')

            #SQS boto3操作 キュー取得
            sqs = boto3.resource('sqs')
            queue = sqs.get_queue_by_name(QueueName='cfn-sqs-inamura')

            #環境変数からバケット名 オブジェクト名取得
            bucket = BUCKET
            template = TEMPLATE

            #DynamoDB(table) から notsend 0 を絞り込んで取得
            response = table.query(
              IndexName='notsend-index',
              KeyConditionExpression=Key('notsend').eq(0)
            )

            #絞り込んだテーブル情報 response['Item']から send 0 にプリペーアドクエリ更新
            for item in response['Items']:
              table.update_item(
                Key={'email' : item['email']},
                UpdateExpression="set send=:val",
                ExpressionAttributeValues= {
                  ':val' : 0
                }
              )

              #SQSにメッセージとして登録
              sqsresponse = queue.send_message(
                MessageBody=item['email'],
                MessageAttributes={
                  'username' : {
                    'DataType' : 'String',
                    'StringValue' : item['username']
                  },
                  'bucket' : {
                    'DataType' : 'String',
                    'StringValue' : bucket
                  },
                  'template' : {
                    'DataType' : 'String',
                    'StringValue' : template
                  }
                }
              )

              #ログ出力
              print(json.dumps(sqsresponse))

      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn
      Environment:
        Variables:
          ##削除する際は TriggerLambdaPermission をコメントアウトして Lambda を更新する
          SNS: !ImportValue cfn-sns-topic-inamura-arn
          S3: !ImportValue  cfn-s3-BucketName
          ##ここまで
          TEMPLATE: template-mail.txt 
      Tags:
        - Key: "User"
          Value: !Ref TagsName
      DeadLetterConfig:
        TargetArn: !ImportValue cfn-dlq-inamura-queuearn

  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:
                  - "sqs:*" 
                Resource: "arn:aws:sqs:ap-northeast-1:XXXXXXXXXXXX:*"

              - Effect: "Allow"
                Action:
                  - "s3:*" 
                Resource: "arn:aws:s3:::*"

              - Effect: "Allow"
                Action:
                  - "dynamodb:*"  
                Resource: "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:*"

  # #削除する際は TriggerLambdaPermission をコメントアウトして Lambda を更新する
  # TriggerLambdaPermission:
  #   Type: AWS::Lambda::Permission
  #   Properties:
  #     FunctionName: !Ref FunctionName
  #     Action: lambda:InvokeFunction
  #     Principal: events.amazonaws.com
  #     SourceArn: !ImportValue cfn-evb-checkdynamodb-inamura-arn
  # #ここまで

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"
  LambdaName:
    Value: !Ref FunctionName
    Export:
      Name: !Sub "${FunctionName}-name"

7.EventBridge作成:CronでLambda(sendqueue)とトリガーを構築

 7.1.CFnでEventBridge構築

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:
          - RuleName
          - EventBusName
          - State
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  EventBusName:
    Type: String
    Default: "default"
  RuleName:
    Type: String
    Default: "cfn-evb-checkdynamodb-inamura"
  State:
    Type: String
    Default: "ENABLED"

# ------------------------------------------------------------#
#  EventBridge
# ------------------------------------------------------------#
Resources:
  EventsRuleCron:
    Type: AWS::Events::Rule
    Properties:
      Description: "Check DynamoDB and send to Lambda"
      EventBusName: !Ref EventBusName
      Name: !Ref RuleName
      State: !Ref State
      ScheduleExpression: cron(0/10 * * * ? *)
      Targets:
        - Arn: !ImportValue cfn-lmd-sendqueue-inamura-arn
          Id: cfn-lmd-inamura

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  EventBridgeArn:
    Value: !GetAtt EventsRuleCron.Arn
    Export:
      Name: !Sub "${RuleName}-arn"

 7.2.手順6.で構築したLambdaのコメントアウト部分を修正し更新する

【修正箇所のみ抜粋】
!ImportValueEventBridgeが作成されていないと構築できなかった

  TriggerLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref FunctionName
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !ImportValue cfn-evb-checkdynamodb-inamura-arn

挙動の確認

前提条件:EventBridgeが10分ごとにLambdaをキックするので、10/20・・・分に起動する

①メールが届いたか確認する

②LambdaのCloudWatchLogsの出力のされ方

sendqueueのCloudWatchLogs】
EventBridgeCronされるたびにLambdaが実行している

sendmailのCloudWatchLogs】
DynamoDBのカラムnotsendが 1 のため Queue が発行されず実行されない

【届いていない場合①】Lambda(sendqueue)のCloudWatchLogsを確認する

・前半の処理部分(EventBridge、DynamoDB、SQS系があやしい)のエラーの場合

【届いていない場合②】Lambda(sendmail)のCloudWatchLogsを確認する

・後半の処理部分(SQS、DynamoDB、SNS系があやしい)のエラーの場合

削除順番

基本的には構築した順番の逆ですが、!ImportValueをしているので削除順がややこしくなっている部分があります。

1.Lambda(sendqueue)Environment部分及び、TriggerLambdaPermission部分をコメントアウトして更新をする(!ImportValueで引用している部分)
2.DynamoDBを削除
3.EventBridgeを削除
4.Lambda(sendqueue)を削除
5.Lambda(sendmail)を削除
6.SQSを削除
7.S3のバケットを空にし、S3を削除
8.SNSを削除


さいごに

盛りだくさんの内容になりましたが、個人的にはCFnでDynamoDBを利用するという目的は果たせたかと思います。ただし、まだまだ使えるのかと問われると基本となるクエリなどの本質的な理解が出来ていないとLambdaを書きながら痛感しました。
次回はAPIGateway経由で、こちらで利用したDynamoDBに名前やアドレスを CLIからではなくて、実際のフロント画面から送ったりして挙動を確認したいと思います。


参考文献

AWS Lambda実践ガイド 第2版 (impress top gear) 著:大澤文孝

Last modified: 2023-01-29

Author