1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[AWS CloudFormation] S3イベント通知先にLambdaを設定(循環依存回避)

Last updated at Posted at 2020-11-17

循環依存の発生

以下のように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.sendawaitで待たない場合もアウト)
  • リソース生成中の状態でハングアップしてしまったリソースの削除にも1時間程度かかる
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?