CloudFormationを使用してネストされたスタックを作成し、JSONファイルでパラメータを設定してAWS CLIで実行する方法

記事タイトルとURLをコピーする

こんにちは!エンタープライズクラウド部技術2課の日高です。

先日、私の1年後輩がインタビュー記事を書いてくれましたので、私についてもっと知りたいと思ってくださる方は、ぜひブログをご覧ください!

sabawaku.serverworks.co.jp

今回は、CloudFormationを利用してネストされたスタックの作成方法と、JSONファイルを用いたパラメータの設定、さらにAWS CLIでの実行手順について詳しく解説していきます。

イメージが湧きづらい方は、「はじめに」をご覧ください。
題名等で今回やることが理解できた方は、「はじめに」は読み飛ばしていただいて問題ありません。

はじめに

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

AWS CloudFormationを使用すると、インフラストラクチャーをコードとして管理することができます。
この中で「ネストされたスタック」という概念があります。

ネストされたスタックとは、スタックを分割して階層化する特性があります。

上記の図のようにネストされたスタックは、1つの「メインスタック(親スタック)」と1つ以上の「子スタック」から構成されます。

  • メインスタック(親スタックやルートスタックとも呼ばれます。): ネストされたスタックの親となるスタックです。このスタックから他の子スタックを参照します。
  • 子スタック: メインスタックによって管理されるスタックです。これらは個別にリソースを持ち、個々のスタックとしてデプロイされます。

こちらを図としてまとめると、下記のようなイメージになります。

上記の図の場合、通常「vpc.yml」「ec2-sg.yml」「ec2.yml」を個別に実行する必要があります。
しかし、ネストされたスタックのメインスタックを実行すると、メインスタックが子スタックである「vpc.yml」「ec2-sg.yml」「ec2.yml」を呼び出し実行してくれます。

実際のマネジメントコンソールでネストされたスタックを実行した画面は下記のようになります。
赤枠で囲まれているのがメインスタックです。青枠で囲まれているのが、メインスタックから呼び出された子スタックになります。

上記の図からも分かるように、メインスタックから呼び出された子スタックには「ネストされた」という表記がされています。

本ブログで記載すること

このブログでは、「AWS CLIと事前に設定されたパラメータを含むJsonファイルを使用して、ユーザーがメインスタック(親スタック)をどのように実行するか」について詳しく解説します。

この図に示されている「①ユーザーがメインスタック(親スタック)を実行する」方法には、下記の2つの方法があります。

  • マネジメントコンソールを使用する方法
  • AWS CLIを使用する方法

マネジメントコンソールを通じて実行する場合、以下のような画面でパラメータを指定する必要があります。

マネジメントコンソール画面

ただ、今回このブログで解説するのはAWS CLIで実行する方法になります。 具体的には、Jsonファイルにパラメータを事前に設定し、それを読み込んでネストされたスタックを実行する方法です。

このプロセスの概念図は以下の通りです。

Jsonファイルをあらかじめ設定することで得られるメリットは以下の通りです。

  • マネジメントコンソールで入力する手間が省ける。
  • 人間による入力ミスを防ぐことができる(Jsonファイルの設定値が正しい場合)。
  • AWS CLIコマンドの実行だけで、迅速に同じ環境を構築できる。

準備(CloudFormationテンプレートとJsonファイル)

下記に記載されているメインスタック(root.yml)、各子スタック(vpc.yml、ec2-sg.yml、ec2.yml)、Jsonファイル(ParameterFile.json)を同一フォルダ内に保存してください。

各子スタックは、独立して実行しやすいように、幅広い用途に適応できるよう汎用的に、また、他のスタックとの密接な依存関係を避けるために疎結合で設計されています。

今回作る構成

上記の構成図の構成を作成します。 EC2にはSSMのセッションマネージャーを利用してログインする想定なので、EC2のセキュリティグループにはインバウンドルールは追加しません。

テンプレート格納用のS3バケットの作成

CloudFormationテンプレートを格納するためのS3バケットを作成してください。
私は「hidaka-cloudformation-template」で作成しました。

詳しくは後述しますが、AWS CLIを実行すると下記の動作をします。

  1. 子スタックを指定したS3バケットに格納する
  2. root.ymlに記載しているPropertiesTemplateURLの値を、格納したS3バケットのオブジェクトURIに書き換える

