はじめに
CloudformationでS3を作成する際、削除時に中身が残ったままだと、エラーになってしまいます。
そのため、削除時に中身を空にするLambdaを実行するように、カスタムリソースを指定する方法を記事にしてみました。
概要
以下のリソースを作成します。
- クリーンアップ用リソース
- スタック削除時に、バケットの中身を空にするLambda
- 上記Lambda用の、IAMロール
- 上記Lambda用の、CloudWatchLog
- 削除時にクリーンアップするS3
- S3バケット
- 削除時にクリーンアップLambdaを実行
- クロススタックで指定
- 削除時にクリーンアップLambdaを実行
- S3バケット
参考
作成
クリーンアップLambda
S3削除時に、S3を空にするLambdaは以下になります。CloudWatchLogsも明示的に作ります。
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を作ります。ここではバージョニングも有効化しています。
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の試験対策時に、どのように実装するのか気になったので、実際にやってみました。
この記事がどなたかのお役に立てれば幸いです。