[CloudFormation] CFnで作成したバケットを空にして削除する
やりたいこと
CloudFormation で作成した S3 バケットをスタック削除時に同時に削除できるようにする
→なにも対策を行っていない場合、作成したバケットにファイルが作成された状態でスタックを削除するとエラーが発生しスタックの削除に失敗します。
このため、カスタムリソースを作成しスタックの削除時にバケットを空にするコードを記載してあげる必要があります
※ECR なども同様です
コード
AWSTemplateFormatVersion: 2010-09-09
Description: If you use S3 bucket, you need to clean up used S3 bucket. so this template is cleanup example
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
CleanupBucketOnDelete:
Type: Custom::cleanupBucket
Properties:
ServiceToken: !GetAtt CleanBucketFunction.Arn
DependsOn: S3Bucket
CleanBucketFunction:
Type: AWS::Lambda::Function
Properties:
Description: Cleans out Bucket during delete
Handler: index.handler
Runtime: python3.9
Role: !GetAtt CleanBucketRole.Arn
Timeout: 900
Environment:
Variables:
BUCKET_NAME: !Ref S3Bucket
Code:
ZipFile: |
import cfnresponse
import logging
import boto3
import time, os
status = cfnresponse.SUCCESS
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(message)s',level=logging.DEBUG)
def handler(event, context):
logger.debug(event)
if event['RequestType'] == 'Delete':
try:
BUCKETNAME = os.environ['BUCKET_NAME']
s3 = boto3.resource('s3')
time.sleep(90)
bucket = s3.Bucket(BUCKETNAME)
bucket_versioning = s3.BucketVersioning(BUCKETNAME)
if bucket_versioning.status == 'Enabled': # disabled buclet versioning setting
bucket.object_versions.delete()
else:
bucket.objects.all().delete()
cfnresponse.send(event, context, status, {}, None)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)
else:
cfnresponse.send(event, context, status, {}, None)
CleanBucketRole:
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
Policies:
- PolicyName: lambda-bucketcleaner
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:DeleteObject
- s3:DeleteObjectVersion
Resource: !Sub 'arn:${AWS::Partition}:s3:::${S3Bucket}/*'
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketVersioning
Resource: !Sub 'arn:${AWS::Partition}:s3:::${S3Bucket}'
コードの説明
S3
S3 バケットを作成します。
設定値についてはシンプルにしてます←
S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
カスタムリソース
カスタムリソースとして Lambda を起動します。
この Lambda では起動時に event['RequestType'] に Create や Delete が含まれているためこれを確認しスタックの削除時の実行する形式にしてあります。
※作成時にも処理を行いたい場合は、event['RequestType'] == 'Create' を指定してコードを書きます
削除時に実行する動作としてはオブジェクトの削除となり、その際にバージョニングが有効化されている場合は全てのバージョンを削除する処理としています。
また、カスタムリソースでは正常に処理が完了したことをスタックに伝えるためにレスポンスを行う必要があります。今回はシンプルなコードなため、cfnresponse を使って成功/失敗のレスポンスを返すようにしています。
もしもう少し複雑な処理であったり、カスタムリソースで行った処理のデータをスタックに渡したい場合、Dict 形式でレスポンスに含めることでスタック内で !GetAtt を使うことで取得、利用することが可能です。
CleanupBucketOnDelete:
Type: Custom::cleanupBucket
Properties:
ServiceToken: !GetAtt CleanBucketFunction.Arn
DependsOn: S3Bucket
CleanBucketFunction:
Type: AWS::Lambda::Function
Properties:
Description: Cleans out Bucket during delete
Handler: index.handler
Runtime: python3.9
Role: !GetAtt CleanBucketRole.Arn
Timeout: 900
Environment:
Variables:
BUCKET_NAME: !Ref S3Bucket
Code:
ZipFile: |
import cfnresponse
import logging
import boto3
import time, os
status = cfnresponse.SUCCESS
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(message)s',level=logging.DEBUG)
def handler(event, context):
logger.debug(event)
if event['RequestType'] == 'Delete':
try:
BUCKETNAME = os.environ['BUCKET_NAME']
s3 = boto3.resource('s3')
time.sleep(90)
bucket = s3.Bucket(BUCKETNAME)
bucket_versioning = s3.BucketVersioning(BUCKETNAME)
if bucket_versioning.status == 'Enabled': # disabled buclet versioning setting
bucket.object_versions.delete()
else:
bucket.objects.all().delete()
cfnresponse.send(event, context, status, {}, None)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)
else:
cfnresponse.send(event, context, status, {}, None)
IAM
カスタムリソースで実行する Lambda に割り当てる権限を指定します。
S3 バケットを削除する際には、オブジェクトとオブジェクトのバージョニングの削除権とバージョニング状態の確認、バケットのリストの権限を付与します。
CleanBucketRole:
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
Policies:
- PolicyName: lambda-bucketcleaner
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:DeleteObject
- s3:DeleteObjectVersion
Resource: !Sub 'arn:${AWS::Partition}:s3:::${S3Bucket}/*'
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketVersioning
Resource: !Sub 'arn:${AWS::Partition}:s3:::${S3Bucket}'
最後に
よく自分で使うので備忘録も兼ねて公開しましたがもし他の方の参考になれば幸いですー
Discussion