この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
前回紹介したrain、rainの操作は理解したのですが、どうやって更新していく?テンプレートは集約する?分割する?このあたりも含めて、もうひと工夫した使い方を紹介します。
テンプレートの集約or分割
CloudFormation(以下、CFn)で必ず出てくる(?)テンプレートの管理問題ですが、どちらが良いのか論争はここでは語らず、集約と分割、それぞれのパターンに合わせたrainの活用方法をご案内します。
以下のCFnテンプレートを例に進めていきます。
sample.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
vpcCidr:
Type: String
domainName:
Type: String
hostedZoneId:
Type: String
### Ec2 Parameters
ec2Ami:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
instanceType:
Type: String
### Rds Parameters
instanceClass:
Type: String
masterPassword:
Type: String
NoEcho: true
Resources:
### VPC
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref vpcCidr
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-vpc
- Key: BillingGroup
Value: !Ref billingTag
### Internet Gateway
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-igw
- Key: BillingGroup
Value: !Ref billingTag
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref MyInternetGateway
VpcId: !Ref MyVPC
### Public Subnet
MyPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [0, !Cidr [!GetAtt MyVPC.CidrBlock, 2, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-public-subnet-1
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [1, !Cidr [!GetAtt MyVPC.CidrBlock, 2, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-public-subnet-2
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
### Public Route Table
MyPublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-public-rtb
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPublicRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
RouteTableId: !Ref MyPublicRouteTable
MyPublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPublicRouteTable
SubnetId: !Ref MyPublicSubnet1
MyPublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPublicRouteTable
SubnetId: !Ref MyPublicSubnet2
### Private Subnet
MyPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [10, !Cidr [!GetAtt MyVPC.CidrBlock, 12, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-private-subnet-1
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [11, !Cidr [!GetAtt MyVPC.CidrBlock, 12, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-private-subnet-2
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
### Private Route Table
MyPrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-private-rtb
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPrivateRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
RouteTableId: !Ref MyPrivateRouteTable
MyPrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPrivateRouteTable
SubnetId: !Ref MyPrivateSubnet1
MyPrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPrivateRouteTable
SubnetId: !Ref MyPrivateSubnet2
### Security Group Alb
AlbSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Application load barancer.
GroupName: !Sub ${env}-${sysName}-alb-sg
VpcId: !Ref MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: "-1"
FromPort: 0
ToPort: 0
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-alb-sg
- Key: BillingGroup
Value: !Ref billingTag
### Security Group ec2
ec2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ec2 Instances.
GroupName: !Sub ${env}-${sysName}-ec2-sg
VpcId: !Ref MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref AlbSG
SecurityGroupEgress:
- IpProtocol: "-1"
FromPort: 0
ToPort: 0
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-ec2-sg
- Key: BillingGroup
Value: !Ref billingTag
### Security Group Rds
RdsSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Rds Instances.
GroupName: !Sub ${env}-${sysName}-rds-sg
VpcId: !Ref MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref ec2SG
SecurityGroupEgress:
- IpProtocol: "-1"
FromPort: 0
ToPort: 0
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-rds-sg
- Key: BillingGroup
Value: !Ref billingTag
### Alb Acm
AlbAcm:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref domainName
DomainValidationOptions:
- DomainName: !Ref domainName
HostedZoneId: !Ref hostedZoneId
ValidationMethod: DNS
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-albacm
- Key: BillingGroup
Value: !Ref billingTag
### Alb
Alb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: !Sub ${env}-${sysName}-alb
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSG
Subnets:
- !Ref MyPublicSubnet1
- !Ref MyPublicSubnet2
Type: application
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-api
- Key: BillingGroup
Value: !Ref billingTag
AlbListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
Certificates:
- CertificateArn: !Ref AlbAcm
DefaultActions:
- FixedResponseConfig:
ContentType: text/plain
MessageBody: Unauthorized Access
StatusCode: "403"
Type: fixed-response
LoadBalancerArn: !Ref Alb
Port: 443
Protocol: HTTPS
SslPolicy: ELBSecurityPolicy-2016-08
AlbListnerRule1:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- '*'
ListenerArn: !Ref AlbListener
Priority: 1
### Alb DNS Record
AlbRecordSet:
Type: AWS::Route53::RecordSet
Properties:
Name: !Ref domainName
Type: A
AliasTarget:
HostedZoneId: Z14GRHDCWA56QT
DNSName: !Join
- ""
- - dualstack.
- !GetAtt Alb.DNSName
EvaluateTargetHealth: false
HostedZoneId: !Ref hostedZoneId
### Target Group for ec2 Instance
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${env}-${sysName}-api
Port: 80
Protocol: HTTP
TargetType: instance
Targets:
- Id: !Ref Instance1
- Id: !Ref Instance2
VpcId: !Ref MyVPC
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-tg
- Key: BillingGroup
Value: !Ref billingTag
### ec2 Profilee
ec2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
ec2Profile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref ec2Role
### ec2 Instance
Instance1:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref ec2Ami
InstanceType: !Ref instanceType
CreditSpecification:
CPUCredits: standard
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: gp3
Iops: 3000
VolumeSize: 8
Encrypted: true
SecurityGroupIds:
- !Ref ec2SG
SubnetId: !Ref MyPrivateSubnet1
IamInstanceProfile: !Ref ec2Profile
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-1
- Key: BillingGroup
Value: !Ref billingTag
Instance2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref ec2Ami
InstanceType: !Ref instanceType
CreditSpecification:
CPUCredits: standard
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: gp3
Iops: 3000
VolumeSize: 8
Encrypted: true
SecurityGroupIds:
- !Ref ec2SG
SubnetId: !Ref MyPrivateSubnet2
IamInstanceProfile: !Ref ec2Profile
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-2
- Key: BillingGroup
Value: !Ref billingTag
### Rds Subnet Group
SubnetGroupRds:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
SubnetIds:
- !Ref MyPrivateSubnet1
- !Ref MyPrivateSubnet2
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-rds-SubnetGroup
- Key: BillingGroup
Value: !Ref billingTag
### Rds Parameter Group
ParameterGroupAurora:
Type: AWS::RDS::DBParameterGroup
Properties:
Description: !Sub ${env}-${sysName}-parametergroup
Family: aurora-mysql5.7
## Rds Cluster Parameter Group
clusterParameterGroupAurora:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Description: !Sub ${env}-${sysName}-cluster-parametergroup
Family: aurora-mysql5.7
Parameters:
character_set_client: utf8
character_set_connection: utf8
character_set_database: utf8
character_set_results: utf8
character_set_server: utf8
general_log: 1
server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
server_audit_logging: 1
slow_query_log: 1
time_zone: Asia/Tokyo
### Rds Aurora Cluster
clusterAurora:
Type: AWS::RDS::DBCluster
Properties:
MasterUsername: root
MasterUserPassword: !Ref masterPassword
DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.10.0
DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
DBSubnetGroupName: !Ref SubnetGroupRds
Port: 3306
PreferredBackupWindow: 17:00-18:00
BackupRetentionPeriod: 7
PreferredMaintenanceWindow: tue:18:00-tue:19:00
EnableCloudwatchLogsExports:
- audit
- error
- general
- slowquery
StorageEncrypted: true
VpcSecurityGroupIds:
- !Ref RdsSG
DeletionProtection: false
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-cluster
- Key: BillingGroup
Value: !Ref billingTag
# Rds Aurora Instance1
DbInstance1:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
DBClusterIdentifier: !Ref clusterAurora
DBInstanceClass: !Ref instanceClass
Engine: aurora-mysql
DBParameterGroupName: !Ref ParameterGroupAurora
AutoMinorVersionUpgrade: false
PubliclyAccessible: false
MonitoringInterval: 0
EnablePerformanceInsights: false
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-db1
- Key: BillingGroup
Value: !Ref billingTag
# Rds Aurora Instance2
DbInstance2:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
DBClusterIdentifier: !Ref clusterAurora
DBInstanceClass: !Ref instanceClass
Engine: aurora-mysql
DBParameterGroupName: !Ref ParameterGroupAurora
AutoMinorVersionUpgrade: false
PubliclyAccessible: false
MonitoringInterval: 0
EnablePerformanceInsights: false
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-db2
- Key: BillingGroup
Value: !Ref billingTag
テンプレート集約パターン
CFnテンプレートを集約している場合は、特に意識する必要ことは無いと思います。 rain deploy
実行時にテンプレートがfmtされる為、テンプレートをfmtし、チェックします。
# fmtしてテンプレートに書き込む
rain fmt -w *tenplatefile*
# fmtされているかチェック
rain fmt -v *tenplatefile*
*tenplatefile*: formatted OK
fmtしたテンプレートを指定してスタックを作成します。
rain deploy -y ./sample.yml dev-stack --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
vpcCidr=$CIDR,\
domainName=$DOMAINNAME,\
hostedZoneId=$HOSTEDZONEID,\
instanceType=$INSTANCETYPE.\
instanceClass=$INSTANCECLASS,\
masterPassword=$MASTERPASSWORD
.
.
.
Successfully deployed dev-stack
作成したスタックを更新するには↑と同じコマンドです。 -y, --yes
オプションがあると差分確認できずに更新されてしまいます。差分確認するには2種類の方法があります。
-y, --yes オプションをはずす
対話式のデプロイとなります。具体的には rain deploy
実行後に変更セットが作成し、変更内容を出力します。 (y/N)
でスタックの更新可否を入力します。意図したリソースのが表示されれば Y 、そうでなければ n で実行します。
rain deploy ./sample.yml dev-stack --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
vpcCidr=$CIDR,\
domainName=$DOMAINNAME,\
hostedZoneId=$HOSTEDZONEID,\
instanceType=$INSTANCETYPE.\
instanceClass=$INSTANCECLASS,\
masterPassword=$MASTERPASSWORD
Enter a value for parameter 'ec2Ami' (existing value: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2):
CloudFormation will make the following changes:
Stack dev-stack:
> AWS::RDS::DBCluster clusterAurora
Do you wish to continue? (Y/n)
diffによるテンプレート比較
対話式ではなく、テンプレートから差分を抽出できます。先程のテンプレートの clusterAurora
の削除保護を有効にします。
.
.
.
### Rds Aurora Cluster
clusterAurora:
Type: AWS::RDS::DBCluster
Properties:
MasterUsername: root
MasterUserPassword: !Ref masterPassword
DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.10.0
DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
DBSubnetGroupName: !Ref SubnetGroupRds
Port: 3306
PreferredBackupWindow: 17:00-18:00
BackupRetentionPeriod: 7
PreferredMaintenanceWindow: tue:18:00-tue:19:00
EnableCloudwatchLogsExports:
- audit
- error
- general
- slowquery
StorageEncrypted: true
VpcSecurityGroupIds:
- !Ref RdsSG
DeletionProtection: true # ←削除保護を有効
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-cluster
- Key: BillingGroup
Value: !Ref billingTag
.
.
.
テンプレートを修正したらfmtも忘れずに。
# fmtしてテンプレートに書き込む
rain fmt -w *tenplatefile*
# fmtされているかチェック
rain fmt -v *tenplatefile*
*tenplatefile*: formatted OK
次に対象スタックからテンプレートを出力します。デプロイしたであろうテンプレートがローカルにあったとしてもそのテンプレートが本当に正しいか?ということはわからないので必ず対象スタックから出力したテンプレートと比較します。
# デプロイ済みスタックからテンプレートを出力。ファイル(tmp-dev-stack.yml)に書き出し
rain cat dev-stack > ./tmp-dev-stack.yml
# テンプレートを比較
% rain diff tmp-dev-stack.yml sample.yml
(|) Resources:
(|) clusterAurora:
(|) Properties:
(>) DeletionProtection: true
rain diff
は差分があるリソースのみ出力します。変更セットと違ってどのリソースのパラメータが変更されるか、確認できます。めっちゃわかりやすいです。
意図した変更であれば rain deploy -y
でスタックを更新します。
テンプレート分割パターン
テンプレートを分割して管理する場合、デプロイ方法が異なります。まずは例で用意したsample.ymlを分割します。
vpc.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
### Vpc Parameters
vpcCidr:
Type: String
Resources:
### VPC
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref vpcCidr
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-vpc
- Key: BillingGroup
Value: !Ref billingTag
### Internet Gateway
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-igw
- Key: BillingGroup
Value: !Ref billingTag
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref MyInternetGateway
VpcId: !Ref MyVPC
### Public Subnet
MyPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [0, !Cidr [!GetAtt MyVPC.CidrBlock, 2, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-public-subnet-1
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [1, !Cidr [!GetAtt MyVPC.CidrBlock, 2, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-public-subnet-2
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
### Public Route Table
MyPublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-public-rtb
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPublicRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
RouteTableId: !Ref MyPublicRouteTable
MyPublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPublicRouteTable
SubnetId: !Ref MyPublicSubnet1
MyPublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPublicRouteTable
SubnetId: !Ref MyPublicSubnet2
### Private Subnet
MyPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [10, !Cidr [!GetAtt MyVPC.CidrBlock, 12, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-private-subnet-1
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [11, !Cidr [!GetAtt MyVPC.CidrBlock, 12, 8]]
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-private-subnet-2
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
### Private Route Table
MyPrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-private-rtb
- Key: BillingGroup
Value: !Ref billingTag
VpcId: !Ref MyVPC
MyPrivateRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
RouteTableId: !Ref MyPrivateRouteTable
MyPrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPrivateRouteTable
SubnetId: !Ref MyPrivateSubnet1
MyPrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref MyPrivateRouteTable
SubnetId: !Ref MyPrivateSubnet2
### Outputs
Outputs:
MyVPC:
Value: !Ref MyVPC
Export:
Name: !Sub ${env}-${sysName}-MyVPC
MyPublicSubnet1:
Value: !Ref MyPublicSubnet1
Export:
Name: !Sub ${env}-${sysName}-MyPublicSubnet1
MyPublicSubnet2:
Value: !Ref MyPublicSubnet2
Export:
Name: !Sub ${env}-${sysName}-MyPublicSubnet2
MyPrivateSubnet1:
Value: !Ref MyPrivateSubnet1
Export:
Name: !Sub ${env}-${sysName}-MyPrivateSubnet1
MyPrivateSubnet2:
Value: !Ref MyPrivateSubnet2
Export:
Name: !Sub ${env}-${sysName}-MyPrivateSubnet2
securitygroup.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
Resources:
### Security Group Alb
AlbSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Application load barancer.
GroupName: !Sub ${env}-${sysName}-alb-sg
VpcId: !ImportValue
Fn::Sub: ${env}-${sysName}-MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: "-1"
FromPort: 0
ToPort: 0
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-alb-sg
- Key: BillingGroup
Value: !Ref billingTag
### Security Group ec2
ec2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ec2 Instances.
GroupName: !Sub ${env}-${sysName}-ec2-sg
VpcId: !ImportValue
Fn::Sub: ${env}-${sysName}-MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref AlbSG
SecurityGroupEgress:
- IpProtocol: "-1"
FromPort: 0
ToPort: 0
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-ec2-sg
- Key: BillingGroup
Value: !Ref billingTag
### Security Group Rds
RdsSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Rds Instances.
GroupName: !Sub ${env}-${sysName}-rds-sg
VpcId: !ImportValue
Fn::Sub: ${env}-${sysName}-MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref ec2SG
SecurityGroupEgress:
- IpProtocol: "-1"
FromPort: 0
ToPort: 0
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-rds-sg
- Key: BillingGroup
Value: !Ref billingTag
### Outputs
Outputs:
AlbSG:
Value: !Ref AlbSG
Export:
Name: !Sub ${env}-${sysName}-alb-sg
ec2SG:
Value: !Ref ec2SG
Export:
Name: !Sub ${env}-${sysName}-ec2-sg
RdsSG:
Value: !Ref RdsSG
Export:
Name: !Sub ${env}-${sysName}-rds-sg
rds.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
### Rds Parameters
instanceClass:
Type: String
masterPassword:
Type: String
NoEcho: true
Resources:
### Rds Subnet Group
SubnetGroupRds:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
SubnetIds:
- !ImportValue
Fn::Sub: ${env}-${sysName}-MyPrivateSubnet1
- !ImportValue
Fn::Sub: ${env}-${sysName}-MyPrivateSubnet2
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-rds-SubnetGroup
- Key: BillingGroup
Value: !Ref billingTag
### Rds Parameter Group
ParameterGroupAurora:
Type: AWS::RDS::DBParameterGroup
Properties:
Description: !Sub ${env}-${sysName}-parametergroup
Family: aurora-mysql5.7
## Rds Cluster Parameter Group
clusterParameterGroupAurora:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Description: !Sub ${env}-${sysName}-cluster-parametergroup
Family: aurora-mysql5.7
Parameters:
character_set_client: utf8
character_set_connection: utf8
character_set_database: utf8
character_set_results: utf8
character_set_server: utf8
general_log: 1
server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
server_audit_logging: 1
slow_query_log: 1
time_zone: Asia/Tokyo
### Rds Aurora Cluster
clusterAurora:
Type: AWS::RDS::DBCluster
Properties:
MasterUsername: root
MasterUserPassword: !Ref masterPassword
DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.10.0
DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
DBSubnetGroupName: !Ref SubnetGroupRds
Port: 3306
PreferredBackupWindow: 17:00-18:00
BackupRetentionPeriod: 7
PreferredMaintenanceWindow: tue:18:00-tue:19:00
EnableCloudwatchLogsExports:
- audit
- error
- general
- slowquery
StorageEncrypted: true
VpcSecurityGroupIds:
- !ImportValue
Fn::Sub: "${env}-${sysName}-rds-sg"
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-cluster
- Key: BillingGroup
Value: !Ref billingTag
# Rds Aurora Instance1
DbInstance1:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
DBClusterIdentifier: !Ref clusterAurora
DBInstanceClass: !Ref instanceClass
Engine: aurora-mysql
DBParameterGroupName: !Ref ParameterGroupAurora
AutoMinorVersionUpgrade: false
PubliclyAccessible: false
MonitoringInterval: 0
EnablePerformanceInsights: false
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-db1
- Key: BillingGroup
Value: !Ref billingTag
# Rds Aurora Instance2
DbInstance2:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
DBClusterIdentifier: !Ref clusterAurora
DBInstanceClass: !Ref instanceClass
Engine: aurora-mysql
DBParameterGroupName: !Ref ParameterGroupAurora
AutoMinorVersionUpgrade: false
PubliclyAccessible: false
MonitoringInterval: 0
EnablePerformanceInsights: false
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-db2
- Key: BillingGroup
Value: !Ref billingTag
acm.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
### Acm And Route53 Parameters
domainName:
Type: String
hostedZoneId:
Type: String
Resources:
### Alb Acm
AlbAcm:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref domainName
DomainValidationOptions:
- DomainName: !Ref domainName
HostedZoneId: !Ref hostedZoneId
ValidationMethod: DNS
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-albacm
- Key: BillingGroup
Value: !Ref billingTag
### Outputs
Outputs:
AlbAcm:
Value: !Ref AlbAcm
Export:
Name: !Sub ${env}-${sysName}-albacm
iam.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
Resources:
### ec2 Profilee
ec2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
ec2Profile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref ec2Role
Outputs:
ec2Profile:
Value: !Ref ec2Profile
Export:
Name: !Sub ${env}-${sysName}-ec2Profile
ec2.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
### Ec2 Parameters
ec2Ami:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
instanceType:
Type: String
Resources:
### ec2 Instance
Instance1:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref ec2Ami
InstanceType: !Ref instanceType
CreditSpecification:
CPUCredits: standard
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: gp3
Iops: 3000
VolumeSize: 8
Encrypted: true
SecurityGroupIds:
- !ImportValue
Fn::Sub: ${env}-${sysName}-ec2-sg
SubnetId: !ImportValue
Fn::Sub: ${env}-${sysName}-MyPrivateSubnet1
IamInstanceProfile: !ImportValue
Fn::Sub: ${env}-${sysName}-ec2Profile
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-instance1
- Key: BillingGroup
Value: !Ref billingTag
Instance2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref ec2Ami
InstanceType: !Ref instanceType
CreditSpecification:
CPUCredits: standard
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: gp3
Iops: 3000
VolumeSize: 8
Encrypted: true
SecurityGroupIds:
- !ImportValue
Fn::Sub: ${env}-${sysName}-ec2-sg
SubnetId: !ImportValue
Fn::Sub: ${env}-${sysName}-MyPrivateSubnet2
IamInstanceProfile: !ImportValue
Fn::Sub: ${env}-${sysName}-ec2Profile
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-instance2
- Key: BillingGroup
Value: !Ref billingTag
Outputs:
Instance1:
Value: !Ref Instance1
Export:
Name: !Sub ${env}-${sysName}-instance1
Instance2:
Value: !Ref Instance2
Export:
Name: !Sub ${env}-${sysName}-instance2
alb.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
Resources:
### Alb
Alb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: !Sub ${env}-${sysName}-alb
Scheme: internet-facing
SecurityGroups:
- !ImportValue
Fn::Sub: ${env}-${sysName}-alb-sg
Subnets:
- !ImportValue
Fn::Sub: ${env}-${sysName}-MyPublicSubnet1
- !ImportValue
Fn::Sub: ${env}-${sysName}-MyPublicSubnet2
Type: application
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-api
- Key: BillingGroup
Value: !Ref billingTag
AlbListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
Certificates:
- CertificateArn: !ImportValue
Fn::Sub: ${env}-${sysName}-albacm
DefaultActions:
- FixedResponseConfig:
ContentType: text/plain
MessageBody: Unauthorized Access
StatusCode: "403"
Type: fixed-response
LoadBalancerArn: !Ref Alb
Port: 443
Protocol: HTTPS
SslPolicy: ELBSecurityPolicy-2016-08
AlbListnerRule1:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- '*'
ListenerArn: !Ref AlbListener
Priority: 1
### Target Group for ec2 Instance
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${env}-${sysName}-ec2
Port: 80
Protocol: HTTP
TargetType: instance
Targets:
- Id: !ImportValue
Fn::Sub: ${env}-${sysName}-instance1
- Id: !ImportValue
Fn::Sub: ${env}-${sysName}-instance2
VpcId:
Fn::ImportValue: !Sub ${env}-${sysName}-MyVPC
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-tg
- Key: BillingGroup
Value: !Ref billingTag
### Outputs
Outputs:
AlbDomainName:
Value: !Join
- ""
- - dualstack.
- !GetAtt Alb.DNSName
Export:
Name: !Sub ${env}-${sysName}-alb-domain
route53.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
### Acm And Route53 Parameters
domainName:
Type: String
hostedZoneId:
Type: String
Resources:
### Alb DNS Record
AlbRecordSet:
Type: AWS::Route53::RecordSet
Properties:
Name: !Ref domainName
Type: A
AliasTarget:
HostedZoneId: Z14GRHDCWA56QT
DNSName: !ImportValue
Fn::Sub: ${env}-${sysName}-alb-domain
EvaluateTargetHealth: false
HostedZoneId: !Ref hostedZoneId
集約パターンと同じ用にfmtしますが、ワイルドカードで指定することで複数のテンプレートを対象にfmtできます。(再帰的な実行不可)
# ディレクトリ配下にあるテンプレートをfmtしてテンプレートに書き込む
rain fmt -w ./template/*yml
# ディレクトリ配下にあるテンプレートがfmtされているかチェック
rain fmt -v ./template/*yml
./template/acm.yml: formatted OK
./template/alb.yml: formatted OK
./template/ec2.yml: formatted OK
./template/iam.yml: formatted OK
./template/rds.yml: formatted OK
./template/route53.yml: formatted OK
./template/securitygroup.yml: formatted OK
./template/vpc.yml: formatted OK
分割したテンプレートをデプロイしていきます。テンプレートによって指定するパラメータが異なります。
rain deploy -y ./template/vpc.yml dev-vpc --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
vpcCidr=$CIDR
rain deploy -y ./template/securitygroup.yml dev-securitygroup --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG
rain deploy -y ./template/rds.yml dev-rds --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
instanceClass=$INSTANCECLASS,\
masterPassword=$MASTERPASSWORD
rain deploy -y ./template/acm.yml dev-acm --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
domainName=$DOMAINNAME,\
hostedZoneId=$HOSTEDZONEID
rain deploy -y ./template/iam.yml dev-iam --params \
env=$ENV,\
sysName=$SYSNAME
rain deploy -y ./template/ec2.yml dev-ec2 --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
instanceType=$INSTANCETYPE
rain deploy -y ./template/alb.yml dev-alb --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG
rain deploy -y ./template/route53.yml dev-route53 --params \
env=$ENV,\
sysName=$SYSNAME,\
domainName=$DOMAINNAME,\
hostedZoneId=$HOSTEDZONEID
分割したことで、テンプレート毎の可読性が良くなりました。ただ、テンプレートが増える度に実行するコマンドを用意するのも運用の手間となります。またデプロイコマンドが複数あると実行ミスの原因にもなります。
なのでNested Stackでデプロイします。Nested Stackは、親スタックに記述した子スタック(AWS::CloudFormation::Stack)を更新してくれます。子スタック(AWS::CloudFormation::Stack)を実行する為には、テンプレートをS3ケットに格納して呼び出すので、S3バケットの操作が必要になりますが、rainはそんなところもうまくやってくれます。
pkgによるアーティファクトのパッケージ化
rainは、ローカルにあるテンプレートをS3にアップロードできます。さらにrain独自のディレクティブを使ってS3バケットにアップロードされたS3URL or URIをテンプレートに埋め込んでくれます。アップロードしたテンプレートの名前など意識する必要がないのが特徴です。また、S3バケットはrainが自動で作成してくれるのでS3バケットも意識する必要がありません。(v1.2.0に実装された機能)
それでは、親スタックを作成します。
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
### Vpc Parameters
vpcCidr:
Type: String
### Acm And Route53 Parameters
domainName:
Type: String
hostedZoneId:
Type: String
### Ec2 Parameters
instanceType:
Type: String
### Rds Parameters
instanceClass:
Type: String
masterPassword:
Type: String
NoEcho: true
Resources:
### VPC
Vpc:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
vpcCidr: !Ref vpcCidr
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-vpc-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/vpc.yml
### Iam
Iam:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-iam-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/iam.yml
### Acm
Acm:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
domainName: !Ref domainName
hostedZoneId: !Ref hostedZoneId
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-acm-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/acm.yml
### Security Group
SecurityGroup:
Type: AWS::CloudFormation::Stack
DependsOn: Vpc
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-securitygroup-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/securitygroup.yml
### Rds
Rds:
Type: AWS::CloudFormation::Stack
DependsOn:
- SecurityGroup
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
instanceClass: !Ref instanceClass
masterPassword: !Ref masterPassword
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-rds-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/rds.yml
### Ec2
Ec2:
Type: AWS::CloudFormation::Stack
DependsOn:
- SecurityGroup
- Iam
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
instanceType: !Ref instanceType
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-ec2-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/ec2.yml
### ALb
Alb:
Type: AWS::CloudFormation::Stack
DependsOn:
- SecurityGroup
- Acm
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-alb-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/alb.yml
### Route 53
route53:
Type: AWS::CloudFormation::Stack
DependsOn:
- Alb
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
domainName: !Ref domainName
hostedZoneId: !Ref hostedZoneId
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-route53-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: !Rain::S3Http ./template/route53.yml
各子スタックの TemplateURL:
でrain独自のディレクティブ !Rain::S3Http ./template/securitygroup.yml
を指定します。rain deploy
を実行するとrainが作成したS3バケット(rain-artifacts-AWSアカウントID-リージョン)に !Rain::S3Http
で指定したローカルテンプレートをアップロードしてS3URLを埋め込みます。 rain deploy
した後のスタックのテンプレートは以下です。
% rain cat dev-stack
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Parameters:
### Common Parameters
env:
Type: String
sysName:
Type: String
billingTag:
Type: String
### Vpc Parameters
vpcCidr:
Type: String
### Acm And Route53 Parameters
domainName:
Type: String
hostedZoneId:
Type: String
### Ec2 Parameters
instanceType:
Type: String
### Rds Parameters
instanceClass:
Type: String
masterPassword:
Type: String
NoEcho: true
Resources:
### VPC
Vpc:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
vpcCidr: !Ref vpcCidr
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-vpc-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/28bcbc2d2191b0c77a8107d8966058391d8f60c71a68783a5ea2a1e1f73d44e5
### Iam
Iam:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-iam-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/d17946b5b9df3fba03d20c6991c90b6ed3d9087d288cddf926b9d0dddfdd09ed
### Acm
Acm:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
domainName: !Ref domainName
hostedZoneId: !Ref hostedZoneId
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-acm-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/cb5b53e05fc629eb50788b6cfdfd7c618de006d0f21ee249dfeadf4227fdb54d
### Security Group
SecurityGroup:
Type: AWS::CloudFormation::Stack
DependsOn: Vpc
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-securitygroup-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/fcf9a6976c8c882599383e17502c797f851674c99cadd9dd0d2094fd10b70b85
### Rds
Rds:
Type: AWS::CloudFormation::Stack
DependsOn:
- SecurityGroup
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
instanceClass: !Ref instanceClass
masterPassword: !Ref masterPassword
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-rds-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/ecfdc118cc69d27356e317e15a19236369e4b8673d7d60953c2f6bda9e513adf
### Ec2
Ec2:
Type: AWS::CloudFormation::Stack
DependsOn:
- SecurityGroup
- Iam
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
instanceType: !Ref instanceType
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-ec2-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/bf8c340891c3749c08e0cbb875b7dab568d022f0355c95c80924699d2f92c641
### ALb
Alb:
Type: AWS::CloudFormation::Stack
DependsOn:
- SecurityGroup
- Acm
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
billingTag: !Ref billingTag
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-alb-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/a01e02df03aef2f72e3ada9deea7e7f14f18eefbf8f0acbe828e41dbc5b7921a
### Route 53
route53:
Type: AWS::CloudFormation::Stack
DependsOn:
- Alb
Properties:
Parameters:
env: !Ref env
sysName: !Ref sysName
domainName: !Ref domainName
hostedZoneId: !Ref hostedZoneId
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-route53-stack
- Key: BillingGroup
Value: !Ref billingTag
TemplateURL: https://rain-artifacts-0123456789010-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/ba9e53772ff334b22f2dcb3d483cff2264e6141cd6964e6883c809780642620c
TemplateURL:
にS3URLが埋め込まれてますね。S3バケット、オブジェクトキーを意識せずにいい感じにやってくれます。注意事項として、rain独自のディレクティブ !Rain::S3Http
や !Rain::Include
は、cfn-lintで不正な関数とみなされます。CI/CDやVSCodeのCloudFormation Linterでテンプレートを検証する場合はエラーが返されるので注意してください。
[cfn-lint] E3002: Property "Resources/VPC/Properties/TemplateURL" has an illegal function Fn::Rain::S3Http
また、deploy時の出力は以下の通りです。子スタック全体の状態が確認できるのが良いですね。
% rain deploy -y ./template/root-stack.yml dev-stack --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
vpcCidr=$CIDR,\
domainName=$DOMAINNAME,\
hostedZoneId=$HOSTEDZONEID,\
instanceType=$INSTANCETYPE.\
instanceClass=$INSTANCECLASS,\
masterPassword=$MASTERPASSWORD
Deploying template 'root-stack.yml' as stack 'dev-stack' in ap-northeast-1.
Stack dev-stack: CREATE_IN_PROGRESS - 5 resources pending, 3 resources in progress
- Stack dev-stack-Acm-1CMVVWZXF93IP: CREATE_IN_PROGRESS - 1 resource in progress
- Stack Alb: PENDING
- Stack Ec2: PENDING
- Stack dev-stack-Iam-URI8LQ30Z0A4: CREATE_IN_PROGRESS - 1 resource in progress
- Stack Rds: PENDING
- Stack SecurityGroup: PENDING
- Stack dev-stack-Vpc-1FUNOD3KZSE6M: CREATE_IN_PROGRESS - 2 resources in progress
- Stack route53: PENDING
.˙ 19s
分割パターンでもスタック(子スタック)を更新してみます。集約パターンと同様に DeletionProtection: true
を追記します。
.
.
.
### Rds Aurora Cluster
clusterAurora:
Type: AWS::RDS::DBCluster
Properties:
MasterUsername: root
MasterUserPassword: !Ref masterPassword
DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.10.0
DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
DBSubnetGroupName: !Ref SubnetGroupRds
DeletionProtection: true # ←削除保護を有効
Port: 3306
PreferredBackupWindow: 17:00-18:00
BackupRetentionPeriod: 7
PreferredMaintenanceWindow: tue:18:00-tue:19:00
EnableCloudwatchLogsExports:
- audit
- error
- general
- slowquery
StorageEncrypted: true
VpcSecurityGroupIds:
- !Ref RdsSG
DeletionProtection: false
Tags:
- Key: Name
Value: !Sub ${env}-${sysName}-cluster
- Key: BillingGroup
Value: !Ref billingTag
.
.
.
Nested Stackの更新だと仕様(?)によりすべての子スタックが更新対象と表示されます。何が更新対象かわかりません。
% rain deploy -y ./template/root-stack.yml dev-stack --params \
env=$ENV,\
sysName=$SYSNAME,\
billingTag=$BILLINGTAG,\
vpcCidr=$CIDR,\
domainName=$DOMAINNAME,\
hostedZoneId=$HOSTEDZONEID,\
instanceType=$INSTANCETYPE.\
instanceClass=$INSTANCECLASS,\
masterPassword=$MASTERPASSWORD
CloudFormation will make the following changes:
Stack dev-stack:
> AWS::CloudFormation::Stack Acm
> AWS::CloudFormation::Stack Alb
> AWS::CloudFormation::Stack Ec2
> AWS::CloudFormation::Stack Iam
> AWS::CloudFormation::Stack Rds
> AWS::CloudFormation::Stack SecurityGroup
> AWS::CloudFormation::Stack Vpc
> AWS::CloudFormation::Stack route53
Do you wish to continue? (Y/n)
なので集約パターンでもやった更新対象の子スタックのテンプレートとローカルテンプレートを rain diff
で地道に確認する方法しか思いつかず。。何かいい方法が見つかればブログにします。
# デプロイ済みスタックからテンプレートを出力。ファイルに書き出し
rain cat dev-stack-Iam-3EZQNF9G4NY6 > ./tmp-iam.yml
# テンプレートを比較
% rain diff tmp-dev-stack.yml sample.yml
(|) Resources:
(|) clusterAurora:
(|) Properties:
(>) DeletionProtection: true
ちなみに、 !Rain::S3Http ./template/securitygroup.yml
はスタックとの差分がなければテンプレートはアップロードされません。(更新対象の子スタックだけ出力してくれると嬉しいな)
まとめ
rainを使ったテンプレートの管理についてまとめました。集約パターンは、AWSリソースが少ないまたは運用でカバーできる行数(1000行まで)など上限を決める、分割パターンは、中規模〜大規模システムで権限分離を目的とする場合など、運用体制を鑑みて決めるのが良いと思います。