時限式CloudFormation を作成してみた
こんにちは、森田です。
みなさんは、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分経過すると、スタックの削除が行われることになります。
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回行うだけであとは、コンポーネントを追加するだけでお手軽に利用できますので、ぜひ利用してみてください!