そのため、前もってS3の準備が必要になります。

メインスタック(root.yml)

AWSTemplateFormatVersion: "2010-09-09"
Description: ParentStack  Main

Parameters:
  #vpc.ymlのパラメータ指定
  VpcCidr:
    Type: String
    Description: CIDR block for the VPC
  PublicSubnetACidr:
    Type: String
    Description: CIDR block for the public subnet in AZ A
  PublicSubnetCCidr:
    Type: String
    Description: CIDR block for the public subnet in AZ C
  ProtectedSubnetACidr:
    Type: String
    Description: CIDR block for the protected subnet in AZ A
  ProtectedSubnetCCidr:
    Type: String
    Description: CIDR block for the protected subnet in AZ C
  PrivateSubnetACidr:
    Type: String
    Description: CIDR block for the private subnet in AZ A
  PrivateSubnetCCidr:
    Type: String
    Description: CIDR block for the private subnet in AZ C
  FlowLogsBucketName:
    Type: String
    Description: <System Name>-flow-logs-<Account Number>
  VpcTagName:
    Type: String
    Description: <System Name>-<Environment>
  VpcApplicationTag:
    Type: String
    Description: Application tag for all resources <System Name>

  #下記よりec2-sg.ymlパラメータを指定。(インバウンドルールを追加する場合はこことec2-sg.ymlのコメントアウト解除し、InfraParameterFile.jsonに各値を追記してください。)
  Ec2SecurityGroupName:
    Description: "Enter the name for the Security Group."
    Type: String
  Ec2SecurityGroupDescription:
    Description: "Enter a description for the Security Group."
    Type: String
  Ec2SecurityGroupTagValue:
    Description: "Enter a tag value for the Security Group."
    Type: String
  #Ec2IngressCidrIp:
    #Description: "CIDR block for inbound rule."
    #Type: String
    #Default: "0.0.0.0/0" # この値は適切なものに変更してください
  #Ec2IngressFromPort:
    #Description: "Start range of the TCP port for inbound rule."
    #Type: Number
    #Default: 80 # この値は適切なものに変更してください
  #Ec2IngressToPort:
    #Description: "End range of the TCP port for inbound rule."
    #Type: Number
    #Default: 80 # この値は適切なものに変更してください
  #Ec2IngressProtocol:
    #Description: "Protocol for inbound rule."
    #Type: String
    #Default: "tcp" # "tcp"、"udp"、"icmp"、または"-1" (すべてのプロトコルを指定)

  # 下記よりec2.ymlのパラメータを指定。(EBS複数作成する場合はこことec2.ymlのコメントアウト解除し、InfraParameterFile.jsonにEc2DiskSize2の値を追記してください。)
  ImageId :
    Type : "AWS::EC2::Image::Id"
    Description: Enter Amazon Machine Image ID for Windows.
  Ec2InstanceTypeParameter:
    Type: String
    Default: t3.micro
  Ec2IamInstanceProfileName:
    Type: String
  Ec2InstanceName:
    Type: String
    Description: (Required) MaxLength 15.Enter EC2 Instance Name. #Windows の制約上、ホスト名は16文字未満となるため。
    MaxLength: 15
    MinLength: 1
  Ec2DiskSize1:
    Type: String
    Description: (Required) Enter the disk size.
    Default: "30"
  #Ec2DiskSize2:
  #  Type: String
  #  Description: (Required) Enter the disk size.
  #  Default: "8"
  Ec2KeyName:
    Type: String
    Description: (Required) Enter the Ec2KeyName.
    Default: "XXXXX"
  Ec2ApplicationTag:
    Type: String
    Description: Application tag for all resources <System Name>


