循環依存の発生
以下のようにS3バケットに通知設定を行い、
オブジェクトが生成されたことをトリガにLambdaを実行しようとした場合、
互いのリソースを参照しており循環依存でデプロイエラーとなる
# S3バケット
testBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${RootStackName}-test-bucket
NotificationConfiguration: # 通知設定
LambdaConfigurations:
- Event: 's3:ObjectCreated:*'
# ** Lambda関数ARN参照 **
Function: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${RootStackName}-OnDetectFunction'
# S3にオブジェクトが生成されたことをトリガに実行されるLambda
OnDetectFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: handlers
FunctionName: !Sub ${RootStackName}-OnDetectFunction
Policies:
- S3ReadPolicy:
BucketName: !Ref testBucket # ** S3バケット名を参照 **
# Lambda実行権限をS3に追加
TriggerLambdaPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !GetAtt OnDetectFunction.Arn
Principal: 's3.amazonaws.com'
循環依存の回避
このAWSの回答を参考にする
方法としては、一旦依存関係のない状態(通知設定をしていない状態)でS3バケットを定義し、
カスタムリソースを使用して、LambdaでS3バケットに通知設定を行う
以下が実際に記述したもの
# S3バケット
testBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${RootStackName}-test-bucket
# NotificationConfiguration削除
# S3にオブジェクトが生成されたことをトリガに実行されるLambda
OnDetectFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: handlers
FunctionName: !Sub ${RootStackName}-OnDetectFunction
Policies:
- S3ReadPolicy:
BucketName: !Ref testBucket
# Lambda実行権限をS3に追加
TriggerLambdaPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !GetAtt OnDetectFunction.Arn
Principal: 's3.amazonaws.com'
# S3バケットにイベント通知設定を追加するためのロール
ApplyNotificationFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
Policies:
- PolicyName: S3BucketNotificationPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowBucketNotification
Effect: Allow
Action: s3:PutBucketNotification
Resource:
- !Sub 'arn:aws:s3:::${testBucket}'
- !Sub 'arn:aws:s3:::${testBucket}/*'
# カスタムリソース生成時に実行するLambda
ApplyBucketNotificationFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${RootStackName}-ApplyBucketNotificationFunction
Role: !GetAtt 'ApplyNotificationFunctionRole.Arn'
InlineCode: |
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
const cfnResponse = require('cfn-response');
exports.handler = async (event, context) => {
try {
if (event.RequestType !== 'Create') {
return await cfnResponse.send(event, context, cfnResponse.SUCCESS);
}
const params = {
Bucket: event.ResourceProperties.S3Bucket,
NotificationConfiguration: {
LambdaFunctionConfigurations: [
{
Events: ['s3:ObjectCreated:*'],
LambdaFunctionArn: event.ResourceProperties.FunctionARN
},
],
},
};
await S3.putBucketNotificationConfiguration(params).promise();
return await cfnResponse.send(event, context, cfnResponse.SUCCESS, {}, event.PhysicalResourceId);
} catch (err) {
return await cfnResponse.send(event, context, cfnResponse.FAILED, {});
}
};
# カスタムリソース
ApplyNotification:
Type: Custom::ApplyNotification
Properties:
ServiceToken: !GetAtt 'ApplyBucketNotificationFunction.Arn' # カスタムリソース生成時に実行するLambda
# 以下はカスタムリソース生成時に実行するLambdaに渡すパラメータ
S3Bucket: !Ref testBucket # イベント通知設定を追加するS3バケット名
FunctionARN: !GetAtt OnDetectFunction.Arn # イベント通知送信先Lambda
注意事項
- CloudFormationに応答を返すためのcfn-responseモジュールは、インライン実装にしか対応していない
-
cfnResponse.send
で送信しているカスタムリソースの応答オブジェクトが正しくCloudFormationに届かない場合、リソース生成中の状態でハングアップしてしまう(応答を返さないパスがないことを確認する、cfnResponse.send
をawait
で待たない場合もアウト) - リソース生成中の状態でハングアップしてしまったリソースの削除にも1時間程度かかる