0
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?

カスタムリソースを活用して、S3バケットをスタック削除時に自動で空にする

0
Posted at

はじめに

CloudformationでS3を作成する際、削除時に中身が残ったままだと、エラーになってしまいます。
そのため、削除時に中身を空にするLambdaを実行するように、カスタムリソースを指定する方法を記事にしてみました。

概要

以下のリソースを作成します。

  • クリーンアップ用リソース
    • スタック削除時に、バケットの中身を空にするLambda
    • 上記Lambda用の、IAMロール
    • 上記Lambda用の、CloudWatchLog
  • 削除時にクリーンアップするS3
    • S3バケット
      • 削除時にクリーンアップLambdaを実行
        • クロススタックで指定

参考

作成

クリーンアップLambda

S3削除時に、S3を空にするLambdaは以下になります。CloudWatchLogsも明示的に作ります。

createLambdaS3Cleanup.yaml.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: S3 cleanup Lambda

Resources:
  # ロググループのライフサイクル管理
  S3CleanupLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /aws/lambda/S3CleanupHandler
      RetentionInDays: 7
    DeletionPolicy: Delete

  S3CleanupRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: S3AndLogsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:ListBucketVersions
                  - s3:DeleteObject
                  - s3:DeleteObjectVersion
                  - s3:GetBucketVersioning
                Resource: "*"
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !GetAtt S3CleanupLogGroup.Arn

  S3CleanupFunction:
    Type: AWS::Lambda::Function
    DependsOn: S3CleanupLogGroup
    Properties:
      FunctionName: S3CleanupHandler
      Handler: index.handler
      Role: !GetAtt S3CleanupRole.Arn
      Runtime: python3.12
      Timeout: 300
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def handler(event, context):
              logger.info(f"Received event: {event}")

              status = cfnresponse.SUCCESS
              # 物理IDを固定することでスタックの安定性を高める
              physical_id = event.get('PhysicalResourceId', 'S3CleanupResource')
              
              try:
                  if event['RequestType'] == 'Delete':
                      bucket_name = event['ResourceProperties'].get('BucketName')
                      if bucket_name:
                          logger.info(f"Start cleaning bucket: {bucket_name}")
                          s3 = boto3.resource('s3')
                          bucket = s3.Bucket(bucket_name)
                          # バージョニング有無どちらでも全削除
                          bucket.object_versions.delete()
                          logger.info(f"Successfully emptied bucket: {bucket_name}")
                  
              except Exception as e:
                  logger.error(f"Error occurred: {str(e)}")
                  # 削除失敗でスタック削除が中途半端にならないよう、とりあえず成功で返却。スタック側でエラー扱いにする。
                  status = cfnresponse.SUCCESS
              
              cfnresponse.send(event, context, status, {}, physicalResourceId=physical_id)

Outputs:
  S3CleanupFunctionArn:
    Description: ARN of the modern S3 cleanup function
    Value: !GetAtt S3CleanupFunction.Arn
    Export:
      Name: S3CleanupFunctionArn

参考のLambdaではレスポンスを自作していますが、このLambdaではレスポンスにAWS標準のimport cfnresponseを使っています。

クロススタックできるよう、Outputsセクションで名前を出力しておきます。

S3:削除時クリーンアップ込み

先のLambdaをクロススタックを使って指定して、S3を作ります。ここではバージョニングも有効化しています。

createS3WithCleanup.yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  BucketName:
    Type: String
    Description: Name of the S3 bucket (must be globally unique)
    Default: "my-simple-cleanup-bucket-2026"

Resources:
  # メインのS3バケット
  MyBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Ref BucketName
      # 必要に応じて、バージョニングを有効化
      VersioningConfiguration:
        Status: Enabled

  # 掃除用Lambdaをキックするカスタムリソース
  CleanupTrigger:
    Type: Custom::S3Cleanup
    DependsOn: MyBucket
    Properties:
      ServiceToken: !ImportValue S3CleanupFunctionArn
      BucketName: !Ref MyBucket

Outputs:
  BucketArn:
    Value: !GetAtt MyBucket.Arn

作成されたS3にファイルを追加・削除などして、空ではないバケットにします。
その後Cloudformationスタックを削除して、正常に消えるか確認してみてください。

おわりに

今回は、Cloudformation スタック削除時に、S3を空にする方法を記事にしました。
SAPの試験対策時に、どのように実装するのか気になったので、実際にやってみました。
この記事がどなたかのお役に立てれば幸いです。

0
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
0
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?