###############################################################################################
#######  ここから下Resourcesセクション   #########################################################
###############################################################################################
Resources:
  #vpc.ymlのスタック実行
  VpcStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./vpc.yml
      Parameters:
        VpcCidr: !Ref VpcCidr
        PublicSubnetACidr: !Ref PublicSubnetACidr
        PublicSubnetCCidr: !Ref PublicSubnetCCidr
        ProtectedSubnetACidr: !Ref ProtectedSubnetACidr
        ProtectedSubnetCCidr: !Ref ProtectedSubnetCCidr
        PrivateSubnetACidr: !Ref PrivateSubnetACidr
        PrivateSubnetCCidr: !Ref PrivateSubnetCCidr
        FlowLogsBucketName: !Ref FlowLogsBucketName
        VpcTagName: !Ref VpcTagName
        VpcApplicationTag: !Ref VpcApplicationTag

  #下記よりec2-sg.ymlのスタック実行(インバウンドルールを追加する場合はこことec2-sg.ymlのコメントアウト解除し、InfraParameterFile.jsonに値を追記してください。)
  Ec2SgStack:
    Type: AWS::CloudFormation::Stack
    DependsOn:
      - VpcStack
    Properties:
      TemplateURL: ./ec2-sg.yml
      Parameters:
        VpcId: !GetAtt VpcStack.Outputs.VpcId
        Ec2SecurityGroupName: !Ref Ec2SecurityGroupName
        Ec2SecurityGroupDescription: !Ref Ec2SecurityGroupDescription
        Ec2SecurityGroupTagValue: !Ref Ec2SecurityGroupTagValue
        #Ec2IngressCidrIp: !Ref Ec2IngressCidrIp
        #Ec2IngressFromPort: !Ref Ec2IngressFromPort
        #Ec2IngressToPort: !Ref Ec2IngressToPort
        #Ec2IngressProtocol: !Ref Ec2IngressProtocol

  #下記よりec2.ymlのスタック実行
  Ec2Stack:
    Type: AWS::CloudFormation::Stack
    DependsOn:
      - VpcStack
      - Ec2SgStack
    Properties:
      TemplateURL: ./ec2.yml
      Parameters:
        ImageId : !Ref ImageId
        Ec2InstanceTypeParameter: !Ref Ec2InstanceTypeParameter
        Ec2IamInstanceProfileName: !Ref Ec2IamInstanceProfileName
        Ec2InstanceName: !Ref Ec2InstanceName
        Ec2DiskSize1: !Ref Ec2DiskSize1
        #Ec2DiskSize2: !Ref Ec2DiskSize2
        Ec2SubnetIdForEth0: !GetAtt VpcStack.Outputs.SubnetProtectedAId
        Ec2SecurityGroupIdsForEth0: !GetAtt Ec2SgStack.Outputs.SecurityGroupId
        Ec2KeyName: !Ref Ec2KeyName
        Ec2ApplicationTag: !Ref Ec2ApplicationTag

子スタック①(vpc.yml)

AWSTemplateFormatVersion: '2010-09-09'
Description: NestedStack For root.yml

Parameters:
  VpcCidr:
    Type: String
    Description: CIDR block for the VPC
  PublicSubnetACidr:
    Type: String
    Description: CIDR block for the public subnet in AZ A
  PublicSubnetCCidr:
    Type: String
    Description: CIDR block for the public subnet in AZ C
  ProtectedSubnetACidr:
    Type: String
    Description: CIDR block for the protected subnet in AZ A
  ProtectedSubnetCCidr:
    Type: String
    Description: CIDR block for the protected subnet in AZ C
  PrivateSubnetACidr:
    Type: String
    Description: CIDR block for the private subnet in AZ A
  PrivateSubnetCCidr:
    Type: String
    Description: CIDR block for the private subnet in AZ C
  FlowLogsBucketName:
    Type: String
    Description: <System Name>-flow-logs-<Account Number>
  VpcTagName:
    Type: String
    Description: <System Name>-<Environment>
  VpcApplicationTag:
    Type: String
    Description: Application tag for all resources <System Name>

