rainを使ったCloudFormationスタックおよびテンプレートの運用方法
はじめに
前回紹介した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行まで)など上限を決める、分割パターンは、中規模〜大規模システムで権限分離を目的とする場合など、運用体制を鑑みて決めるのが良いと思います。