📥

超細分化 CloudFormation スタックテンプレート

2021/08/31に公開

超細分化 AWS CloudFormation RootStackTemplate

AWSTemplateFormatVersion: 2010-09-09
 _____                 _     _____  _                 _
|  __ \               | |   / ____|| |               | |
| |__) |  ___    ___  | |_ | (___  | |_   __ _   ___ | | __
|  _  /  / _ \  / _ \ | __| \___ \ | __| / _` | / __|| |/ /
| | \ \ | (_) || (_) || |_  ____) || |_ | (_| || (__ |   <
|_|  \_\ \___/  \___/  \__||_____/  \__| \__,_| \___||_|\_\

あらまし

こんにちは、先月SAAに合格したのですが、どうも机上の空論で終わっている気がしたので、復習も兼ねて実際にCloudFormationをもっと実践的に記述していこうと色々やってみています。

https://www.credly.com/badges/a9a52cc4-9106-4c48-b434-84cc4adaaa56

今回はCloudFormationテンプレートをもっと柔軟に使いまわせるように、超細分化して、リソースごとにテンプレートを分けて、スタックを構成してみました。

キーワード

  • AWS
  • CloudFormation
  • S3
  • ルートスタックテンプレート
  • クロススタック参照
  • Outputsセクション
  • ImportValue関数
  • GetAtt関数

AWS CloudFormationとは?

シンプルなテキストファイル(テンプレート)を使用して、あらゆるリージョンとアカウントでアプリケーションに必要とされるAWSリソースをプロビジョニングできるサービスです。
CloudFormationに関する追加料金は発生しません。(プロビジョニングされたAWSリソース分の料金のみ発生します。)

https://ecs-for-aws-summit-online.workshop.aws/1_setup/11_cloudformation.html

今回の基本構成方針図

スクリーンショット 2021-08-31 0.59.20.png

スクリーンショット 2021-08-31 1.10.11.png

  • マルチAZ構成(トリプルAZ)
  • ELBによる負荷分散

VPC

まずはVPCです、
超細分化する際に基準にしたのは一テンプレートにおいて極力一リソースまでを出力すると言うことを意識しました。

また細分化するにあたって工夫した点は、
CloudFormationを記述していると、細分化した際にOutputsをいちいち確認したり探したりするのに苦労すると思います。

特に今回のように超細分化してCloudFormationTemplateを記述するとなると、すぐに見るべきファイルを見失いがちです。

YAMLで記述するとJSONと違ってコメントを記述できるので、アスキーアートを使ってコメントで看板的なものを記述してあります。ダラダラとDescriptionを記述するよりは視認性が上がって個人的にはいいんじゃないかと気に入っています。

こんな感じ↓

 __      __
 \ \    / /
  \ \  / /  _ __    ___
   \ \/ /  | '_ \  / __|
    \  /   | |_) || (__
     \/    | .__/  \___|
           | |
           |_|

スクリーンショット 2021-08-13 15.16.52.png

アスキーアートは次のWebサービスで生成させていただきました。

https://rakko.tools/tools/68/

では、一行目から順にみていきましょう。

AWSTemplateFormatVersion: 2010-09-09

この宣言に関してはCloundFormationを記述する上で、どのバージョンで実行させるかと言うことを宣言できます。

最新のテンプレートの形式バージョンは 2010-09-09 であり、現時点で唯一の有効な値です。

AWSTemplateFormatVersion セクションは任意記載です。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/format-version-structure.html

Resources:以下にはAWSにおける実際のリソース定義を記述します。

ではVPCのTemplateを見ていきましょう。

AWSTemplateFormatVersion: 2010-09-09
# __      __
# \ \    / /
#  \ \  / /  _ __    ___
#   \ \/ /  | '_ \  / __|
#    \  /   | |_) || (__
#     \/    | .__/  \___|
#           | |
#           |_|
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: cfn-training-vpc
Outputs:
  VpcOut:
    Value: !Ref VPC
    Export:
      Name: CreateVpc

今回はYAMLで記述するので、インデントに意味があります。
また、文字列型の記述に関してYAMLはシングルクウォート、ダブルクウォート、文字列を区別しないので、以下の宣言は全て同じ意味として解釈されます。

AWSTemplateFormatVersion: "2010-09-09"
AWSTemplateFormatVersion: '2010-09-09'
AWSTemplateFormatVersion: 2010-09-09

VPC:に関しては自分の好きな定義名を宣言できます。

またこのフィールドはAWSの何のリソースについての記述なのか、
と言うことを宣言するために次の記述が必要になります。

Type: AWS::EC2::VPC

ここでは、VPCを定義するためTypeにVPCを宣言しましょう。

次に、Properties:ですが、この予約語以下インデント区切りのパラメータがVPCの定義に必要なプロパティーを記述できます。

では順にプロパティーを見ていきましょう。

CidrBlock

CidrBlock: 10.0.0.0/16

ここでは、
クラスレスインタードメインルーティングブロックを定義します、今回はクラスA、
10.0.0.0 ~ 10.255.255.255 (10.0.0.0/8) として定義しておきますが、 Cidrと言う名前の通りネットワーククラスを深く意識する必要はないと思います。

ちなみに、このブロックの意味は
10.0.0.0/16は、10.0.0.0から10.0.255.255までの65,536個のアドレス
256 x 256 = 65,536
8 + 8なので第二オクテットまでという意味。

EnableDns*

EnableDnsHostnames: true

単純にDNSホスト名を有効にするための宣言です。

EnableDnsSupport: true

こちらも単純にDNSサポートを有効にするか否かの宣言です。

InstanceTenancy

InstanceTenancy: default

VPC内に起動されたインスタンスは、インスタンスの起動時に別のテナント属性を明示的に指定しない限り、
今回のデフォルト指定では共有ハードウェアで実行されます。

VPCに関するそのほかのプロパティーに関しては公式ドキュメントを参考にしましょう。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html

Outputsセクション

Outputs:
  VpcOut:
    Value: !Ref VPC
    Export:
      Name: CreateVpc

他のCloudFormationテンプレートから出力した値を使用するという宣言をすることができます。
これは、例えばあるリソースを、別のもしくは同名のエクスポート名に置き換えると、別のテンプレートからこのリソースのIDを取得できます。

具体的には、他のCloudFormationテンプレートからどのように扱えば良いのか?

ポイントは二つです。

  • !ImportValue: エクスポート名 といった形式でインポートする

  • 他のCloudFormationテンプレート依存するStackに当たるので、Outputsするスタックを取り込むには依存関係を設定する

例えば今回作成したVPCを他のテンプレートでインポートするには次のように記述します。

Resources:
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !ImportValue CreateVpc
      InternetGatewayId: !ImportValue CreateIgw

簡単ですね、これをクロススタックと呼んだりします。
同一テンプレート内で参照するには!Ref等を使ったりするのですが、基本的には同じ考え方です。
詳しくは公式ドキュメントを参照しましょう。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

同じ要領でその他、必要なテンプレートを記述していきます。

  • InternetGateway
  • Subnet
  • NatGateway
  • Route
  • SecurityGroup
  • EC2
  • ELB

InternetGateway

AWSTemplateFormatVersion: 2010-09-09
#  _____
# |_   _|
#   | |    __ _ __      __
#   | |   / _` |\ \ /\ / /
#  _| |_ | (_| | \ V  V /
# |_____| \__, |  \_/\_/
#          __/ |
#         |___/
Resources:
  InternetGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: cfn-training-igw
Outputs:
  IgwOut:
    Value: !Ref InternetGW
    Export:
      Name: CreateIgw

Subnet

AWSTemplateFormatVersion: 2010-09-09
#   _____         _                   _
#  / ____|       | |                 | |
# | (___   _   _ | |__   _ __    ___ | |_
#  \___ \ | | | || '_ \ | '_ \  / _ \| __|
#  ____) || |_| || |_) || | | ||  __/| |_
# |_____/  \__,_||_.__/ |_| |_| \___| \__|
Resources:
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !ImportValue CreateVpc
      InternetGatewayId: !ImportValue CreateIgw
  PublicSubnet1a:
    Type: AWS::EC2::Subnet
    DependsOn: VPCGatewayAttachment
    Properties:
      AvailabilityZone: !Sub ${AWS::Region}a
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: cfn-training-publicsubnet1a
      VpcId: !ImportValue CreateVpc
  PublicSubnet1c:
    Type: AWS::EC2::Subnet
    DependsOn: VPCGatewayAttachment
    Properties:
      AvailabilityZone: !Sub ${AWS::Region}c
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: cfn-training-publicsubnet1c
      VpcId: !ImportValue CreateVpc
  PublicSubnet1d:
    Type: AWS::EC2::Subnet
    DependsOn: VPCGatewayAttachment
    Properties:
      AvailabilityZone: !Sub ${AWS::Region}d
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: cfn-training-publicsubnet1d
      VpcId: !ImportValue CreateVpc
  PrivateSubnet1a:
    Type: AWS::EC2::Subnet
    DependsOn: VPCGatewayAttachment
    Properties:
      AvailabilityZone: !Sub ${AWS::Region}a
      CidrBlock: 10.0.10.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: cfn-training-privatesubnet1a
      VpcId: !ImportValue CreateVpc
  PrivateSubnet1c:
    Type: AWS::EC2::Subnet
    DependsOn: VPCGatewayAttachment
    Properties:
      AvailabilityZone: !Sub ${AWS::Region}c
      CidrBlock: 10.0.11.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: cfn-training-privatesubnet1c
      VpcId: !ImportValue CreateVpc
  PrivateSubnet1d:
    Type: AWS::EC2::Subnet
    DependsOn: VPCGatewayAttachment
    Properties:
      AvailabilityZone: !Sub ${AWS::Region}d
      CidrBlock: 10.0.20.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: cfn-training-privatesubnet1d
      VpcId: !ImportValue CreateVpc
Outputs:
  PublicSubnet1aOut:
    Value: !Ref PublicSubnet1a
    Export:
      Name: CreatePublicSubnet1a
  PublicSubnet1cOut:
    Value: !Ref PublicSubnet1c
    Export:
      Name: CreatePublicSubnet1c
  PublicSubnet1dOut:
    Value: !Ref PublicSubnet1d
    Export:
      Name: CreatePublicSubnet1d
  PrivateSubnet1aOut:
    Value: !Ref PrivateSubnet1a
    Export:
      Name: CreatePrivateSubnet1a
  PrivateSubnet1cOut:
    Value: !Ref PrivateSubnet1c
    Export:
      Name: CreatePrivateSubnet1c
  PrivateSubnet1dOut:
    Value: !Ref PrivateSubnet1d
    Export:
      Name: CreatePrivateSubnet1d

NatGateway

AWSTemplateFormatVersion: 2010-09-09
#  _   _         _     _____ __          __
# | \ | |       | |   / ____|\ \        / /
# |  \| |  __ _ | |_ | |  __  \ \  /\  / /
# | . ` | / _` || __|| | |_ |  \ \/  \/ /
# | |\  || (_| || |_ | |__| |   \  /\  /
# |_| \_| \__,_| \__| \_____|    \/  \/
Resources:
  NATGateway1a:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIP1a.AllocationId
      SubnetId: !ImportValue CreatePublicSubnet1a
      Tags:
        - Key: Name
          Value: cfn-training-nat-gateway-1a
  EIP1a:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: cfn-training-eip-1a
  NATGateway1c:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIP1c.AllocationId
      SubnetId: !ImportValue CreatePublicSubnet1c
      Tags:
        - Key: Name
          Value: cfn-training-nat-gateway-1c
  EIP1c:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: cfn-training-eip-1c
  NATGateway1d:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIP1d.AllocationId
      SubnetId: !ImportValue CreatePublicSubnet1d
      Tags:
        - Key: Name
          Value: cfn-training-nat-gateway-1d
  EIP1d:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: cfn-training-eip-1d
Outputs:
  NATGateway1aOut:
    Value: !Ref NATGateway1a
    Export:
      Name: CreateNATGateway1a
  NATGateway1cOut:
    Value: !Ref NATGateway1c
    Export:
      Name: CreateNATGateway1c
  NATGateway1dOut:
    Value: !Ref NATGateway1d
    Export:
      Name: CreateNATGateway1d

Route

AWSTemplateFormatVersion: 2010-09-09
#  _____                 _
# |  __ \               | |
# | |__) |  ___   _   _ | |_   ___
# |  _  /  / _ \ | | | || __| / _ \
# | | \ \ | (_) || |_| || |_ |  __/
# |_|  \_\ \___/  \__,_| \__| \___|
Resources:
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !ImportValue CreateVpc
      Tags:
      - Key: Name
        Value: cfn-training-route-table
  InternetGWRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !ImportValue CreateIgw
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: cfn-training-public-route-table
      VpcId: !ImportValue CreateVpc
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !ImportValue CreateIgw
      RouteTableId: !Ref PublicRouteTable
  PrivateRouteTable1a:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: cfn-training-private-route-table-1a
      VpcId: !ImportValue CreateVpc
  PrivateRoute1a:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !ImportValue CreateNATGateway1a
      RouteTableId: !Ref PrivateRouteTable1a
  PrivateRouteTable1c:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: cfn-training-private-route-table-1c
      VpcId: !ImportValue CreateVpc
  PrivateRoute1c:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !ImportValue CreateNATGateway1c
      RouteTableId: !Ref PrivateRouteTable1c
  PrivateRouteTable1d:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: cfn-training-private-route-table-1d
      VpcId: !ImportValue CreateVpc
  PrivateRoute1d:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !ImportValue CreateNATGateway1d
      RouteTableId: !Ref PrivateRouteTable1d
  PublicSubnet1aRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !ImportValue CreatePublicSubnet1a
  PublicSubnet1cRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !ImportValue CreatePublicSubnet1c
  PublicSubnet1dRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !ImportValue CreatePublicSubnet1d
  PrivateSubnet1aRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1a
      SubnetId: !ImportValue CreatePrivateSubnet1a
  PrivateSubnet1cRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1c
      SubnetId: !ImportValue CreatePrivateSubnet1c
  PrivateSubnet1dRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1d
      SubnetId: !ImportValue CreatePrivateSubnet1d
  VPCGatewayEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PrivateRouteTable1a
        - !Ref PrivateRouteTable1c
        - !Ref PrivateRouteTable1d
        - !Ref PublicRouteTable
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcId: !ImportValue CreateVpc

SecurityGroup

AWSTemplateFormatVersion: 2010-09-09
#   _____               _____
#  / ____|             / ____|
# | (___    ___   ___ | |  __  _ __
#  \___ \  / _ \ / __|| | |_ || '_ \
#  ____) ||  __/| (__ | |__| || |_) |
# |_____/  \___| \___| \_____|| .__/
#                             | |
#                             |_|
Resources:
  SecurityGp:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: DefaultRule
      VpcId: !ImportValue CreateVpc
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 22, ToPort: 22, CidrIp: 0.0.0.0/0 }
        - { IpProtocol: tcp, FromPort: 80, ToPort: 80, CidrIp: 0.0.0.0/0 }
  ELBSecurityGp:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue CreateVpc
      GroupDescription: "-"
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 80, ToPort: 80, CidrIp: 0.0.0.0/0 }
        - { IpProtocol: tcp, FromPort: 443, ToPort: 443, CidrIp: 0.0.0.0/0 }