Resources:
  # ここからVPCとVPCフローログの定義。
  Vpc:
    Type: 'AWS::EC2::VPC'
    DeletionPolicy: Delete
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsHostnames: 'true'
      EnableDnsSupport: 'true'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-vpc'
        - Key: Application
          Value: !Ref VpcApplicationTag
  S3BucketForFlowLogs:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Delete
    Properties:
      BucketName: !Ref FlowLogsBucketName
      AccessControl: Private
      Tags:
        - Key: Application
          Value: !Ref VpcApplicationTag
  VPCFlowLog:
    Type: 'AWS::EC2::FlowLog'
    DeletionPolicy: Delete
    Properties:
      LogDestinationType: s3
      LogDestination: !Sub 'arn:aws:s3:::${S3BucketForFlowLogs}'
      ResourceId: !Ref Vpc
      ResourceType: VPC
      TrafficType: ALL
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-log'
        - Key: Application
          Value: !Ref VpcApplicationTag
  # ここからサブネットの定義。各サブネットはパラメータ化されたCIDRブロックを使用
  SubnetPublicA:
    Type: 'AWS::EC2::Subnet'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnetACidr
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-public-1a'
        - Key: Application
          Value: !Ref VpcApplicationTag
  SubnetPublicC:
    Type: 'AWS::EC2::Subnet'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnetCCidr
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-public-1c'
        - Key: Application
          Value: !Ref VpcApplicationTag
  SubnetProtectedA:
    Type: 'AWS::EC2::Subnet'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref ProtectedSubnetACidr
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-protected-1a'
        - Key: Application
          Value: !Ref VpcApplicationTag
  SubnetProtectedC:
    Type: 'AWS::EC2::Subnet'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref ProtectedSubnetCCidr
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-protected-1c'
        - Key: Application
          Value: !Ref VpcApplicationTag
  SubnetPrivateA:
    Type: 'AWS::EC2::Subnet'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnetACidr
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-private-1a'
        - Key: Application
          Value: !Ref VpcApplicationTag
  SubnetPrivateC:
    Type: 'AWS::EC2::Subnet'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnetCCidr
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-private-1c'
        - Key: Application
          Value: !Ref VpcApplicationTag

  # ここからIGWとNGWの定義
  Igw:
    Type: 'AWS::EC2::InternetGateway'
    DeletionPolicy: Delete
    Properties:
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-igw'
        - Key: Application
          Value: !Ref VpcApplicationTag
  IgwAttach:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref Igw

  NgwA:
    Type: 'AWS::EC2::NatGateway'
    DeletionPolicy: Delete
    Properties:
      AllocationId: !GetAtt EipNgwA.AllocationId
      SubnetId: !Ref SubnetPublicA
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-ngw-a'
        - Key: Application
          Value: !Ref VpcApplicationTag
  EipNgwA:
    Type: 'AWS::EC2::EIP'
    DeletionPolicy: Delete
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-ngw-a-eip'
        - Key: Application
          Value: !Ref VpcApplicationTag

# 2つNGWを作る場合は下記のコメントアウトを解除。
#  NgwC:
#    Type: 'AWS::EC2::NatGateway'
#    DeletionPolicy: Delete
#    Properties:
#      AllocationId: !GetAtt EipNgwC.AllocationId
#      SubnetId: !Ref SubnetPublicC
#      Tags:
#        - Key: Name
#          Value: !Sub '${VpcTagName}-ngw-c'
#        - Key: Application
#          Value: !Ref VpcApplicationTag
#  EipNgwC:
#    Type: 'AWS::EC2::EIP'
#    DeletionPolicy: Delete
#    Properties:
#      Domain: vpc
#      Tags:
#        - Key: Name
#          Value: !Sub '${VpcTagName}-ngw-c-eip'
#        - Key: Application
#          Value: !Ref VpcApplicationTag

  #ここからRtbの定義
  RtbPublic:
    Type: 'AWS::EC2::RouteTable'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-public-rtb'
        - Key: Application
          Value: !Ref VpcApplicationTag
  RtbPublicRoute0:
    Type: 'AWS::EC2::Route'
    DeletionPolicy: Delete
    DependsOn: IgwAttach
    Properties:
      RouteTableId: !Ref RtbPublic
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref Igw

  RtbPrivate:
    Type: 'AWS::EC2::RouteTable'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-private-rtb'
        - Key: Application
          Value: !Ref VpcApplicationTag
  RtbProtectedA:
    Type: 'AWS::EC2::RouteTable'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-protected-a-rtb'
        - Key: Application
          Value: !Ref VpcApplicationTag
  RtbProtectedARoute0:
    Type: 'AWS::EC2::Route'
    DeletionPolicy: Delete
    Properties:
      RouteTableId: !Ref RtbProtectedA
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NgwA

  RtbProtectedC:
    Type: 'AWS::EC2::RouteTable'
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub '${VpcTagName}-protected-c-rtb'
        - Key: Application
          Value: !Ref VpcApplicationTag
  RtbProtectedCRoute0:
    Type: 'AWS::EC2::Route'
    DeletionPolicy: Delete
    Properties:
      RouteTableId: !Ref RtbProtectedC
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NgwA #NgwCを作成する場合、NgwCに変更する。

  # サブネットとルートテーブルの関連付け
  SubnetRouteTableAssociationSubnetPublicA:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref SubnetPublicA
      RouteTableId: !Ref RtbPublic
  SubnetRouteTableAssociationSubnetPublicC:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref SubnetPublicC
      RouteTableId: !Ref RtbPublic
  SubnetRouteTableAssociationSubnetPrivateA:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref SubnetPrivateA
      RouteTableId: !Ref RtbPrivate
  SubnetRouteTableAssociationSubnetPrivateC:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref SubnetPrivateC
      RouteTableId: !Ref RtbPrivate
  SubnetRouteTableAssociationSubnetProtectedA:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref SubnetProtectedA
      RouteTableId: !Ref RtbProtectedA
  SubnetRouteTableAssociationSubnetProtectedC:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref SubnetProtectedC
      RouteTableId: !Ref RtbProtectedC

