こんにちは、森田です。
みなさんは、AWSリソースちゃんと削除できていますか?
私は、特に CloudFormation で構築した場合に、忘れてしまうことがあります。
本記事では、リソース削除忘れを防止する方法として、自動で CloudFormation スタックを削除する方法をご紹介します。
アーキテクチャ
以前、以下のような 時限式CloudFormation をご紹介しましたが、それよりシンプルな構成となっております。
CloudFormationテンプレートの内部に EventBridge Scheduler を埋め込み、特定の時間になったらスタックの削除を行います。
事前準備
なお、事前準備として EventBridge Scheduler で利用する IAMロール を CloudFormation で用意する必要があります。
iam.yml
Description: EventBridge Scheduler IAM Role
Resources:
Role:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- scheduler.amazonaws.com
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/PowerUserAccess'
Path: "/"
Outputs:
DeleteSchedulerRoleARN:
Value: !GetAtt Role.Arn
Export:
Name: DeleteSchedulerRoleARN
コピペして使えるテンプレート
あとは、お好きなCloudFormationテンプレートに以下のパラメータとリソースを追加するだけで自動で削除してくれます。
追加するパラメータとリソース
Parameters:
Hour:
Description: Please type Delete Hour.
Type: String
Default: "13"
Minute:
Description: Please type Delete Minute.
Type: String
Default: "00"
Resources:
EventSchedule:
Type: AWS::Scheduler::Schedule
Properties:
Description: 'Delete Schedule'
ScheduleExpression: !Sub "cron(${Minute} ${Hour} ? * * *)"
ScheduleExpressionTimezone: "Asia/Tokyo"
FlexibleTimeWindow:
Mode: 'OFF'
State: ENABLED
Target:
Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:deleteStack"
Input: !Sub "{ \"StackName\": \"${AWS::StackName}\" }"
RoleArn: !ImportValue DeleteSchedulerRoleARN
パラメータについては、削除する時間の Hour と Minute を入力します。
- Hour
- 削除する時間の時
- Minute
- 削除する時間の分
実際にやってみる
以下の記事のテンプレートに対して実際に適用させてみます。
なお、EventBridge Scheduler で利用する IAMロールは作成済みとします。
作成していない方は、事前準備を実施してください。
テンプレートの準備
以下のようにテンプレートを用意します。元のテンプレートに削除用のテンプレートを追加します。
alb.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: ALB and Nginx on EC2
Parameters:
Hour:
Description: Please type Delete Hour.
Type: String
Default: "13"
Minute:
Description: Please type Delete Minute.
Type: String
Default: "00"
Resources:
EventSchedule:
Type: AWS::Scheduler::Schedule
Properties:
Description: 'Delete Schedule'
ScheduleExpression: !Sub "cron(${Minute} ${Hour} ? * * *)"
ScheduleExpressionTimezone: "Asia/Tokyo"
FlexibleTimeWindow:
Mode: 'OFF'
State: ENABLED
Target:
Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:deleteStack"
Input: !Sub "{ \"StackName\": \"${AWS::StackName}\" }"
RoleArn: !ImportValue DeleteSchedulerRoleARN
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-vpc
PublicSubnet0:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
VpcId: !Ref VPC
AvailabilityZone: ap-northeast-1a
CidrBlock: 10.0.0.0/24
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-public-1a-subnet
PublicSubnet1:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
VpcId: !Ref VPC
AvailabilityZone: ap-northeast-1c
CidrBlock: 10.0.1.0/24
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-public-1c-subnet
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-igw
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
DependsOn: AttachGateway
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-public-rt
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet0RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet0
RouteTableId: !Ref PublicRouteTable
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
ALBSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "ALB SG"
GroupName: !Sub ${AWS::StackName}-alb-sg
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-alb-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
EC2SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "EC2 SG"
GroupName: !Sub ${AWS::StackName}-ec2-sg
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-ec2-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref EC2Role
InstanceProfileName: !Sub ${AWS::StackName}-ec2-profile
EC2Role:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
RoleName: !Sub ${AWS::StackName}-ec2-role
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-ec2-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
KeyPair:
Type: 'AWS::EC2::KeyPair'
Properties:
KeyName: !Sub ${AWS::StackName}-keypair
WebServer:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-web
InstanceType: t3.nano
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: gp3
VolumeSize: 8
DeleteOnTermination: true
Encrypted: true
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
GroupSet:
- !Ref EC2SecurityGroup
SubnetId: !Ref PublicSubnet0
ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}'
IamInstanceProfile: !Ref EC2InstanceProfile
KeyName: !Ref KeyPair
DisableApiTermination: false
EbsOptimized: true
UserData:
Fn::Base64: |
#!/bin/bash
sudo amazon-linux-extras install nginx1
sudo systemctl enable nginx
sudo systemctl start nginx
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: "application"
Scheme: "internet-facing"
Name: !Sub ${AWS::StackName}-alb
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-alb
IpAddressType: ipv4
Subnets:
- !Ref PublicSubnet0
- !Ref PublicSubnet1
SecurityGroups:
- !Ref ALBSecurityGroup
ListenerHTTP:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${AWS::StackName}-tg
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-tg
Port: 80
Protocol: HTTP
Matcher:
HttpCode: '200'
VpcId: !Ref VPC
TargetType: instance
Targets:
- Id: !Ref WebServer
Outputs:
ALBURL:
Description: ALB endpoint URL
Value: !Join
- ""
- - http://
- !GetAtt ALB.DNSName
スタックの作成
あとは、上記のテンプレートでスタックを作成します。以下の場合は、14:00に削除するようにパラメータを与えています。
スタック作成後は、しばらく待つと通常通り CREATE_COMPLETE になります。
スタックの自動削除
パラメータに指定した時間までしばらく待ちます。
すると、以下のようにスタックが自動的に削除されます。
まとめ
本記事では、自動でCloudFormationのスタックを削除する方法をご紹介しました。
EventBridge Scheduler のおかげで結構シンプルに実現できたと思います。
皆さんもリソースの削除忘れがないようこのテンプレートをぜひ利用してみてください。