Outputs:
  SecurityGpOut:
    Value: !Ref SecurityGp
    Export:
      Name: CreateSecGp
  ELBSecurityGpOut:
    Value: !Ref ELBSecurityGp
    Export:
      Name: CreateELBSecGp

EC2

AWSTemplateFormatVersion: 2010-09-09
# | __|  __|_  )
# | _|  (     /   Amazon Linux 2 AMI
# |___|\___|___|
Parameters:
  EC2InstanceType:
    Type: String
    Default: t2.nano
  EC2KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: SSH public key
    Default: cfn-training
Resources:
  EC2Instance1a:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-097473abce069b8e9
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref EC2KeyName
      SubnetId: !ImportValue CreatePublicSubnet1a
      SecurityGroupIds:
        - !ImportValue CreateSecGp
      UserData: !Base64 |
        #! /bin/bash
        yum update -y
        amazon-linux-extras install nginx1.12 -y
        systemctl start nginx
        systemctl enable nginx
      Tags:
        - Key: Name
          Value: cfn-training-ec2-1a
  EC2Instance1c:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-097473abce069b8e9
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref EC2KeyName
      SubnetId: !ImportValue CreatePublicSubnet1c
      SecurityGroupIds:
        - !ImportValue CreateSecGp
      UserData: !Base64 |
        #! /bin/bash
        yum update -y
        amazon-linux-extras install nginx1.12 -y
        systemctl start nginx
        systemctl enable nginx
      Tags:
        - Key: Name
          Value: cfn-training-ec2-1c
  EC2Instance1d:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-097473abce069b8e9
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref EC2KeyName
      SubnetId: !ImportValue CreatePublicSubnet1d
      SecurityGroupIds:
        - !ImportValue CreateSecGp
      UserData: !Base64 |
        #! /bin/bash
        yum update -y
        amazon-linux-extras install nginx1.12 -y
        systemctl start nginx
        systemctl enable nginx
      Tags:
        - Key: Name
          Value: cfn-training-ec2-1d