Outputs:
  VpcId:
    Description: The ID of the created VPC
    Value: !Ref Vpc
    Export:
      Name: !Sub "${AWS::StackName}-VpcId"

  SubnetPublicAId:
    Description: The ID of the public subnet in AZ A
    Value: !Ref SubnetPublicA
    Export:
      Name: !Sub "${AWS::StackName}-SubnetPublicAId"

  SubnetPublicCId:
    Description: The ID of the public subnet in AZ C
    Value: !Ref SubnetPublicC
    Export:
      Name: !Sub "${AWS::StackName}-SubnetPublicCId"

  SubnetProtectedAId:
    Description: The ID of the protected subnet in AZ A
    Value: !Ref SubnetProtectedA
    Export:
      Name: !Sub "${AWS::StackName}-SubnetProtectedAId"

  SubnetProtectedCId:
    Description: The ID of the protected subnet in AZ C
    Value: !Ref SubnetProtectedC
    Export:
      Name: !Sub "${AWS::StackName}-SubnetProtectedCId"

  SubnetPrivateAId:
    Description: The ID of the private subnet in AZ A
    Value: !Ref SubnetPrivateA
    Export:
      Name: !Sub "${AWS::StackName}-SubnetPrivateAId"

  SubnetPrivateCId:
    Description: The ID of the private subnet in AZ C
    Value: !Ref SubnetPrivateC
    Export:
      Name: !Sub "${AWS::StackName}-SubnetPrivateCId"

子スタック②(ec2-sg.yml)

AWSTemplateFormatVersion: "2010-09-09"
Description: NestedStack For root.yml

Parameters:
  VpcId:
    Description: "Required. Select VPC ID"
    Type: String
  Ec2SecurityGroupName:
    Description: "Enter the name for the Security Group."
    Type: String
  Ec2SecurityGroupDescription:
    Description: "Enter a description for the Security Group."
    Type: String
  Ec2SecurityGroupTagValue:
    Description: "Enter a tag value for the Security Group."
    Type: String
  #Ec2IngressCidrIp: #インバウンド通信を許可したい場合コメント解除してください。
    #Description: "CIDR block for inbound rule."
    #Type: String
    #Default: "0.0.0.0/0" # この値は適切なものに変更してください
  #Ec2IngressFromPort:
    #Description: "Start range of the TCP port for inbound rule."
    #Type: Number
    #Default: 80 # この値は適切なものに変更してください
  #Ec2IngressToPort:
    #Description: "End range of the TCP port for inbound rule."
    #Type: Number
    #Default: 80 # この値は適切なものに変更してください
  #Ec2IngressProtocol:
    #Description: "Protocol for inbound rule."
    #Type: String
    #Default: "tcp" # "tcp"、"udp"、"icmp"、または"-1" (すべてのプロトコルを指定)

