5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

みなさんCloudFormationは利用しておりますでしょうか?
AWSの代表的なIaCのため、使ったことがあるもしくは聞いたことがある人は多いと思います。

私も業務で利用しているのですが、基本的に複数のリソースを作成したい場合には
1つのテンプレートにすべてのリソースを記述、もしくはリソースごとにテンプレートを分割して複数回テンプレートを実行しています。

複数のリソースを一括で作成したい場合に便利な ”ネストされたスタック” という使い方があることを知ったので、実際に動かしながら運用について考えてみたいと思います。

この記事の対象

今回はCloudFormationについての基礎知識(パラメータの意味がわかる)がある方が対象です。
また検証用に利用したテンプレートは本記事で公開しますが、テンプレート内の記述についてはネストと関係がある箇所のみ説明を行います。

ネストされたスタックとは

CloudFormationのテンプレート構成の技法のひとつで、1つのスタック内に複数のテンプレートを入れ子状にすることができます。
1つのルートスタックと複数の子スタックからなり、1つのテンプレートを分割して階層的にスタックを作成する形になります。
最上位層となるルートスタックでは ”AWS::CloudFormation::Stack” を利用して子スタックのテンプレートを指定します。

ルート_子スタックの関係性.png

やってみる

検証内容

さっそく試してみたいと思います。
今回構築するリソースは以下です。

  • 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

そして、スタックごとにテンプレートを作成します。

構成図に表すと以下のようになります。

構築物

構成図.png

スタックの分割

構成図_2.png

テンプレートの分割

stackとyamlの関係性.png

テンプレート

今回用意したテンプレートをピックアップして紹介します。
※全文は後述

root.yaml

大本となるルートスタックから作成していきます。
まず最初のParametersセクションでは、子のスタックに継承するパラメータを入力可能にします。
例)EC2Stackで利用するためのインスタンスタイプを指定させる。

root.yaml
## 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 にすることでテンプレートを指定することが可能です。

root.yaml
## 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が必要ないため)

vpc.yaml
## ルートスタックからパラメータを継承

## ::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
## 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)を指定します。

root.yaml
## ::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セクションにも継承した値を設定します。

subnet.yaml
## 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
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
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
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
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つのスタックから複数のスタックが作成されます。
実際に実行した結果が以下です。
子スタックに関しては”ネストされた”と表示されています。

実行結果.png

ネストされたスタックのメリット・デメリット

ネストされたスタックのメリット・デメリットについて考えてみました。

メリット

  • 1回のスタック実行で多数のリソースを作成できる
  • テンプレートを分割することでコードの可読性が上がる
  • スタック間の依存関係の可視性が高い
  • Outputsセクションで指定した値が他のスタックでも参照できる(Exportが必要ない)

デメリット

  • スタック間の依存関係を明確にする必要がある(DependsOnセクションが重要)
  • ネストの階層が深くなるとルートスタックからの追跡が難しい
  • 子のスタックの数が増えるとその分可視性は低くなり、依存関係も複雑になる
  • スタック間で共用のParametersセクションの値が少ないと、ルートスタックのParametersセクションで設定する値が増える

注意
ネストの階層について、今回の検証では子スタックまでとしましたが、実際には子スタックのさらに下の階層(孫・玄孫)のスタックも作成することは可能です。
しかし下記の通り複雑性が増すので個人的にはおすすめしません。

ネストされたスタックを使用してテンプレートを再利用可能な部分に分割する
ネストされたスタックの複雑性.png

ネストされたスタックのおすすめ運用方法

今回の検証を踏まえた個人的なおすすめ運用方法をご紹介します。

  • 追跡の容易さからネストは1回まで(子スタックまで)の方がよい
  • 作成リソースを一覧化するため、ルートスタックのOutputsセクションで各子スタックのOutputsセクションの値を出力する(子スタックのOutputs→ルートスタックのOutputsで値の受け渡しを行う)
  • 変更等スタックの更新はルートスタックから行う
  • 同じParametersセクションの値を複数のリソースで共用したいときに利用する

総評

ネストされたスタックは検証等で一時的な環境を用意したい場合にはとても便利だと思いました。
案件で利用する際には、小規模ならまだしも中~大規模案件では懸念点が複数あると思います。
1つ目はスタック分割の設計要素が増えることです。案件では作成するリソースが増えるため、すべてのリソースをネストされたスタックで作成することができなくなります。その場合ネストされたスタックを利用すると、ネストを行うリソース群、ネストを行わないリソース群、ネストを行う単位といった設計要素が増えると考えます。
2つ目は拡張性・柔軟性が減ることです。ネストされたスタックではできる限り共通のParametersセクションの値を利用して各子スタックを作成した方が利点を生かせます。逆に言えば後から共通でないParametersセクションの値を追加したい場合、ルートスタックの記述が増え複雑性が増し、結果ネストではなく完全に分割したいといった結論になるかもしれません。

まとめ

ネストされたスタックは一括してリソースを作成したいときにとても便利です。
個人の検証環境等、一括で環境を用意したいときにぜひ利用してみてください!

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?