Outputs:
  Region:
    Value: !Ref AWS::Region
  EC2Instance1a:
    Value: !Ref EC2Instance1a
    Export:
      Name: CreateEC21a
  EC2Instance1c:
    Value: !Ref EC2Instance1c
    Export:
      Name: CreateEC21c
  EC2Instance1d:
    Value: !Ref EC2Instance1d
    Export:
      Name: CreateEC21d

CLB

AWSTemplateFormatVersion: 2010-09-09
Description: Create CLB
Resources:
  InternetELB:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      LoadBalancerName: cfn-training-elb
      Scheme: internet-facing
      CrossZone: true
      HealthCheck:
        Target: "TCP:80"
        HealthyThreshold: "2"
        UnhealthyThreshold: "2"
        Interval: "30"
        Timeout: "5"
      Listeners:
        - LoadBalancerPort: "80"
          InstancePort: "80"
          Protocol: HTTP
      Instances:
        - !ImportValue CreateEC21a
        - !ImportValue CreateEC21c
        - !ImportValue CreateEC21d
      SecurityGroups:
        - !ImportValue CreateELBSecGp
      Subnets:
        - Fn::ImportValue: CreatePublicSubnet1a
        - Fn::ImportValue: CreatePublicSubnet1c
        - Fn::ImportValue: CreatePublicSubnet1d

