この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、森田です。
みなさんは、AWSリソースの消し忘れをしたことありませんか。
私は、結構消し忘れをしてしまいます...笑
本記事では、AWSリソースの消し忘れを防止するべく時限式CloudFormation
を考えてみましたので、そのご紹介をいたします。
時限式CloudFormationとは
時限式爆弾のように、ある時間経過後自動で削除するCloudFormationスタックを作成します。
こちらを実現するために、削除を行うコンポーネントをInclude Transform
を用いて作成し、事前にS3バケットに保存しておきます。
削除を行うコンポーネントでは、以下のように、カスタムリソース用の AWS Lambda を作成します。
その処理としては、動的な EventBridge ルールの作成を行います。
また、実行元がカスタムリソースからではなく、EventBridge ルールからであれば、自身のスタックを削除するように構成します。
あとは、時限式CloudFormationとしたいテンプレート中にコンポーネントを追加するだけで自動で削除してくれるようにします。
実際にやってみた
では、実際に時限式CloudFormation
をやっていきます。
今回使用するテンプレートやコマンドなどは以下のリポジトリにも置いてあります。
(初回のみ) Delete Component のアップロード
まずは、Delete Component を事前にS3バケットに格納しておく必要があります。
以下のテンプレートを用意します。
delete.yml(ここを押すとコードが展開されます)
TimeSetupper:
Type: Custom::SetupLambda
Properties:
ServiceToken:
Fn::GetAtt:
- Lambda
- Arn
Lambda:
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-stack-deleter
Runtime: python3.9
Handler: index.handler
Role:
Fn::ImportValue: DeleteLambdaRoleARN
Environment:
Variables:
StackName:
Ref: AWS::StackName
AccountID:
Ref: AWS::AccountId
Timer:
Ref: Timer
Code:
ZipFile: |
from datetime import datetime, timedelta, timezone
import cfnresponse
import os
import boto3
JST = timezone(timedelta(hours=+9), 'JST')
StackName = os.environ.get('StackName')
account_id = os.environ.get('AccountID')
Timer = int(os.environ.get('Timer', "3") )
def handler(event, context):
if event.get('RequestType') == 'Create':
create_eb()
cfnresponse.send(event, context, cfnresponse.SUCCESS,
{'Response': 'Success'})
elif event.get('RequestType') == 'Delete':
delete_eb()
cfnresponse.send(event, context, cfnresponse.SUCCESS,
{'Response': 'Success'})
elif event.get('RequestType') == 'Update':
print('Update')
cfnresponse.send(event, context, cfnresponse.SUCCESS,
{'Response': 'Success'})
else:
client = boto3.client('cloudformation')
response = client.delete_stack(
StackName=StackName
)
print(response)
jp_time = datetime.now(JST)
print(jp_time)
return { 'statusCode': 200, 'exec_date': jp_time.strftime('%Y%m%d') }
def create_eb():
client = boto3.client('events')
event_name = "{}-deleterule".format(StackName)
now = datetime.now() + timedelta(minutes=Timer)
event_time = "cron({} {} * * ? *)".format(now.minute, now.hour)
print(event_time)
description = "CloudFormation {} Stack Delete Rule".format(StackName)
response = client.put_rule(
Name=event_name,
ScheduleExpression= event_time,
EventPattern='',
State="ENABLED",
Description=description)
rule_arn = response["RuleArn"]
lambda_arn = 'arn:aws:lambda:ap-northeast-1:{}:function:{}-stack-deleter'.format(account_id, StackName)
response = client.put_targets(
Rule=event_name,
Targets=[
{
'Id': "lambada-target",
'Arn': lambda_arn,
}
]
)
client = boto3.client('lambda')
response = client.add_permission(
FunctionName="{}-stack-deleter".format(StackName),
StatementId='{}-delete'.format(StackName),
Action='lambda:InvokeFunction',
Principal='events.amazonaws.com',
SourceArn=rule_arn
)
def delete_eb():
client = boto3.client('events')
event_name = "{}-deleterule".format(StackName)
response = client.remove_targets(
Rule=event_name,
Ids=["lambada-target"]
)
response = client.delete_rule(Name=event_name)
以下のコマンドを実行しバケットの作成、ファイルのアップロードを行います。
# バケットの作成
aws s3 mb バケット名
# バケットにファイルを格納する
aws s3 cp delete.yml s3://バケット名
(初回のみ) 削除用IAMロールの作成
続いてDelete Component内で利用するIAMロールを事前に作成しておきます。
以下のテンプレートを用いてロールの作成を行います。
delete_iam.yml(ここを押すとコードが展開されます)
Description: Time Delete
Resources:
LambdaRole:
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/AdministratorAccess'
Path: "/"
Outputs:
DeleteLambdaRoleARN:
Value: !GetAtt LambdaRole.Arn
Export:
Name: DeleteLambdaRoleARN
以上で、事前準備は終わりです!
時限式CloudFormationとするテンプレートの作成
では、実際に時限式CloudFormationを作成します。
今回はシンプルにS3バケットの作成を行うテンプレートに対してDelete Componentを追加して時限式CloudFormationとします。
Delete Componentとして以下のコードを記述します。
テンプレートに追加するコード
Fn::Transform:
Name: AWS::Include
Parameters:
Location: s3://バケット名/delete.yml
また、パラメータとして削除する時間(Timer)を指定する必要があります。
下記の例では、スタック作成後3分経過すると、スタックの削除が行われることになります。
example.yml
Description: Time Delte Item
Parameters:
BucketName:
Type: String
Timer:
Type: Number
MinValue: 3
Default: 3
Resources:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: s3://バケット名/delete.yml
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Ref: BucketName
あとは、作成したテンプレートをAWSコンソールやAWS CLIなどで展開してスタックの作成を行います。
作成されたスタックの確認
まずは、リソースが作成されていることを確認します。以下のように、S3バケット、削除用のLambdaが作成されていることを確認します
そして、スタックが自動で削除されるかを確認するため、一定時間経過(今回であれば3分)するのを待ちます。
すると、正常に動作していると以下のように対象のリソース、CloudFormationスタックが削除されていることが確認できます!
補足
削除のタイミングとしては、厳密には、スタックの作成から3分後ではなく、 上記のように、Delete Component内のカスタムリソース実行後から3分後となります。
最後に
時限式CloudFormationを利用することでCloudFormationで作ったリソースの削除忘れを防止することができます。
また、利用方法としても事前準備を1回行うだけであとは、コンポーネントを追加するだけでお手軽に利用できますので、ぜひ利用してみてください!