前回は、S3へダイレクトアップロードした後の後続処理をAWS Lambdaファンクションを使ったサーバレスアプリケーションとして実装しました。続く今回は、実際にAWSに環境を構築してLambdaファンクションをデプロイして実行してみます。

事前準備:デプロイのためのS3バケット作成とアップロード

前回も解説した通り、以下の構成図の「7」の部分で、LambdaファンクションとしてDynamoDBへのアクセスとSQSキュー送信を実装しています。

構成図

今回はAWS LambdaのデプロイとアクセスするDynamoDB(7’)、SQS(7”)の構築、トリガーとなるS3およびイベント設定(6)をCloudFormationで実装します。

CloudFormationの基本的な使い方は連載「AWSで実践!デプロイ・基盤自動化」の第32回を参照してください。ただし、Lambdaのデプロイを行う前に、図中のものとは別のデプロイ用S3バケットを作成して、前回作成したLambdaファンクションをアップロードしておく必要があります。

そこで、S3バケットを作成するCloudFormationテンプレートとLambdaファンクションをビルドしてアップロードするスクリプトをまず実装します。

S3バケットを作成するCloudFormationテンプレートでは、バケットの名称やARNをOutput要素で指定しておきます。これは、後に作成するLambdaファンクションのデプロイを行うCloudFormationテンプレートで、クロススタックリファレンスで参照するためです。

AWSTemplateFormatVersion: '2010-09-09'

Description: S3 Bucket for Lambda function template with YAML - S3 Bucket Definition

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: debugroom-mynavi-sample-lambda-s3event-for-deploy
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

Outputs:
  S3Bucket:
    Description: Lambda deploy S3 bucket name
    Value: !Ref S3Bucket
    Export:
      Name: MynaviSampleLambdaS3Event-deployS3Bucket

  S3BucketArn:
    Description: Deploy S3 for Lambda bucket arn
    Value: !GetAtt S3Bucket.Arn
    Export:
      Name: MynaviSampleLambdaS3Event-deployS3BucketArn

続いて、前回実装したLambdaファンクションをビルドして、上記のS3バケットにアップロードするシェルスクリプトを作成します。

#!/usr/bin/env bash

bucket_name=debugroom-mynavi-sample-lambda-s3event-for-deploy
stack_name="mynavi-sample-s3-lambda-s3event"
template_path="src/main/cloudformation/s3-deploy-lambda-cfn.yml"
s3_objectkey="mynavi-sample-aws-lambda-s3event-0.0.1-SNAPSHOT-aws.jar"

if [ "" == "`aws s3 ls | grep $bucket_name`" ]; then
   aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --capabilities CAPABILITY_IAM
fi

./mvnw package
aws s3 cp target/${s3_objectkey} s3://${bucket_name}/

上記のスクリプトを実行して、バケット内にJARファイルがあることを確認しましょう。

CloudFormationを使った環境構築とLambdaファンクションのデプロイ

引き続き、DynamoDBおよびSQSを構築するCloudFormationを実装します。DynamoDBのテンプレートではテーブル定義をリソースとして定義しますが、その際、前回の実装でDynamoDBのキーとして指定した「fileId」をHASH属性で指定します。またLambdaファンクションから参照するため、Output要素にエンドポイントとリージョンを指定しておきます。

AWSTemplateFormatVersion: '2010-09-09'

Description: Sample CloudFormation template with YAML - DynamoDB Definition

Resources:
  DynamoDBUploadFileTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: "upload-file-table"
      BillingMode: PROVISIONED
      AttributeDefinitions:
        - AttributeName: fileId
          AttributeType: S
      KeySchema:
        - AttributeName: fileId
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

Outputs:
  EnvironmentRegion:
    Description: Dev Environment Region
    Value: !Sub ${AWS::Region}
    Export:
      Name: MynaviSampleLambdaS3Event-DynamoDB-Region
  DynamoDBServiceEndpoint:
    Description: DynamoDB service endipoint
    Value: !Sub https://dynamodb.${AWS::Region}.amazonaws.com
    Export:
      Name:  MynaviSampleLambdaS3Event-DynamoDB-ServiceEndpoint

同様にSQSのテンプレートでも、リソースの定義に加えて、Output要素にエンドポイントとリージョン、そしてキュー名をLambdaファンクションからの参照用に出力しておきます。

AWSTemplateFormatVersion: '2010-09-09'