最後にこれらパーツを使って先程の構成図のような環境を構築しましょう。
今回はルートスタックテンプレートを使ってパーツをスタックとして定義し、実際にdeployまでしていきます。

まず、ルートスタックテンプレートを記述しましょう。
ディレクトリ構成は次のようにします。

template
├── child_stack_template
│   ├── alb.yml
│   ├── autoscaling.yml
│   ├── clb.yml
│   ├── ec2.yml
│   ├── internetgateway.yml
│   ├── natgateway.yml
│   ├── nlb.yml
│   ├── route.yml
│   ├── securitygroup.yml
│   ├── subnet.yml
│   └── vpc.yml
└── root-stack.yml

ルートスタックテンプレートは次のようになります。

AWSTemplateFormatVersion: "2010-09-09"
#  _____                 _     _____  _                 _
# |  __ \               | |   / ____|| |               | |
# | |__) |  ___    ___  | |_ | (___  | |_   __ _   ___ | | __
# |  _  /  / _ \  / _ \ | __| \___ \ | __| / _` | / __|| |/ /
# | | \ \ | (_) || (_) || |_  ____) || |_ | (_| || (__ |   <
# |_|  \_\ \___/  \___/  \__||_____/  \__| \__,_| \___||_|\_\
Parameters:
  TemplateVPC:
    Type: String
  TemplateInternetGateway:
    Type: String
  TemplateSubnet:
    Type: String
  TemplateNatGateway:
    Type: String
  TemplateRoute:
    Type: String
  TemplateSecurityGroup:
    Type: String
  TemplateEC2:
    Type: String
  TemplateCLB:
    Type: String
