前回は、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にも処理のログが出力されます。
今回は、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 まで。