Resources:
  Ec2Sg:
    Type: "AWS::EC2::SecurityGroup"
    DeletionPolicy: "Delete"
    Properties:
      GroupDescription: !Ref Ec2SecurityGroupDescription
      GroupName: !Ref Ec2SecurityGroupName
      Tags:
        - Key: "Name"
          Value: !Ref Ec2SecurityGroupTagValue
      VpcId: !Ref VpcId
  #Ec2SgIngress0:
    #Type: "AWS::EC2::SecurityGroupIngress"
    #DeletionPolicy: "Delete"
    #Properties:
      #CidrIp: !Ref Ec2IngressCidrIp
      #Description: "Inbound access"
      #FromPort: !Ref Ec2IngressFromPort
      #GroupId: !Ref Ec2Sg
      #IpProtocol: !Ref Ec2IngressProtocol
      #ToPort: !Ref Ec2IngressToPort
  Ec2SgEgress0:
    Type: "AWS::EC2::SecurityGroupEgress"
    DeletionPolicy: "Delete"
    Properties:
      CidrIp: "0.0.0.0/0"
      Description: "No limit access to anywhere"
      FromPort: 0
      GroupId: !Ref Ec2Sg
      IpProtocol: "-1"
      ToPort: 65535

Outputs:
  SecurityGroupId:
    Description: "The ID of the created Security Group"
    Value: !Ref Ec2Sg
    Export:
      Name: Ec2SecurityGroupIdExport

子スタック③(ec2.yml)

AWSTemplateFormatVersion: '2010-09-09'
Description: NestedStack Forroot.yml

Parameters:
  ImageId :
    Type : "AWS::EC2::Image::Id"
    Description: Enter Amazon Machine Image ID for Windows.
  Ec2InstanceTypeParameter:
    Type: String
    Default: t3.micro
  Ec2IamInstanceProfileName:
    Type: String
  Ec2InstanceName:
    Type: String
    Description: (Required) MaxLength 15.Enter EC2 Instance Name. #Windows の制約上、ホスト名は16文字未満となるため。
    MaxLength: 15
    MinLength: 1
  Ec2DiskSize1:
    Type: String
    Description: (Required) Enter the disk size.
    Default: "30"
  #Ec2DiskSize2:
  #  Type: String
  #  Description: (Required) Enter the disk size.
  #  Default: "8"
  Ec2SubnetIdForEth0:
    Type: "AWS::EC2::Subnet::Id"
    Description: "Required. Select SubnetId in execution environment!"
  Ec2SecurityGroupIdsForEth0:
    Type: "List<AWS::EC2::SecurityGroup::Id>"
    Description: "Required. Select SecurityGroup in execution environment!"
  Ec2KeyName:
    Type: String
    Description: (Required) Enter the Ec2KeyName.
    Default: "XXXXX"
  Ec2ApplicationTag:
    Type: String
    Description: Application tag for all resources <System Name>
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "Basic Configuration"
        Parameters:
          - "Ec2InstanceName"
          - "Ec2InstanceTypeParameter"
          - "InstanceType"
          - "ImageId"
          - "Ec2IamInstanceProfileName"
          - "Ec2DiskSize1"
          #- "Ec2DiskSize2"
          - "Ec2KeyName"
      -
        Label:
          default: "NetworkInterface Configuration For Eth0"
        Parameters:
          - "Ec2SubnetIdForEth0"
          - "Ec2SecurityGroupIdsForEth0"