Resources:
  VPC:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateVPC
  InternetGateway:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateInternetGateway
    DependsOn: VPC
  Subnet:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateSubnet
    DependsOn: InternetGateway
  NatGateway:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateNatGateway
    DependsOn: Subnet
  Route:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateRoute
    DependsOn: NatGateway
  SecurityGroup:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateSecurityGroup
    DependsOn: Route
  EC2:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateEC2
    DependsOn: SecurityGroup
  CLB:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref TemplateCLB
    DependsOn: EC2

デプロイ

ルートスタックテンプレートが準備できたら次はデプロイを行ってみましょう。
デプロイにはいくつか方法があって、AWSマネジメントコンソールを用いる方法が一般的だと思いますが、今回はAWS CLIを用いてデプロイを行ってみましょう。

S3へのアップロード

このような形でShellScriptにしておくと便利ですね。

#!/bin/sh -eux

readonly BUCKETNAME
readonly REGIONNAME
aws s3api create-bucket \
  --bucket "${BUCKETNAME}" \
  --create-bucket-configuration "LocationConstraint=${REGIONNAME}"
aws s3api list-buckets

デプロイ

#!/bin/sh -eux

readonly BUCKETNAME
aws s3 sync template/child_stack_template "s3://${BUCKETNAME}"/