Description: Sample CloudFormation template with YAML - SQS Definition

Resources:
  SQSSampleQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: mynavi-sample-lambda-s3event-queue
      VisibilityTimeout: 30
      DelaySeconds: 5
      MaximumMessageSize: 26144
      MessageRetentionPeriod: 345600
      ReceiveMessageWaitTimeSeconds: 0

Outputs:
  SQSServiceEndpoint:
    Description: SQS service endipoint
    Value: !Sub https://sqs.${AWS::Region}.amazonaws.com
    Export:
      Name: MynaviSampleLambdaS3Event-SQS-ServiceEndpoint

  SQSServiceRegion:
    Description: SQS service region
    Value: !Sub ${AWS::Region}
    Export:
      Name: MynaviSampleLambdaS3Event-SQS-Region

  SQSSampleQueue:
    Description: SQS sample queue name
    Value: !Ref SQSSampleQueue
    Export:
      Name: MynaviSampleLambdaS3Event-SQS-QueueName

続いて、LambdaファクションをデプロイするCloudFormationテンプレートを実装します。

AWSTemplateFormatVersion: '2010-09-09'

Description: Lambda template with YAML

Resources:
  SampleLambdaS3Event:
    Type: AWS::Lambda::Function                                              # (A)
    Properties:
      Code:
        S3Bucket:
          Fn::ImportValue: MynaviSampleLambdaS3Event-deployS3Bucket          # (B)
      S3Key: mynavi-sample-aws-lambda-s3event-0.0.1-SNAPSHOT-aws.jar         # (C)
      Handler: org.debugroom.mynavi.sample.aws.lambda.s3event.app.handler.S3UploadEventHandler::handleRequest
                                                                             # (D)
      FunctionName: mynavi-sample-aws-lambda-s3event-function
      Environment:
        Variables:
          FUNCTION_NAME: sampleFunction                                      # (E)
      MemorySize: 1024                                                       # (F)
      Runtime: java8
      Timeout: 120F
      Role: !GetAtt LambdaRole.Arn                                           # (G)

  LambdaRole:                                                                # (H)
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"

  SQSAccessPolicy:                                                           # (I)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mynavi-sample-lambda-s3event-sqs-access-policy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "sqs:*"
            Resource: "*"
      Roles:
        - !Ref LambdaRole

  DynamoDBAccessPolicy:                                                      # (J)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mynavi-sample-lambda-s3event-dynamodb-access-policy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "dynamodb:*"
              # omit
        Resource: "*"
      Roles:
        - !Ref LambdaRole

  CloudFormationAccessPolicy:                                                # (K)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mynavi-sample-lambda-s3event-cloudformation-access-policy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cloudformation:*"
            Resource: "*"
      Roles:
        - !Ref LambdaRole

  SSMAccessPolicy:                                                           # (L)
    Type: AWS::IAM::Policy
    Properties:
    PolicyName: mynavi-sample-lambda-s3event-ssm-access-policy
    PolicyDocument:
      Statement:
        - Effect: Allow
          Action:
            - "cloudwatch:PutMetricData"
            # omit
      Roles:
        - !Ref LambdaRole

Outputs:
  SampleLambdaS3EventArn:                                                    # (M)
    Value: !GetAtt SampleLambdaS3Event.Arn
    Export:
      Name: MynaviSampleLambdaS3Event-LambdaArn

テンプレートのポイントになる設定箇所は、以下の通りです。