Resources:
  ## 既存キーペア利用時は以下KeyPairは不要のためコメントアウトする。
  KeyPair:
    Type: "AWS::EC2::KeyPair"
    Properties:
      KeyName:
        Ref: "Ec2KeyName"
      KeyType: rsa
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
  EC2Instance:
    Type: "AWS::EC2::Instance"
    DeletionPolicy: "Delete"
    Properties:
      BlockDeviceMappings:
        - DeviceName: "/dev/sda1"
          Ebs:
            DeleteOnTermination: true
            Encrypted: false
            VolumeSize:
              Ref: "Ec2DiskSize1"
            VolumeType: "gp3"
        ## EBSを複数使用する場合は以下2つ目のDeviceNameを使用。
        #- DeviceName: "/dev/sdf"
        #  Ebs:
        #    DeleteOnTermination: true
        #    Encrypted: false
        #    VolumeSize:
        #      Ref: "Ec2DiskSize2"
        #    VolumeType: "gp3"
      #CreditSpecification: # バーストパフォーマンスUnlimited無効化。T系インスタンスタイプ以外はサポートしない項目。
        #CPUCredits: standard
      DisableApiTermination: true
      EbsOptimized: true # EBS最適化
      IamInstanceProfile:
        Ref: "Ec2IamInstanceProfileName"
      ImageId:
        Ref: "ImageId"
      InstanceInitiatedShutdownBehavior: "stop"
      InstanceType:
        Ref: "Ec2InstanceTypeParameter"
      Monitoring: false
      KeyName: !Ref KeyPair
      NetworkInterfaces:
        - AssociatePublicIpAddress: false
          DeleteOnTermination: true #Eth0は、DeleteOnTermination:trueでなければローンチできないため、強制的にtrueを設定。falseの場合、"A network interface without a network interface ID must have delete on termination as true"となるため。
          DeviceIndex: 0
          GroupSet:
            Ref: "Ec2SecurityGroupIdsForEth0"
          SubnetId:
            Ref: "Ec2SubnetIdForEth0"
      SourceDestCheck: true
      Tags:
        - Key: Name
          Value:
            Ref: Ec2InstanceName
        - Key: Application
          Value: !Ref Ec2ApplicationTag
      Tenancy: "default"
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate01
        Version: 1
  LaunchTemplate01: ## EBS,ENIにタグ付与
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        MetadataOptions: # セキュリティ強化のためIMDSv2のみ有効を標準とする
          HttpTokens: required
        TagSpecifications:
          - ResourceType: volume
            Tags:
              - Key: Name
                Value:
                  Ref: Ec2InstanceName
              - Key: Application
                Value: !Ref Ec2ApplicationTag
          - ResourceType: network-interface
            Tags:
              - Key: Name
                Value:
                  Ref: Ec2InstanceName
              - Key: Application
                Value: !Ref Ec2ApplicationTag

Outputs:
  EC2InstanceId:
    Description: The ID of the created EC2 Instance
    Value: !Ref EC2Instance
    Export:
      Name: Ec2InstanceIdExport

Jsonファイル(ParameterFile.json)

[
    {
        "ParameterKey": "VpcCidr",
        "ParameterValue": "10.0.0.0/16"
    },
    {
        "ParameterKey": "PublicSubnetACidr",
        "ParameterValue": "10.0.1.0/24"
    },
    {
        "ParameterKey": "PublicSubnetCCidr",
        "ParameterValue": "10.0.2.0/24"
    },
    {
        "ParameterKey": "ProtectedSubnetACidr",
        "ParameterValue": "10.0.3.0/24"
    },
    {
        "ParameterKey": "ProtectedSubnetCCidr",
        "ParameterValue": "10.0.4.0/24"
    },
    {
        "ParameterKey": "PrivateSubnetACidr",
        "ParameterValue": "10.0.5.0/24"
    },
    {
        "ParameterKey": "PrivateSubnetCCidr",
        "ParameterValue": "10.0.6.0/24"
    },
    {
        "ParameterKey": "FlowLogsBucketName",
        "ParameterValue": "<実際に起動したいVPCフローログを保存するS3バケット名に書き換えてください>"
    },
    {
        "ParameterKey": "VpcTagName",
        "ParameterValue": "<実際に起動したいVPC名に書き換えてください>"
    },
    {
        "ParameterKey": "VpcApplicationTag",
        "ParameterValue": "<アプリケーションタグに指定したい値に書き換えてください>"
    },
    {
        "ParameterKey": "Ec2SecurityGroupName",
        "ParameterValue": "<実際に起動したいセキュリティグループ名に書き換えてください>"
    },
    {
        "ParameterKey": "Ec2SecurityGroupDescription",
        "ParameterValue": "<実際に起動したいセキュリティグループ名の説明に書き換えてください>"
    },
    {
        "ParameterKey": "Ec2SecurityGroupTagValue",
        "ParameterValue": "<実際に起動したいセキュリティグループ名に書き換えてください>"
    },
    {
        "ParameterKey": "ImageId",
        "ParameterValue": "<実際に起動するAMI IDに書き換えてください>"
    },
    {
        "ParameterKey": "Ec2InstanceTypeParameter",
        "ParameterValue": "<実際に起動したいインスタンスタイプに書き換えてください>"
    },
    {
        "ParameterKey": "Ec2IamInstanceProfileName",
        "ParameterValue": "<実際に紐づけるIAMロール名に書き換えてください>"
    },
    {
        "ParameterKey": "Ec2InstanceName",
        "ParameterValue": "<実際に起動したいインスタンス名に書き換えてください>"
    },
    {
        "ParameterKey": "Ec2DiskSize1",
        "ParameterValue": "65"
    },
    {
        "ParameterKey": "Ec2KeyName",
        "ParameterValue": "<実際に起動したいキーペア名に書き換えてください>"
    },
    {
        "ParameterKey": "Ec2ApplicationTag",
        "ParameterValue": "<アプリケーションタグに指定したい値に書き換えてください>"
    }
]