readonly REGIONNAME
readonly STACKNAME
readonly FILENAME="template/root-stack.yml"
readonly S3URL
echo "${S3URL}"
changeset_option=""

aws cloudformation validate-template --template-body file://${FILENAME}

aws cloudformation deploy \
    ${changeset_option} \
    --stack-name "${STACKNAME}" \
    --template-file "${FILENAME}" \
    --parameter-overrides \
      TemplateVPC="${S3URL}/vpc.yml" \
      TemplateInternetGateway="${S3URL}/internetgateway.yml" \
      TemplateSubnet="${S3URL}/subnet.yml" \
      TemplateNatGateway="${S3URL}/natgateway.yml" \
      TemplateRoute="${S3URL}/route.yml" \
      TemplateSecurityGroup="${S3URL}/securitygroup.yml" \
      TemplateEC2="${S3URL}/ec2.yml" \
      TemplateCLB="${S3URL}/clb.yml"

最後にデプロイが成功するとこのようにAWSマネジメントコンソール上でも確認できます。

スクリーンショット 2021-08-23 12.18.32.png

今回は各EC2にあらかじめNginxをインストール済みなので次のようにCLBのドメインにアクセスするとこのようなwelcomeメッセージを表示することができると思います。

スクリーンショット 2021-08-30 11.19.55.png

またトリプルAZ構成かどうかはhostコマンド等を打ってみるとより実感できるかもしれません。

host cfn-training-elb-省略.ap-northeast-1.elb.amazonaws.com
cfn-training-elb-省略.ap-northeast-1.elb.amazonaws.com has address 35.XX.省略
cfn-training-elb-省略.ap-northeast-1.elb.amazonaws.com has address 13.XX.省略
cfn-training-elb-省略.ap-northeast-1.elb.amazonaws.com has address 54.XX.省略

最後に

今回作成したテンプレートには、AWSのベストプラクティス的には非推奨の構成があります。
お気づきですね、CLB(クラシックロードバランサー)の利用は好ましくありません。

ロードバランサーのテンプレートをALBに書き換えてみましょう。
こんな感じで、手を動かして、テンプレートを改良していくことでCloudFormationの感覚を掴めると思います。

今回はEC2にマルチAZ構成と言う昔ながらの王道な構成でしたが、そもそもこの構成は状況により必ずしもコスト最適ではありません。
と言うことで、次回はSAMにおけるCloudFormationの書き方等をまとめていきたいと思います。

参考文献

GitHubで編集を提案

Discussion