項番 説明
A Lambdaファンクションとしてのリソースを定義します。定義の詳細はAWS::Lambda::Functionも参照してください
B 前節で作成したデプロイ用S3テンプレートのOutput要素で出力したバケット名をクロススタックリファレンスを使って取得します
C 前節のシェルスクリプト内でMavenビルドしたLambdaファンクションのJarファイル名を指定します
D 前回実装したHandlerクラスのFQCNとハンドラメソッドを指定します
E 連載「AWSで作るクラウドネイティブアプリケーションの基本」の第2回と同様、環境変数にFUNCATION_NAMEとして、ファンクションクラスとなるBean名を指定しておきます(Spring Cloud Functionでは、実行する関数を環境変数FUNCTION_NAMEで指定したBean名で取得するためです)
F ファンクション実行時のメモリサイズを指定します。SpringアプリケーションをLambdaで実行する場合1024MB以上のメモリサイズを確保しなければ、起動時間が大幅にかかってしまうケースがあります
G Lambdaに設定するIAMロールのARNを指定します。ここではHで定義したARNを設定します
H Lambdaに設定するIAMロールを定義します。ファンクション内の実装では、DynamoDBやSQSに加えて。CloudFormationのスタック参照、SystemsManager Parameter Storeの参照を行いますが、I~Lで定義したポリシーからアタッチして、このロールを参照するように設定します
I SQSのアクセスを可能にするIAMポリシーを定義し、Hのロールへアタッチします
J DynamoDBのアクセスを可能にするIAMポリシーを定義し、Hのロールへアタッチします
K CloudFormationスタックのアクセスを可能にするIAMポリシーを定義し、Hのロールへアタッチします
L SystemsManager Parameter Storeへのアクセスを可能にするIAMポリシーを定義し、Hのロールへアタッチします
M 後述するアップロード用のS3テンプレートのイベント設定で参照するため、LambdaファンクションのARNをOutput要素として出力します

続いて、アップロードによりイベントを発生させるためのS3の設定を行います。前回までに使用してきたアップロード用のS3バケットを一度削除して、下記の通りLambdaへのイベントトリガーの設定を加え、CloudFormationで作成し直します。

AWSTemplateFormatVersion: '2010-09-09'

Description: Sample CloudFormation template with YAML - S3 Bucket Definition

Resources:
  S3Bucket:                                                                  # (A)
    Type: AWS::S3::Bucket
    DependsOn: LambdaInvokePermission                                        # (B)
    Properties:
      BucketName: debugroom-mynavi-sample-lambda-s3event
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      NotificationConfiguration:                                             # (C)
        LambdaConfigurations:
          - Event: s3:ObjectCreated:*                                        # (D)
            Function:
              Fn::ImportValue: MynaviSampleLambdaS3Event-LambdaArn           # (E)
  LambdaInvokePermission:                                                    # (F)
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName:
        Fn::ImportValue: MynaviSampleLambdaS3Event-LambdaArn                 # (G)
      Principal: s3.amazonaws.com
      Action: lambda:InvokeFunction
      SourceArn: !Join
        - ""
        - - "arn:aws:s3:::"
          - "debugroom-mynavi-sample-lambda-s3event"                         # (H)

Outputs:
  S3Bucket:
    Description: Lambda S3 bucket name
    Value: !Ref S3Bucket
    Export:
      Name: MynaviSampleLambdaS3Event-s3Bucket                               # (I)

  # omit

設定のポイントは以下の通りです。

項番 説明
A S3のリソース定義を行います。定義の要領は連載「AWSで作るクラウドネイティブアプリケーションの基本」の第30回と同様です
B DependsOn属性を使って、リソースLambdaInvokePermissionの作成が完了してから、S3リソースの作成を行います
C イベント通知の設定はNotificationConfigurationで行います
D イベントとして、当バケットでオブジェクトが生成されたときを設定します
E 実行するファンクションとして、上記で作成したLambdaファンクションのARNをクロススタックリファレンスで参照します
F Lambdaの実行許可をリソースとして定義します。定義の詳細はAWS::Lambda::Permissionも参照してください
G 対象のファンクションとして、上記で作成したLambdaファンクションのARNをクロススタックリファレンスで参照します
H Lambdaの実行元となるARNを設定します。Aの定義と相互参照になるため、!Ref参照は行わず、直接S3のARNを定義します
I ダイレクトアップロードを行うアプリケーションから参照するため、Output要素として、S3のバケット名を出力しておきます

各テンプレートを順次実行して環境を構築した後、対象のS3にファイルがアップロードされると、以下のようにDynamoDBやS3に処理結果が残り、CloudWatch Logsにも処理のログが出力されます。

処理結果1
処理結果2
処理結果3

今回は、Lambdaファンクションの処理に必要な環境構築とLambdaのデプロイをCloudFormaitonを使って実装しました。次回はファンクション内でエラーが発生した際のCloudWatchでのイベント発生や、エラーハンドリングの実装方法を解説していきます。

著者紹介


川畑 光平(KAWABATA Kohei) - NTTデータ

金融機関システム業務アプリケーション開発・システム基盤担当、ソフトウェア開発自動化関連の研究開発を経て、デジタル技術関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。AWS Top Engineers & Ambassadors選出。

本連載の内容に対するご意見・ご質問は Facebook まで。