CFNカスタムリソースでDynamoDBを初期セットアップ

CloudFormationカスタムリソースでDynamoDBを初期セットアップ

CloudFormationカスタムリソースでDynamoDBを初期セットアップを実行する

CloudFormationでDynamoDB作成時に、DBの初期化として、テストレコードの追加を併せて実行することを考えます。

今回はCloudFormationカスタムリソースを使用して、DynamoDBテーブルの初期セットアップを実施します。

構築する環境

Diagram of initial setup of DynamoDB with CloudFormation custom resources

CloudFormationでDynamoDBテーブルを作成します。

合わせてLambda関数も作成します。
この関数がCloudFormationスタック作成時に自動的に実行されるように、CloudFormationカスタムリソースに関数を関連づけます。
この関数の働きは、DynamoDBテーブルを初期化することです。
初期化処理は、S3バケットに配置されているJSONデータを読み込み、DynamoDBテーブルにアイテムとして登録する処理を行います。
関数のランタイム環境はPython3.8です。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/105

テンプレートファイルのポイント解説

DynamoDBテーブル

Resources:
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: Artist
          AttributeType: S
        - AttributeName: SongTitle
          AttributeType: S
      BillingMode: PROVISIONED
      KeySchema:
        - AttributeName: Artist
          KeyType: HASH
        - AttributeName: SongTitle
          KeyType: RANGE
      ProvisionedThroughput:
        ReadCapacityUnits: !Ref ReadCapacityUnits
        WriteCapacityUnits: !Ref WriteCapacityUnits
      TableClass: STANDARD
      TableName: !Sub "${Prefix}-Music"
Code language: YAML (yaml)

特別な設定は不要です。
以下のAWS公式ページの設定をCloudFormationテンプレートに書き出しました。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/getting-started-step-1.html

DynamoDBに関する基本的な事項に関しては、以下のページもご確認ください。

https://awstut.com/2022/02/07/introduction-to-dynamodb

Lambda関数

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Environment:
        Variables:
          JSON_S3_BUCKET: !Ref JsonS3Bucket
          JSON_S3_KEY: !Ref JsonS3Key
          TABLE_NAME: !Ref Table
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import json
          import os

          JSON_S3_BUCKET = os.environ['JSON_S3_BUCKET']
          JSON_S3_KEY = os.environ['JSON_S3_KEY']
          TABLE_NAME = os.environ['TABLE_NAME']

          CREATE = 'Create'
          response_data = {}

          s3_client = boto3.client('s3')
          dynamodb_client = boto3.client('dynamodb')

          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                s3_response = s3_client.get_object(
                  Bucket=JSON_S3_BUCKET,
                  Key=JSON_S3_KEY)

                body = s3_response['Body'].read()
                json_data = json.loads(body.decode('utf-8'))
                print(json_data)

                for item in json_data:
                  dynamodb_response = dynamodb_client.put_item(
                    TableName=TABLE_NAME,
                    Item=item
                  )
                  print(dynamodb_response)

              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。

https://awstut.com/2022/02/02/3-patterns-for-creating-lambda-with-cloudformation

cfnresponseモジュールを使用して、関数をLambda-backedカスタムリソースとして実装します。
詳細につきましては、以下のページをご確認ください。

https://awstut.com/2022/05/04/introduction-to-cloudformation-custom-resources

Variablesプロパティで環境変数を定義することができます。
環境変数は関数内から参照することができます。
今回は初期化処理用のデータを格納しているS3バケット名や、ファイル名を環境変数として渡します。

実行するコードの内容ですが、以下の通りです。

  • os.environにアクセスして、CloudFormationテンプレートで定義した環境変数を取得する。
  • Boto3でS3用クライアントオブジェクトを作成して、S3バケットから初期化用のJSONファイルを取得する。
  • Boto3でDynamoDBクライアントオブジェクトを作成して、サンプルデータをテーブルに保存する。
  • cfnresponseモジュールのsend関数を使用して、カスタムリソースが正常に完了した旨のメッセージをCloudFormationに通知する。

S3バケットから読み込むJSONファイルの内容は以下の通りです。

[
  {"Artist": {"S": "No One You Know"}, "SongTitle": {"S": "Call Me Today"}, "AlbumTitle": {"S": "Somewhat Famous"}, "Awards": {"N": "1"}},
  {"Artist": {"S": "No One You Know"}, "SongTitle": {"S": "Howdy"}, "AlbumTitle": {"S": "Somewhat Famous"}, "Awards": {"N": "2"}},
  {"Artist": {"S": "Acme Band"}, "SongTitle": {"S": "Happy Day"}, "AlbumTitle": {"S": "Songs About Life"}, "Awards": {"N": "10"}},
  {"Artist": {"S": "Acme Band"}, "SongTitle": {"S": "PartiQL Rocks"}, "AlbumTitle": {"S": "Another Album Title"}, "Awards": {"N": "8"}}
]
Code language: JSON / JSON with Comments (json)

このデータもAWS公式ページで紹介されているデータです。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/getting-started-step-2.html

関数用のIAMロールは以下の通りです。

Resources:
  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: !Sub "${Prefix}-S3GetObjectPolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource:
                  - !Sub "arn:aws:s3:::${JsonS3Bucket}/*"
        - PolicyName: !Sub "${Prefix}-DynamodbPutItemPolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:PutItem
                Resource:
                  - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${Table}"
Code language: YAML (yaml)

AWS管理ポリシーであるAWSLambdaBasicExecutionRoleに加えて、インラインポリシーで2つの権限を与えます。

1つ目はS3バケットからオブジェクトを取得する権限です。
これはサンプルデータであるJSONファイルを取得するために必要な権限です。

2つ目はDynamoDBにアイテムを保存する権限です。
JSONファイル内のデータをDynamoDBテーブルに書き込むために必要な権限です。

CloudFormationカスタムリソース

続いてCloudFormationカスタムリソース本体を確認します。

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !Ref FunctionArn
Code language: YAML (yaml)

カスタムリソースに、先述のLambda関数を関連付けます。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

https://awstut.com/2021/12/02/cloudformation-nested-stacks

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • DynamoDBテーブル:fa-105-Music
  • Lambda関数:fa-105-function

AWS Management Consoleから各リソースを確認します。
まずCloudFormationカスタムリソースおよびLambda関数を確認します。

Detail of CloudFormation Custom Resources 1.
Detail of CloudFormation Custom Resources 2.

確かにLambda関数とカスタムリソース本体が正常に作成されていることがわかります。
つまりCloudFormationスタック作成時に、自動的にLambda関数が作成・実行されたことになります。

次にDynamoDBを確認します。

Detail of DynamoDB 1.
Detail of DynamoDB 2.

CloudFormationテンプレートで指定した通りに、DynamoDBテーブルが作成されています。

動作確認

CloudFormationカスタムリソースの動作状況を確認します。

CloudFormationカスタムリソースに紐づけたLambda関数の実行ログを確認します。

Detail of CloudFormation Custom Resources 3.

S3バケットに設置されているJSONデータを読み込んで、1行ずつDynamoDBテーブルに保存しています。
最後にCloudFormationにメッセージを通知しています。

DynamoDBテーブルの内容を確認します。

Detail of DynamoDB 3.

確かにテーブルにアイテムが保存されています。

以上のことから、CloudFormationスタック作成時に、CloudFormationカスタムリソースによって、これに紐づくLambda関数が自動的に実行されて、DynamoDBテーブルにアイテムが保存されたことが確認できました。

まとめ

CloudFormationカスタムリソースを使用して、DynamoDBテーブルの初期セットアップを実施する方法を確認しました。