カスタムリソースを使用して CloudFormation スタックの削除時に S3 バケットと一緒にオブジェクトを削除する方法

CloudFormation スタックを削除する際にカスタムリソースを使用して S3 バケットとオブジェクトを同時に削除する方法について紹介します。
2022.02.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

CloudFormation スタックを削除する際にカスタムリソースを使用して S3 バケットとオブジェクトを同時に削除する機会がありましたので、その方法について記載します。

前提として、CloudFormation で S3 バケットを作成します。
今回は以下のテンプレートで、バージョニングを有効化&任意のバケット名の S3 バケットを作成しました。

---
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  myBucketResource:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: s3-bucket-cleaning-on-delete
      VersioningConfiguration:
        Status: Enabled

スタックの作成が完了すると、以下の通り S3 バケットが作成されます。

バケット作成しました

Lambda

次に Lambda 関数を作成します。
Lambda コンソールで「関数の作成」をクリックし、「一から作成」をして任意の関数名を入力します。
ランタイムは「Python3.7」を選択してその他はデフォルトのまま「関数の作成」をクリックします。

Lambda関数の作成とランタイムの指定

関数の作成が完了したら、参考 URL に記載のコードを一部修正してデプロイを行います。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import boto3
import requests # ベンダーバージョンのリクエストを削除


def lambda_handler(event, context):
    try:
        bucket = event['ResourceProperties']['BucketName']

        if event['RequestType'] == 'Delete':
            s3 = boto3.resource('s3')
            bucket = s3.Bucket(bucket)
            for obj in bucket.objects.filter():
                s3.Object(bucket.name, obj.key).delete()
                bucket.object_versions.delete() # コード追加

        sendResponseCfn(event, context, "SUCCESS")
    except Exception as e:
        print(e)
        sendResponseCfn(event, context, "FAILED")


def sendResponseCfn(event, context, responseStatus):
    response_body = {'Status': responseStatus,
                     'Reason': 'Log stream name: ' + context.log_stream_name,
                     'PhysicalResourceId': context.log_stream_name,
                     'StackId': event['StackId'],
                     'RequestId': event['RequestId'],
                     'LogicalResourceId': event['LogicalResourceId'],
                     'Data': json.loads("{}")}

    requests.put(event['ResponseURL'], data=json.dumps(response_body))

デプロイ後、設定タブから一般設定を選択して編集をクリックします。

一般設定

タイムアウトを 10分 など長めの時間に変更したら「IAM コンソールでサービスロールを表示します」をクリックします。

Lambda設定編集

IAM コンソールに移動したら「アクセス許可を追加」をクリックして「ポリシーをアタッチ」を選択します。 そして今回は AmazonS3FullAccess ポリシーを付与します。※ 必要に応じた権限の設定を行ってください。

AmazonS3FullAccess

CloudFormation

次に参考 URL をもとに CloudFormation テンプレートにカスタムリソースを追加してスタックを更新します。
ServiceToken では作成した Lambda の ARN を指定し、BucketName は S3 バケットの論理 ID を指定します。

---
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  myBucketResource:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: s3-bucket-cleaning-on-delete
      VersioningConfiguration:
        Status: Enabled

# カスタムリソースを追加
  LambdaUsedToCleanUp:
     Type: Custom::cleanupbucket
     Properties:
       ServiceToken: arn:aws:lambda:us-west-2:XXXXXXXXXXXX:function:LambdaEmptyS3Objects
       BucketName: !Ref myBucketResource

テスト

カスタムリソースがうまく稼働するかどうかテストを行います。

作成した S3 バケットにオブジェクトを配置しました。
同オブジェクト名の Zip ファイルをアップロードしたので以下のようにバージョン管理されています。

S3にオブジェクトを置くテスト

上記で作成したスタックを削除すると、S3 バケットと一緒にバケット内のオブジェクトも削除されました。

バケットとオブジェクトが同時に削除される

おわりに

カスタムリソースでの対応方法は参考 URL に記載の内容とあらかた同じなのですが、ランタイムや IAM ロールの設定などで詰まるところがあったため、記事に残しました。この記事がどなたかのお役に立てれば幸いです。

参照