ネストされたCFnテンプレートをaws cliで扱う

CFnのテンプレートを1つにしておくと可読性が悪くなったりなどはよく言われているのでNested Stackを使って分割するサンプルを書いてみた。
これをaws cliで扱うようにするとネストされたスタックのファイルが扱いやすくなる。

ネストされたスタック

CloudFormationの最上位のテンプレート内で、別のCloudFormationテンプレートをリソースとして定義することができる、というもの。

これによってテンプレートを分割できる。

試す

VPCの作成とEC2の作成を別テンプレートにして試す。

ディレクトリ構成は以下のようにしてみる。
直下の./master.ymlの中に./ec2/master.yml./vpc/master.ymlを呼び出すように定義する。

./
┗ master.yml
  ec2/
  ┗ master.yml
  vpc/
  ┗ master.yml

master.ymlの中身は以下のような感じにしている。

  • ./master.yml
  • AWSTemplateFormatVersion: '2010-09-09'
    
    Resources:
      vpcStack:
        Type: AWS::CloudFormation::Stack
        Properties:
          TemplateURL: ./vpc/master.yml
      EC2Stack:
        Type: AWS::CloudFormation::Stack
        Properties:
          TemplateURL: ./ec2.yml

TemplateURLのところでネストするテンプレートを定義する。
通常この部分はアップロードする先のS3のURLを指定しなければならない。

  • ./vpc/master.yml
  • AWSTemplateFormatVersion: 2010-09-09
    
    Parameters:
      ProjectNameParameter:
        Type: String
        Default: 'sample-cfn'
        Description: Project name for tag
    
    Resources:
      # VPC
      vpc1:
        Type: 'AWS::EC2::VPC'
        Properties:
          CidrBlock: 10.1.0.0/16
          InstanceTenancy: default
          EnableDnsSupport: 'true'
          EnableDnsHostnames: 'false'
          Tags:
            - Key: Name
              Value: !Sub "${ProjectNameParameter}-vpc"
    
      # Inernet Gateway
      igw1:
        Type: 'AWS::EC2::InternetGateway'
        Properties:
          Tags:
            - Key: Name
              Value: !Sub "${ProjectNameParameter}-igw"
    
      # DHCP Options
      dopt1:
        Type: 'AWS::EC2::DHCPOptions'
        Properties:
          Tags:
            - Key: Name
              Value: !Sub "${ProjectNameParameter}-dopt"
          DomainName: ap-northeast-1.compute.internal
          DomainNameServers:
            - AmazonProvidedDNS
    
      # RouteTable
      rt1:
        Type: 'AWS::EC2::RouteTable'
        Properties:
          VpcId: !Ref vpc1
          Tags:
            - Key: Name
              Value: !Sub "${ProjectNameParameter}-rt"
    
      # NetworkACL
      nacl1:
        Type: 'AWS::EC2::NetworkAcl'
        Properties:
          VpcId: !Ref vpc1
    
      # NetworkACL Entry
      acl1:
        Type: 'AWS::EC2::NetworkAclEntry'
        Properties:
          CidrBlock: 0.0.0.0/0
          Egress: 'true'
          Protocol: '-1'
          RuleAction: allow
          RuleNumber: '100'
          NetworkAclId: !Ref nacl1
      acl2:
        Type: 'AWS::EC2::NetworkAclEntry'
        Properties:
          CidrBlock: 0.0.0.0/0
          Protocol: '-1'
          RuleAction: allow
          RuleNumber: '100'
          NetworkAclId: !Ref nacl1
    
      # InternetGateway
      gw1:
        Type: 'AWS::EC2::VPCGatewayAttachment'
        Properties:
          VpcId: !Ref vpc1
          InternetGatewayId: !Ref igw1
    
      # RouteTable
      route1:
        Type: 'AWS::EC2::Route'
        Properties:
          DestinationCidrBlock: 0.0.0.0/0
          RouteTableId: !Ref rt1
          GatewayId: !Ref igw1
        DependsOn: gw1
    
      # DHCP Options Association
      dchpassoc1:
        Type: 'AWS::EC2::VPCDHCPOptionsAssociation'
        Properties:
          VpcId: !Ref vpc1
          DhcpOptionsId: !Ref dopt1
    
      # Subnet
      subnet1:
        Type: 'AWS::EC2::Subnet'
        Properties:
          CidrBlock: 10.1.1.0/24
          AvailabilityZone: ap-northeast-1a
          VpcId: !Ref vpc1
          Tags:
            - Key: Name
              Value: !Sub "${ProjectNameParameter}-web-public-1a-subnet"
      mySubnetRouteTableAssociation:
        Type: 'AWS::EC2::SubnetRouteTableAssociation'
        Properties:
          SubnetId: !Ref subnet1
          RouteTableId: !Ref rt1
      subnetacl1:
        Type: 'AWS::EC2::SubnetNetworkAclAssociation'
        Properties:
          SubnetId: !Ref subnet1
          NetworkAclId: !Ref nacl1
    
    ### Outputs
    Outputs:
      VpcId:
        Value: !Ref vpc1
        Export:
          Name: !Sub "${ProjectNameParameter}-vpc"
      RouteTableId:
        Value: !Ref rt1
        Export:
          Name: !Sub "${ProjectNameParameter}-rt"
      NetworkAclId:
        Value: !Ref nacl1
        Export:
          Name: !Sub "${ProjectNameParameter}-nacl"
      SubnetId:
        Value: !Ref subnet1
        Export:
          Name: !Sub "${ProjectNameParameter}-subnet"
  • ./ec2/master.yml
  • AWSTemplateFormatVersion: 2010-09-09
    Parameters:
      ProjectNameParameter:
        Type: String
        Default: 'sample-cfn'
        Description: Project name for tag
    Resources:
      EC2:
        Type: 'AWS::EC2::Instance'
        Properties:
          DisableApiTermination: 'false'
          InstanceInitiatedShutdownBehavior: stop
          ImageId: ami-00f9d04b3b3092052
          InstanceType: t2.micro
          KeyName: ec2-key-pair-apn1
          Monitoring: 'false'
          Tags:
            - Key: Name
              Value: !Sub "${ProjectNameParameter}-ec2"
          NetworkInterfaces:
            - DeleteOnTermination: 'true'
              Description: Primary network interface
              DeviceIndex: 0
              SubnetId:
                "Fn::ImportValue": !Sub "${ProjectNameParameter}-subnet"
              PrivateIpAddresses:
                - PrivateIpAddress: 10.1.1.45
                  Primary: 'true'
              GroupSet:
                - !Ref sg1
              AssociatePublicIpAddress: 'true'
      sg1:
        Type: 'AWS::EC2::SecurityGroup'
        Properties:
          GroupDescription: 'ds-common-ec2-bastion01-sg created 2018-11-15T15:18:31.279+09:00'
          VpcId:
            "Fn::ImportValue": !Sub "${ProjectNameParameter}-vpc"
      ingress1:
        Type: 'AWS::EC2::SecurityGroupIngress'
        Properties:
          GroupId: !Ref sg1
          IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: 0.0.0.0/0
      egress1:
        Type: 'AWS::EC2::SecurityGroupEgress'
        Properties:
          GroupId: !Ref sg1
          IpProtocol: '-1'
          CidrIp: 0.0.0.0/0
    Description: Create EC2 Template
    

まとめ

ネストされたスタックをaws cliで扱うやり方について書いた。親テンプレートに定義するTemplateURLは本来、子テンプレートが置いてあるs3のバケットURLを指定する必要があるが、ローカルのディレクトリを指定してaws cliを使ってパッケージングすると、子テンプレートをS3にアップロードした上でTemplateURLをs3のURLに書き換えてくれるのでその点は便利だと思う。
ただ、テンプレートが肥大化すると結局変数の受け渡しが煩雑になる気はする。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です