実行コマンド

前提

「準備」にて記載した内容を全て保存していると下記のようなフォルダ構成になっているはずです。

<フォルダ名>
    - root.yml
    - vpc.yml
    - ec2-sg.yml
    - ec2.yml
    - ParameterFile.json

①テンプレートを保存したディレクトリ配下に移動します。

cd <ディレクトリ名>

私はblogディレクトリ配下にテンプレートを保存したので、下記のように移動します。

②子スタックをパッケージングする。

子スタックをパッケージングします。
この段階でTemplateURLが置き換わって作成されます。

aws cloudformation package --template-file <メインスタック名> --output-template-file packaged-<メインスタック名> --s3-bucket <子スタックを保存するS3バケット名>

<コマンド例>
aws cloudformation package --template-file root.yml --output-template-file packaged-root.yml --s3-bucket hidaka-cloudformation-template

※aws cloudformation packageについて詳しくは以下をご覧ください。

docs.aws.amazon.com

コマンドを実行すると、--output-template-fileで指定した「packaged-root.yml」という名前のテンプレートができています。
「packaged-root.yml」の中を見てみると、下記のようにTemplateURLが置き換わっていました。

  • packaged-root.yml

  • root.yml

③メインスタックを即時実行させる。

②で作成したメインスタックを実行させます。

aws cloudformation deploy --template-file packaged-<メインスタック名> --stack-name <スタック名> --capabilities CAPABILITY_NAMED_IAM --parameter-overrides file://<パラメータを指定しているJsonファイル名>

<コマンド例>
aws cloudformation deploy --template-file packaged-root.yml --stack-name test --capabilities CAPABILITY_NAMED_IAM --parameter-overrides file://ParameterFile.json

※aws cloudformation deployについて詳しくは以下をご覧ください。

docs.aws.amazon.com

実際にコマンドを実行すると下記のような表示がされます。

CloudFormationのマネジメントコンソールに移動するとネストされたスタックが実行されています。

では①〜③のコマンドを実行した後、どのように動作しているのか。
「コマンド実行後イメージ」について記載していきます。

コマンド実行後イメージ

vpc.ymlがroot.yml(メインスタック)から呼び出されるイメージ図

AWS CLIコマンドで実行すると上記の図のように実行されます。
※あくまでイメージなので厳密な実行プロセスは違う可能性があります。

  1. メインスタックが、ParameterFile.jsonから設定するパラメータを読み込む。
  2. メインスタック内のResources句に、ParameterFile.jsonlから受け取った値を渡す。
  3. メインスタックのProperties句が実行する子スタックを呼び出す。
  4. 子スタックのParameters句にParameterFile.jsonlから受け取った値を渡す。

あとは、メインスタックで指定している子スタック分③、④が繰り返されるイメージです。
下記の画面のようになっていたら無事成功しています。

まとめ

CloudFormationを使用してネストされたスタックを作成し、JSONファイルでパラメータを設定してAWS CLIで実行する方法についてブログを記載しました。
かなりややこしく理解に時間がかかったので、何回も読み返していただけると幸いです。

本記事が誰かの助けになっていれば幸いです。

日高 僚太(執筆記事の一覧)

EC部ソリューションアーキテクト2課 / AWS認定資格 12冠

2022年IT未経験で新卒入社しました。
最近はダーツとサウナが気になっています!
記事に関するお問い合わせや修正依頼⇒ hidaka@serverworks.co.jp