はじめに
みなさんCloudFormationは利用しておりますでしょうか?
AWSの代表的なIaCのため、使ったことがあるもしくは聞いたことがある人は多いと思います。
私も業務で利用しているのですが、基本的に複数のリソースを作成したい場合には
1つのテンプレートにすべてのリソースを記述、もしくはリソースごとにテンプレートを分割して複数回テンプレートを実行しています。
複数のリソースを一括で作成したい場合に便利な ”ネストされたスタック” という使い方があることを知ったので、実際に動かしながら運用について考えてみたいと思います。
この記事の対象
今回はCloudFormationについての基礎知識(パラメータの意味がわかる)がある方が対象です。
また検証用に利用したテンプレートは本記事で公開しますが、テンプレート内の記述についてはネストと関係がある箇所のみ説明を行います。
ネストされたスタックとは
CloudFormationのテンプレート構成の技法のひとつで、1つのスタック内に複数のテンプレートを入れ子状にすることができます。
1つのルートスタックと複数の子スタックからなり、1つのテンプレートを分割して階層的にスタックを作成する形になります。
最上位層となるルートスタックでは ”AWS::CloudFormation::Stack” を利用して子スタックのテンプレートを指定します。
やってみる
検証内容
さっそく試してみたいと思います。
今回構築するリソースは以下です。
- VPC ×1
- サブネット ×4 (パブリック ×2, プライベート ×2)
- インタネットゲートウェイ ×1
- NATゲートウェイ ×2
- EC2インスタンス ×1
このリソースを以下のようにスタックを分割します。
- VPC ×1 → VpcStack
- サブネット ×4 (パブリック ×2, プライベート ×2) →SubnetStack
- インタネットゲートウェイ ×1 →SubnetStack
- NATゲートウェイ ×2 →SubnetStack
- EC2インスタンス ×1 →EC2Stack
そして、スタックごとにテンプレートを作成します。
構成図に表すと以下のようになります。
構築物
スタックの分割
テンプレートの分割
テンプレート
今回用意したテンプレートをピックアップして紹介します。
※全文は後述
root.yaml
大本となるルートスタックから作成していきます。
まず最初のParametersセクションでは、子のスタックに継承するパラメータを入力可能にします。
例)EC2Stackで利用するためのインスタンスタイプを指定させる。
## Parametersは子スタックが利用するパラメータを入力
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
stackSystem:
Type: String
Description: Specify the sub system name abbreviation.
MinLength: 4
MaxLength: 8
~~中略~~
## ::METADATA::
## CloudFormation parameter UI definitions
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: Sub System & Environment Configuration
Parameters:
- stackSystem
~~中略~~
ParameterLabels:
stackSystem:
default: Sub System Name
Resourcesセクションでは、子となるテンプレートファイルの保存場所を指定します。
Typeは AWS::CloudFormation::Stack にすることでテンプレートを指定することが可能です。
## Resourcesで子スタックのテンプレートファイルを指定
Typeは AWS::CloudFormation::Stack を利用する
## ::RESOURCES::
## Resources used in this solution
Resources:
VpcStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/vpc.yaml
Parameters:
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackVpcCidr: !Ref stackVpcCidr
vpc.yaml
次に子スタックを作成します。
ネットワークの土台となるVpcSatckの記述から確認します。
Parametersセクションの値はルートスタックから継承します。
必要な分パラメータを記載します。
Metadataセクションは不要です。(入力UIが必要ないため)
## ルートスタックからパラメータを継承
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
stackSystem:
Type: String
Description: Specify the sub system name abbreviation.
MinLength: 4
MaxLength: 8
Outputsセクションは子スタックごとに記載します。
## vpc.yaml で作成したリソースを出力する
## ::OUTPUTS::
## Outputs useful in other templates
Outputs:
VpcId:
Description: VPC Id
Value: !Ref Vpc
subnet.yaml
次にSubnetStackです。
SubnetStackでは サブネットを配置するVPCを指定するときに、VPCStackで作成したVPCを参照する必要があります。
その場合はルートスタックで経由でVpcStackの出力を継承することができます。
ルートスタックのSubnetStackを設定する箇所に、別のスタック(今回はVpcStack)の出力を継承するため ”!GetAtt [StackName].Outputs.[OutputName]" (今回は!GetAtt VpcStack.Outputs.VpcId)を指定します。
## ::RESOURCES::
## Resources used in this solution
Resources:
~~中略~~
## vpc.yamlの出力をsubnet.yamlに継承(!GetAtt VpcStack.Outputs.VpcId)
SubnetStack:
Type: AWS::CloudFormation::Stack
DependsOn:
- VpcStack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/subnet.yaml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
その後、SubnetStackの Parametersセクションにも継承した値を設定します。
## Parameters で VpcIdの値を継承
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: Specify the VpcId.
~~中略~~
## ::RESOURCES::
## Resources used in this solution
Resources:
~~中略~~
## Parametersで継承したVpcIdでパラメータを指定(!Ref VpcId)
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref stackPublicSubnet01AZ
CidrBlock: !Ref stackPublicSubnet01Cidr
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-public01"
上記でVPCStackの値をSubnetStackに継承することができました。
補足
テンプレート間の受け渡しについて、ネストされたスタック同士であればこのようにOutPutsセクションで指定した値を受け渡しができます。
単純にテンプレートを分けた場合はExportが必要なことに比べて簡易ですみます。
何故ならOutPutsセクションの値はネストされたスタック間で一意である必要がありますが、アカウント間で一意である必要はありません。
対してExportはアカウント間で一意でなければならないため、OutPutsセクションの方が再利用性が高く使い勝手がよいです。
CloudFormationテンプレート間で値を渡す3つの方法
以下は各テンプレートの全文です。
よければルートスタックのS3URLを変更すれば他AWSアカウントでも実行可能ため、自身のAWSアカウントで実行してみてください。
root.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: ParentStack Main
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
stackSystem:
Type: String
Description: Specify the sub system name abbreviation.
MinLength: 4
MaxLength: 8
stackEnv:
Type: String
Description: Specify the system environment.
Default: dev
AllowedValues:
- dev
- prod
stackVpcCidr:
Type: String
Description: Specify the CIDR to assign to the VPC.
Default: 10.0.0.0/16
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet01Cidr:
Type: String
Description: Specify the CIDR to assign to the first public subnet.
Default: 10.0.0.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet02Cidr:
Type: String
Description: Specify the CIDR to assign to the second public subnet.
Default: 10.0.1.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPrivateSubnet01Cidr:
Type: String
Description: Specify the CIDR to assign to the first private subnet.
Default: 10.0.2.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPrivateSubnet02Cidr:
Type: String
Description: Specify the CIDR to assign to the second private subnet.
Default: 10.0.3.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet01AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1a
stackPublicSubnet02AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1d
stackPrivateSubnet01AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1a
stackPrivateSubnet02AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1d
stackEC2InstanceType:
Type: String
Description: EC2 instance type for the private subnet instance
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
ConstraintDescription: Must be a valid EC2 instance type
stackEC2AMI:
Type: AWS::EC2::Image::Id
Description: Amazon Linux 2 AMI ID
Default: ami-0d3bbfd074edd7acb # 東京リージョンの最新Amazon Linux 2 AMI ID (適宜更新が必要)
## ::METADATA::
## CloudFormation parameter UI definitions
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: Sub System & Environment Configuration
Parameters:
- stackSystem
- stackEnv
- Label:
default: VPC Configuration
Parameters:
- stackVpcCidr
- Label:
default: Subnet Configuration
Parameters:
- stackPublicSubnet01Cidr
- stackPublicSubnet02Cidr
- stackPrivateSubnet01Cidr
- stackPrivateSubnet02Cidr
- stackPublicSubnet01AZ
- stackPublicSubnet02AZ
- stackPrivateSubnet01AZ
- stackPrivateSubnet02AZ
- Label:
default: EC2 Configuration
Parameters:
- stackEC2InstanceType
- stackEC2AMI
ParameterLabels:
stackSystem:
default: Sub System Name
stackVpcCidr:
default: VPC CIDR
stackPublicSubnet01Cidr:
default: Public Subnet 01 CIDR
stackPublicSubnet02Cidr:
default: Public Subnet 02 CIDR
stackPrivateSubnet01Cidr:
default: Private Subnet 01 CIDR
stackPrivateSubnet02Cidr:
default: Private Subnet 02 CIDR
stackPublicSubnet01AZ:
default: Public Subnet 01 AZ-Name
stackPublicSubnet02AZ:
default: Public Subnet 02 AZ-Name
stackPrivateSubnet01AZ:
default: Private Subnet 01 AZ-Name
stackPrivateSubnet02AZ:
default: Private Subnet 02 AZ-Name
stackEC2InstanceType:
default: EC2 Instance Type
stackEC2AMI:
default: EC2 AMI ID
## ::RESOURCES::
## Resources used in this solution
Resources:
VpcStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/vpc.yaml
Parameters:
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackVpcCidr: !Ref stackVpcCidr
SubnetStack:
Type: AWS::CloudFormation::Stack
DependsOn:
- VpcStack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/subnet.yaml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackTypeOfSubnet: !Ref stackTypeOfSubnet
stackPublicSubnet01Cidr: !Ref stackPublicSubnet01Cidr
stackPublicSubnet02Cidr: !Ref stackPublicSubnet02Cidr
stackPrivateSubnet01Cidr: !Ref stackPrivateSubnet01Cidr
stackPrivateSubnet02Cidr: !Ref stackPrivateSubnet02Cidr
stackPublicSubnet01AZ: !Ref stackPublicSubnet01AZ
stackPublicSubnet02AZ: !Ref stackPublicSubnet02AZ
stackPrivateSubnet01AZ: !Ref stackPrivateSubnet01AZ
stackPrivateSubnet02AZ: !Ref stackPrivateSubnet02AZ
EC2Stack:
Type: AWS::CloudFormation::Stack
DependsOn:
- VpcStack
- SubnetStack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/ec2.yaml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
VpcCidrBlock: !GetAtt VpcStack.Outputs.VpcCidrBlock
SubnetPrivate01Id: !GetAtt SubnetStack.Outputs.SubnetIdPrivate01
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackEC2InstanceType: !Ref stackEC2InstanceType
stackEC2AMI: !Ref stackEC2AMI
vpc.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: ParentStack Main
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
stackSystem:
Type: String
Description: Specify the sub system name abbreviation.
MinLength: 4
MaxLength: 8
stackEnv:
Type: String
Description: Specify the system environment.
Default: dev
AllowedValues:
- dev
- prod
stackVpcCidr:
Type: String
Description: Specify the CIDR to assign to the VPC.
Default: 10.0.0.0/16
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackTypeOfSubnet:
Type: String
Description: Specify type of Subnets.
Default: Both
AllowedValues:
- Public
- Private
- Both
stackPublicSubnet01Cidr:
Type: String
Description: Specify the CIDR to assign to the first public subnet.
Default: 10.0.0.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet02Cidr:
Type: String
Description: Specify the CIDR to assign to the second public subnet.
Default: 10.0.1.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPrivateSubnet01Cidr:
Type: String
Description: Specify the CIDR to assign to the first private subnet.
Default: 10.0.2.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPrivateSubnet02Cidr:
Type: String
Description: Specify the CIDR to assign to the second private subnet.
Default: 10.0.3.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet01AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1a
stackPublicSubnet02AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1d
stackPrivateSubnet01AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1a
stackPrivateSubnet02AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1d
stackEC2InstanceType:
Type: String
Description: EC2 instance type for the private subnet instance
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
ConstraintDescription: Must be a valid EC2 instance type
stackEC2AMI:
Type: AWS::EC2::Image::Id
Description: Amazon Linux 2 AMI ID
Default: ami-0d3bbfd074edd7acb # 東京リージョンの最新Amazon Linux 2 AMI ID (適宜更新が必要)
## ::METADATA::
## CloudFormation parameter UI definitions
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: Sub System & Environment Configuration
Parameters:
- stackSystem
- stackEnv
- Label:
default: VPC Configuration
Parameters:
- stackVpcCidr
- Label:
default: Subnet Configuration
Parameters:
- stackTypeOfSubnet
- stackPublicSubnet01Cidr
- stackPublicSubnet02Cidr
- stackPrivateSubnet01Cidr
- stackPrivateSubnet02Cidr
- stackPublicSubnet01AZ
- stackPublicSubnet02AZ
- stackPrivateSubnet01AZ
- stackPrivateSubnet02AZ
- Label:
default: EC2 Configuration
Parameters:
- stackEC2InstanceType
- stackEC2AMI
ParameterLabels:
stackSystem:
default: Sub System Name
stackVpcCidr:
default: VPC CIDR
stackTypeOfSubnet:
default: Type Of Subnet
stackPublicSubnet01Cidr:
default: Public Subnet 01 CIDR
stackPublicSubnet02Cidr:
default: Public Subnet 02 CIDR
stackPrivateSubnet01Cidr:
default: Private Subnet 01 CIDR
stackPrivateSubnet02Cidr:
default: Private Subnet 02 CIDR
stackPublicSubnet01AZ:
default: Public Subnet 01 AZ-Name
stackPublicSubnet02AZ:
default: Public Subnet 02 AZ-Name
stackPrivateSubnet01AZ:
default: Private Subnet 01 AZ-Name
stackPrivateSubnet02AZ:
default: Private Subnet 02 AZ-Name
stackEC2InstanceType:
default: EC2 Instance Type
stackEC2AMI:
default: EC2 AMI ID
## ::RESOURCES::
## Resources used in this solution
Resources:
VpcStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/vpc.yaml
Parameters:
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackVpcCidr: !Ref stackVpcCidr
SubnetStack:
Type: AWS::CloudFormation::Stack
DependsOn:
- VpcStack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/subnet.yaml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackTypeOfSubnet: !Ref stackTypeOfSubnet
stackPublicSubnet01Cidr: !Ref stackPublicSubnet01Cidr
stackPublicSubnet02Cidr: !Ref stackPublicSubnet02Cidr
stackPrivateSubnet01Cidr: !Ref stackPrivateSubnet01Cidr
stackPrivateSubnet02Cidr: !Ref stackPrivateSubnet02Cidr
stackPublicSubnet01AZ: !Ref stackPublicSubnet01AZ
stackPublicSubnet02AZ: !Ref stackPublicSubnet02AZ
stackPrivateSubnet01AZ: !Ref stackPrivateSubnet01AZ
stackPrivateSubnet02AZ: !Ref stackPrivateSubnet02AZ
EC2Stack:
Type: AWS::CloudFormation::Stack
DependsOn:
- VpcStack
- SubnetStack
Properties:
TemplateURL: https://test-kamibayashi-nest01.s3.ap-northeast-1.amazonaws.com/ec2.yaml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
VpcCidrBlock: !GetAtt VpcStack.Outputs.VpcCidrBlock
SubnetPrivate01Id: !GetAtt SubnetStack.Outputs.SubnetIdPrivate01
stackSystem: !Ref stackSystem
stackEnv: !Ref stackEnv
stackEC2InstanceType: !Ref stackEC2InstanceType
stackEC2AMI: !Ref stackEC2AMI
subnet.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: ParentStack Main
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: Specify the VpcId.
stackSystem:
Type: String
Description: Specify the sub system name abbreviation.
MinLength: 4
MaxLength: 8
stackEnv:
Type: String
Description: Specify the system environment.
Default: dev
AllowedValues:
- dev
- prod
stackPublicSubnet01Cidr:
Type: String
Description: Specify the CIDR to assign to the first public subnet.
Default: 10.0.0.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet02Cidr:
Type: String
Description: Specify the CIDR to assign to the second public subnet.
Default: 10.0.1.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPrivateSubnet01Cidr:
Type: String
Description: Specify the CIDR to assign to the first private subnet.
Default: 10.0.2.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPrivateSubnet02Cidr:
Type: String
Description: Specify the CIDR to assign to the second private subnet.
Default: 10.0.3.0/24
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
stackPublicSubnet01AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1a
stackPublicSubnet02AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1d
stackPrivateSubnet01AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1a
stackPrivateSubnet02AZ:
Type: String
Description: Specify the AZ-Name to assign to the first public subnet.
Default: ap-northeast-1d
## ::RESOURCES::
## Resources used in this solution
Resources:
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-igw01"
VpcGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VpcId
InternetGatewayId: !Ref InternetGateway
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref stackPublicSubnet01AZ
CidrBlock: !Ref stackPublicSubnet01Cidr
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-public01"
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref stackPublicSubnet02AZ
CidrBlock: !Ref stackPublicSubnet02Cidr
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-public02"
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref stackPrivateSubnet01AZ
CidrBlock: !Ref stackPrivateSubnet01Cidr
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-private01"
PrivateSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref stackPrivateSubnet02AZ
CidrBlock: !Ref stackPrivateSubnet02Cidr
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-private02"
ElasticIpPublic01:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-eip-ngw01"
ElasticIpPublic02:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-eip-ngw02"
NatGatewayPublic01:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt ElasticIpPublic01.AllocationId
SubnetId: !Ref PublicSubnet01
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-private01-ngw01"
NatGatewayPublic02:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt ElasticIpPublic02.AllocationId
SubnetId: !Ref PublicSubnet02
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-private02-ngw01"
RouteTablePublic01:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-Public01-rtb01"
RouteTablePublic02:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-Public02-rtb01"
RouteTablePrivate01:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-private01-rtb01"
RouteTablePrivate02:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-sbn-private02-rtb01"
RouteTableAssociationPublic01:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePublic01
SubnetId: !Ref PublicSubnet01
RouteTableAssociationPublic02:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePublic02
SubnetId: !Ref PublicSubnet02
RouteTableAssociationPrivate01:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePrivate01
SubnetId: !Ref PrivateSubnet01
RouteTableAssociationPrivate02:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePrivate02
SubnetId: !Ref PrivateSubnet02
RoutePublic01:
Type: AWS::EC2::Route
DependsOn: VpcGatewayAttachment
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref RouteTablePublic01
RoutePublic02:
Type: AWS::EC2::Route
DependsOn: VpcGatewayAttachment
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref RouteTablePublic02
NATRoutePrivate01:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGatewayPublic01
RouteTableId: !Ref RouteTablePrivate01
NATRoutePrivate02:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGatewayPublic02
RouteTableId: !Ref RouteTablePrivate02
VpcEndpointS301:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref RouteTablePrivate01
- !Ref RouteTablePrivate02
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcEndpointType: Gateway
VpcId: !Ref VpcId
## ::OUTPUTS::
## Outputs useful in other templates
Outputs:
InternetGatewayId:
Description: Internet Gateway Id
Value: !Ref InternetGateway
VpcGatewayAttachmentId:
Description: VPC Gateway Attachment Id
Value: !Ref VpcGatewayAttachment
SubnetIdPublic01:
Description: Subnet Id
Value: !Ref PublicSubnet01
SubnetIdPublic02:
Description: Subnet Id
Value: !Ref PublicSubnet02
SubnetIdPrivate01:
Description: Subnet Id
Value: !Ref PrivateSubnet01
SubnetIdPrivate02:
Description: Subnet Id
Value: !Ref PrivateSubnet02
ElasticIp01:
Description: Elastic Ip
Value: !Ref ElasticIpPublic01
ElasticIp02:
Description: Elastic Ip
Value: !Ref ElasticIpPublic02
NatGatewayIdPublic01:
Description: Nat Gateway Id
Value: !Ref NatGatewayPublic01
NatGatewayIdPublic02:
Description: Nat Gateway Id
Value: !Ref NatGatewayPublic02
RouteTableIdPublic01:
Description: Route Table Id
Value: !Ref RouteTablePublic01
RouteTableIdPublic02:
Description: Route Table Id
Value: !Ref RouteTablePublic02
RouteTableIdPrivate01:
Description: Route Table Id
Value: !Ref RouteTablePrivate01
RouteTableIdPrivate02:
Description: Route Table Id
Value: !Ref RouteTablePrivate02
VpcEndpointIdS301:
Description: VPC Endpoint Id
Value: !Ref VpcEndpointS301
ec2.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Create network-related resources.
## ::PARAMETERS::
## Template parameters to be configured by user
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: Specify the VpcId.
VpcCidrBlock:
Type: String
Description: Specify the VpcCidrBlock.
SubnetPrivate01Id:
Type: AWS::EC2::Subnet::Id
Description: Specify the SubnetPrivate01Id.
stackSystem:
Type: String
Description: Specify the sub system name abbreviation.
MinLength: 4
MaxLength: 8
stackEnv:
Type: String
Description: Specify the system environment.
Default: dev
AllowedValues:
- dev
- prod
stackTypeOfSubnet:
Type: String
Description: Specify type of Subnets.
Default: Both
AllowedValues:
- Public
- Private
- Both
stackEC2InstanceType:
Type: String
Description: EC2 instance type for the private subnet instance
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
ConstraintDescription: Must be a valid EC2 instance type
stackEC2AMI:
Type: AWS::EC2::Image::Id
Description: Amazon Linux 2 AMI ID
Default: ami-0d3bbfd074edd7acb # 東京リージョンの最新Amazon Linux 2 AMI ID (適宜更新が必要)
## ::CONDITIONS::
## Determines if we're generating regional or global resources
Conditions:
IsCreatePublicSubnets:
!Or [ !Equals [!Ref stackTypeOfSubnet, "Public" ], !Equals [!Ref stackTypeOfSubnet, "Both" ] ]
IsCreatePrivateSubnets:
!Or [ !Equals [!Ref stackTypeOfSubnet, "Private" ], !Equals [!Ref stackTypeOfSubnet, "Both" ] ]
IsCreateBothSubnets:
!Equals [ !Ref stackTypeOfSubnet, "Both" ]
## ::RESOURCES::
## Resources used in this solution
Resources:
PrivateEC2SecurityGroup:
Condition: IsCreatePrivateSubnets
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for private EC2 instance
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref VpcCidrBlock
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-private-ec2-sg01"
PrivateEC2Instance:
Condition: IsCreatePrivateSubnets
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref stackEC2InstanceType
ImageId: !Ref stackEC2AMI
SubnetId: !Ref SubnetPrivate01Id
SecurityGroupIds:
- !Ref PrivateEC2SecurityGroup
Tags:
- Key: Name
Value: !Sub "${stackSystem}-${stackEnv}-private-ec201"
## ::OUTPUTS::
## Outputs useful in other templates
Outputs:
PrivateEC2InstanceId:
Condition: IsCreatePrivateSubnets
Description: Private EC2 Instance Id
Value: !Ref PrivateEC2Instance
実行してみる
スタックを実行する際はroot.yamlをテンプレートとして実行します。
すると1つのスタックから複数のスタックが作成されます。
実際に実行した結果が以下です。
子スタックに関しては”ネストされた”と表示されています。
ネストされたスタックのメリット・デメリット
ネストされたスタックのメリット・デメリットについて考えてみました。
メリット
- 1回のスタック実行で多数のリソースを作成できる
- テンプレートを分割することでコードの可読性が上がる
- スタック間の依存関係の可視性が高い
- Outputsセクションで指定した値が他のスタックでも参照できる(Exportが必要ない)
デメリット
- スタック間の依存関係を明確にする必要がある(DependsOnセクションが重要)
- ネストの階層が深くなるとルートスタックからの追跡が難しい
- 子のスタックの数が増えるとその分可視性は低くなり、依存関係も複雑になる
- スタック間で共用のParametersセクションの値が少ないと、ルートスタックのParametersセクションで設定する値が増える
注意
ネストの階層について、今回の検証では子スタックまでとしましたが、実際には子スタックのさらに下の階層(孫・玄孫)のスタックも作成することは可能です。
しかし下記の通り複雑性が増すので個人的にはおすすめしません。
ネストされたスタックのおすすめ運用方法
今回の検証を踏まえた個人的なおすすめ運用方法をご紹介します。
- 追跡の容易さからネストは1回まで(子スタックまで)の方がよい
- 作成リソースを一覧化するため、ルートスタックのOutputsセクションで各子スタックのOutputsセクションの値を出力する(子スタックのOutputs→ルートスタックのOutputsで値の受け渡しを行う)
- 変更等スタックの更新はルートスタックから行う
- 同じParametersセクションの値を複数のリソースで共用したいときに利用する
総評
ネストされたスタックは検証等で一時的な環境を用意したい場合にはとても便利だと思いました。
案件で利用する際には、小規模ならまだしも中~大規模案件では懸念点が複数あると思います。
1つ目はスタック分割の設計要素が増えることです。案件では作成するリソースが増えるため、すべてのリソースをネストされたスタックで作成することができなくなります。その場合ネストされたスタックを利用すると、ネストを行うリソース群、ネストを行わないリソース群、ネストを行う単位といった設計要素が増えると考えます。
2つ目は拡張性・柔軟性が減ることです。ネストされたスタックではできる限り共通のParametersセクションの値を利用して各子スタックを作成した方が利点を生かせます。逆に言えば後から共通でないParametersセクションの値を追加したい場合、ルートスタックの記述が増え複雑性が増し、結果ネストではなく完全に分割したいといった結論になるかもしれません。
まとめ
ネストされたスタックは一括してリソースを作成したいときにとても便利です。
個人の検証環境等、一括で環境を用意したいときにぜひ利用してみてください!