【AWS】CloudFormationまとめ

25 min read読了の目安(約23100字

背景・目的

近年ではWebアプリケーションを作成する際に必要となるサーバーやそれに対するセキュリティをコードで管理する、Infrastructure as Code (IaC)が利用されており、企業での導入も進んでいる。IaCを導入することで以下のようなメリットがある。

  • インフラの変更を自動化できる
  • コード化することにより、再利用が可能
  • 構成管理しやすくなる

など様々な恩恵を受けることができる。
今回は、AWSでのIaCツールとして広く知られている、CloudFormationについてまとめてみたいと思う。

CloudFormationとは

AWSのインフラ環境/リソースをコードで管理することができ、それを用いて一括でリソースを起動/削除が可能となるAWSのサービスである。


押さえておきたいポイントは以下。

  • YAML or JSONで管理する。
  • YAML or JSONで作成したテンプレートから、スタックを作成する。
  • スタックとは、テンプレートによって起動されるリソースの集合のこと。
  • CloudFormation自体の利用料金は無料。使ったリソースに対して課金される。

細かい話は以下にわかりやすくまとめってあったのでそちらを参照していただきたい。

https://dev.classmethod.jp/articles/cloudformation-beginner01/

CloudFormationテンプレートを構成する要素

本章では、CloudFormaitonのテンプレートをYAMLで作成する例をとって、スタックを作成するためのテンプレートファイルの要素を述べる。

template.yml
# バージョン(必須)
AWSTemplateFormatVersion: "2010-09-09"

# スタックの説明(任意)
Description: 

# メタデータ(任意)
Metadata: 

# パラメータ(任意)
Parameters: 

# マッピング(任意)
Mappings: 

# 条件(任意)
Conditions: 

# 起動するAWSリソース(必須)
Resources:

# 出力
Outputs:

CloudFormationテンプレートは上記のような、AWSTemplateFormatVersion, Description, Metadata, Parameter, Mappings, Condition, Resouces, Outputsから構成される。
各項目は以下のような役割を果たしている。

項目 必須 説明
AWSTemplateFormatVersion テンプレートが準拠している AWS CloudFormation テンプレートバージョン。
Description × テンプレートの説明。記述は任意。ただし、必ずテンプレートの Format Version セクションの後に記述する必要がある。
Metadata × CloudFormationに関するメタデータを定義することができる。
AWS::CloudFormation::Init
EC2インスタンス内でパッケージのインストールやファイルの作成、サービスの開始などを実行するスクリプトを記述する。
AWS::CloudFormation::Interface
Parameterセクションに記載したパラメータの順序を規定する。
AWS::CloudFormation::Designe
AWS CloudFormation デザイナー (デザイナー) におけるリソースのレイアウトを記述する。
Parameters × スタック作成時にテンプレートに渡すことができる値。
Mappings × キーとそれに関連する値のマッピングを作成することができる。作成したMappingはCloudFormationのFn::FindInMap関数で呼び出すことができる。
Conditions × 条件式をここで定義することができる。例えば、スタックが本稼働用であるかテスト環境用であるかに依存する、条件付きのリソースを作成できる。
Resources 実際に起動するAWSリソースを記述するセクション。
Outputs × スタックのプロパティを確認すると返される値を記述する。テンプレートファイルを分けたときに、別ファイルのリソースのIDなどを参照するために使われる。

実際は、全項目が必須というわけではなく、AWSTemplateFormatVersion, Resourcesが記述されていればよい。

CloudFormationを使ってみる

実際にテンプレートを作って、VPCやSubnetを構築してみる。

テンプレートの記述

例えば、AWSのVPCとサブネットを構築するテンプレートは以下のようになる。

template.yml

AWSTemplateFormatVersion: '2010-09-09'

Description:
  Create VPC and Subnet

Resources:

  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: MyVPC
        
  # ------------------------------------------------------------#
  #  Sunbet
  # ------------------------------------------------------------#
  MySubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: MySubnet

上記のように、構築するAWSリソースはResoucesセクションに記述される。Resoucesセクションには、論理ID, リソースタイプ、プロパティを記述する。

項目 説明
論理ID リソースを識別するIDのこと。テンプレート内で一意である必要がある。論理IDは、テンプレートの他の部分のリソースを参照するために使用される。
リソースタイプ 宣言しているリソースの種類を表す。上記の例でいえば、AWS::EC2::VPCやAWS::EC2::Subnetである、
プロパティ リソースに対して指定できる追加オプション。リソースによって指定できるオプションは異なる。

リソースタイプや、プロパティに何を指定できるかは、以下の公式ドキュメントを参照すると良い。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

また、CloudFormationテンプレートでは組み込み関数が利用でき、上記例では、リソースの値を返すRef関数を利用している。ここでは、Subnetを作成するときに、どのVPCに紐づけるかを示すために使われている。

なお、Ref関数によって返ってくる値は、リソースごとに異なるので注意すること。

テンプレートでリソースを宣言するときに別のテンプレートリソースを名前で指定する必要がある場合は、Ref を使用して別のリソースを参照できます。一般的に、Ref はリソースの名前を返します。たとえば、AWS::AutoScaling::AutoScalingGroup の参照は、Auto Scaling グループリソースの名前を返します。
一部のリソースでは、リソースのコンテキストにおいて別の重要な意味を持つ識別子が返されます。たとえば、AWS::EC2::EIP リソースは IP アドレスを返し、AWS::EC2::Instance はインスタンス ID を返します。

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

なお、その他の組み込み関数は以下の公式ドキュメントを参照。

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

スタックの作成

次に、実際にAWSのコンソールにログインして、スタックを作成してみる。

  • サービス > CloudFormationを選択し、「スタックの作成」を押下

  • 以下の通りテンプレートをアップロードし、「次へ」を押下。

  • スタック名を入力する画面に遷移するので、適当にスタック名を入力して「次へ」を押下。

  • スタックオプションの設定画面になるが、何も入力せずに「次へ」を押下。

  • レビュー画面で内容を確認したら、「スタックの作成」を押下。

  • 以下の画面のようにCREATE_COMPLETEと表示されたら作成完了

  • サービス > VPC を確認すると、テンプレートに記載した、MyVPCやMySubnetが作成されていることがわかる。

  • 作成されていることが確認できたら、今度はスタックを削除すれば、MyVPCやMySubnetが勝手に削除されることがわかる。

ここまでで、実際にCloudFormationでAWSリソースを構築するまでの流れを記載した。

次からは、テンプレートの作成するためのTipsについて記載する。

テンプレートを作成するときのTips

ここでは、CloudFormationテンプレートを作成するときのTipsについて記述する。
先述の「CloudFormationテンプレートを構成する要素」で記載した各セクションを記述することで、CloudFormationテンプレートをより見やすく、より再利用しやすくすることができる。

Parametersを利用して、パラメータを外出ししてみる

Parametersセクションを利用すると、パラメータを外だしできる。
例えば、CloudFormationでEC2インスタンスを構築する例で見てみる。

以下の例では、EC2インスタンスを作成するときに、AMIのIDやキーの名前といったものが直にテンプレートに記述されている。
これでは、起動したいテンプレートが変わった時などにテンプレートを修正しなければいけなくなる。

create_instances.yml(VPC, SG等は省略)

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  # ------------------------------------------------------------#
  #  EC2
  # ------------------------------------------------------------#
  WebServer: 
    Type: AWS::EC2::Instance
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp2
    # AMIのIDを直書き
      ImageId: ami-XXXXXXXXXXXXXXX        
      InstanceInitiatedShutdownBehavior: 'stop'
      InstanceType: t2.micro
      # キーペア名も直書き
      KeyName: MyKeyPair
      SecurityGroupIds: 
        - !Ref WebSG
      SubnetId: !Ref SubnetA
      Tenancy: default
      Tags: 
        - Key: Name
          Value: WebServer

そこで、以下のように、Parametersセクションにパラメータを記述することで、パラメータを外出しすることができる。パラメータには、論理IDとデータタイプを指定する。その他にもデフォルト値や、許容する値を配列で指定したりすることもできる。

create_instances.yml

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  KeyName:
    Description: The EC2 Key Pair
    Type: "AWS::EC2::KeyPair::KeyName"
  EC2AMIId:
    Description: AMI ID
    Type : String
    Default: ami-XXXXXXXXXXX

Resources:
  # ------------------------------------------------------------#
  #  EC2
  # ------------------------------------------------------------#
  WebServer: 
    Type: AWS::EC2::Instance
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp2
      # Parametersで作成した論理IDを指定
      ImageId: !Ref EC2AMIId
      InstanceInitiatedShutdownBehavior: 'stop'
      InstanceType: t2.micro
      # Parametersで作成した論理IDを指定
      KeyName: !Ref KeyName
      SecurityGroupIds: 
        - !Ref WebSG
      SubnetId: !Ref SubnetA
      Tenancy: default
      Tags: 
        - Key: Name
          Value: WebServer

上記のテンプレートを使ってスタックを作成しようとすると、以下のように、スタックの詳細画面でパラメータ入力のフォームが表示されるようになる。

これで、AMIのIDやキーペア名が変更されてもテンプレートを修正せずに、スタックを作成できる。

Metadataを利用して、パラメータを管理しやすくする

上記でパラメータを外出ししたものの、パラメータの数が増えてくると管理が煩雑になったり、スタック作成の際に見づらくなったりする。
例えば、以下のようにEC2とRDSを作成するテンプレートがあったとする。

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
  EC2AMIId:
    Description: AMI ID
    Type : String
    Default: ami-XXXXXXXXXXXX
  DatabaseName:
    Description: Database Name
    Type : String
    Default: UserName
  DatabaseMasterName:
    Description: Database Master User Namee
    Type : String
    Default: MasterName
  DatabaseMasterPassword:
    Description: Database Master User Password
    Type : String

Resources:
  # ------------------------------------------------------------#
  #  EC2
  # ------------------------------------------------------------#
  WebServer: 
    Type: AWS::EC2::Instance
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp2
      ImageId: !Ref EC2AMIId
      InstanceInitiatedShutdownBehavior: 'stop'
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      SecurityGroupIds: 
        - test
      SubnetId: test
      Tenancy: default
      Tags: 
        - Key: Name
          Value: WebServer
      
  # ------------------------------------------------------------#
  #  RDS 
  # ------------------------------------------------------------#
  Database:
    Type: AWS::RDS::DBInstance
    Properties: 
      AllocatedStorage: 20
      AllowMajorVersionUpgrade: false
      AutoMinorVersionUpgrade: false
      AvailabilityZone: ap-northeast-1a
      BackupRetentionPeriod: 0
      DBInstanceClass: db.t3.micro
      DBInstanceIdentifier: database-1
      DBName: !Ref DatabaseName
      DBSubnetGroupName: test
      DeleteAutomatedBackups: false
      DeletionProtection: false
      Engine: mysql
      EngineVersion: 8.0.20
      MasterUsername: !Ref DatabaseMasterName
      MasterUserPassword: !Ref DatabaseMasterPassword
      MaxAllocatedStorage: 1000
      MultiAZ: false
      PubliclyAccessible: false
      StorageEncrypted: false
      StorageType: gp2
      Tags: 
        - Key: Name
          Value: My-RDS-1
      VPCSecurityGroups: 
        - test

このテンプレートを使ってスタックを作成しようとすると、以下のようにパラメータの入力順がバラバラになったりして管理しずらい。

そこで以下のように、Metadataセクションに、AWS::CloudFormation::Interfaceキーを記述して、パラメータのグループを作成する。


AWSTemplateFormatVersion: '2010-09-09'

# Metadataセクション
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - 
        Label:
          default: EC2 Configuration
        Parameters:
          - EC2AMIId
          - KeyName
      - 
        Label:
          default: RDS Configuration
        Parameters:
          - DatabaseName
          - DatabaseMasterName
          - DatabaseMasterPassword
     
Parameters:
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
  EC2AMIId:
    Description: AMI ID
    Type : String
    Default: ami-XXXXXXXXXXXX
  DatabaseName:
    Description: Database Name
    Type : String
    Default: UserName
  DatabaseMasterName:
    Description: Database Master User Namee
    Type : String
    Default: MasterName
  DatabaseMasterPassword:
    Description: Database Master User Password
    Type : String

Resources:
  # ------------------------------------------------------------#
  #  EC2
  # ------------------------------------------------------------#
  WebServer: 
    Type: AWS::EC2::Instance
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp2
      ImageId: !Ref EC2AMIId
      InstanceInitiatedShutdownBehavior: 'stop'
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      SecurityGroupIds: 
        - test
      SubnetId: test
      Tenancy: default
      Tags: 
        - Key: Name
          Value: WebServer
      
  # ------------------------------------------------------------#
  #  RDS 
  # ------------------------------------------------------------#
  Database:
    Type: AWS::RDS::DBInstance
    Properties: 
      AllocatedStorage: 20
      AllowMajorVersionUpgrade: false
      AutoMinorVersionUpgrade: false
      AvailabilityZone: ap-northeast-1a
      BackupRetentionPeriod: 0
      DBInstanceClass: db.t3.micro
      DBInstanceIdentifier: database-1
      DBName: !Ref DatabaseName
      DBSubnetGroupName: test
      DeleteAutomatedBackups: false
      DeletionProtection: false
      Engine: mysql
      EngineVersion: 8.0.20
      MasterUsername: !Ref DatabaseMasterName
      MasterUserPassword: !Ref DatabaseMasterPassword
      MaxAllocatedStorage: 1000
      MultiAZ: false
      PubliclyAccessible: false
      StorageEncrypted: false
      StorageType: gp2
      Tags: 
        - Key: Name
          Value: My-RDS-1
      VPCSecurityGroups: 
        - test

上記のテンプレートを使ってスタックを作成しようとすると、以下のようにテンプレートで定義したグループと順番でパラメータ入力フォームが現れる。

これで、パラメータの入力も見やすくなる!
Metadataキーは他にもあるがここでは割愛する。

Mappingsを記述してテンプレートを再利用しやすくしてみる

Mappingsを利用すると、キーとそれに関連する値のマッピングを作成することができる。
そして、それと合わせてFindInMap関数を使うと、同じテンプレートも場合によって設定を変えることができるようになる。

例えば以下の例のように、検証環境と本番環境で作成するサブネットのCIDRブロックを変える場合などに、MappingsとFindInMap関数が有効である。

mapping.yml

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  Environment:
    Description: Type of this environment.
    Type: String
    Default: prd
    AllowedValues:
      - prd
      - stg

Mappings:
  SubnetMap:
    prd:
      Subnet: 10.0.1.0/24
    stg:
      Subnet: 10.0.2.0/24

Resources: 

  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: MyVPC
  
  # ------------------------------------------------------------#
  #  Public Sunbet A
  # ------------------------------------------------------------#
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !FindInMap [SubnetMap, !Ref Environment, Subnet]
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref MyVPC
      Tags:
      - Key: Name
        Value: PublicSubnetA

あらかじめ、Paramtersセクションで入力パラメータを決めておく。今回は"stg"(検証環境)と"prd"(本番環境)のどちらかを入力できるようにする。

そして、Mappingセクションで、キー名stg or prdに対するサブネットの値をそれぞれ定義しておく。Mappingセクションは以下のように記載する。

Mappings: 
  Mapping01: 
    Key01: 
      Name: Value01
    Key02: 
      Name: Value02
    Key03: 
      Name: Value03

そしてFindInMap関数で呼び出すと、入力パラメータ値(stg or prd)に応じて作成されるサブネットのCIDR値を変えることができる。

Fn::FindInMapは以下のように使用する。

!FindInMap [ MapName, TopLevelKey, SecondLevelKey ]
項目 説明
MapName Mappings セクションで宣言された、キーと値を含むマッピングの論理名。
TopLevelKey 最上位のキー名。この値は、キーと値のペアのリスト。
SecondLevelKey 2番目のレベルのキー名で、TopLevelKey に割り当てられたリストのキーの1つに設定される。
戻り値 SecondLevelKeyに割り当てられた値。

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

ちなみに上記テンプレートを使ってスタックを作成すると、以下のようになる。

  1. スタックの詳細を指定画面で、Environmentパラメータをprdにして、スタックを作成する。

  2. 実際に作成されたサブネットを見ると、prd用のサブネットになっていることがわかる。

Conditionsを利用して条件によって挙動を変えてみる

Mappingsセクションで、本番環境と検証環境で設定値を変えてみたが、条件分岐など複雑な動作は記述できなかった。そこで、CloudFormationの条件関数を使ってConditionsセクションに条件を記述し、本番環境だけ、追加でEC2インスタンスを構築するといった動きを記述してみる。

Conditions

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html

条件関数

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

先ほどのMappingsセクションを記述したテンプレートを修正して、以下のようにする。

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  Environment:
    Description: Type of this environment.
    Type: String
    Default: prd
    AllowedValues:
      - prd
      - stg

Mappings:
  SubnetMap:
    prd:
      Subnet: 10.0.1.0/24
    stg:
      Subnet: 10.0.2.0/24

Conditions:
  IsPrd: !Equals [!Ref Environment, "prd"]

Resources: 

  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: MyVPC
  
  # ------------------------------------------------------------#
  #  Public Sunbet A
  # ------------------------------------------------------------#
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !FindInMap [SubnetMap, !Ref Environment, Subnet]
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref MyVPC
      Tags:
      - Key: Name
        Value: PublicSubnetA

  # ------------------------------------------------------------#
  # EC2 Instance 
  # ------------------------------------------------------------#
  myEC2Instance:
    Type: AWS::EC2::Instance
    Condition: IsPrd
    Properties:
      KeyName: MyKeyPair
      ImageId: ami-XXXXXXXXXX
      InstanceType: t2.micro

今回はConditionsセクションに、本スタックが本番環境かどうかを判定する条件式IsPrdを作成した。

Conditions:
  IsPrd: !Equals [!Ref Environment, "prd"]

それを、EC2インスタンスを作成する際にConditionプロパティに指定することで、本番環境のとき(入力パラメータがprdのとき)のみにEC2インスタンスを作成するという挙動を記述できる。

myEC2Instance:
    Type: AWS::EC2::Instance
    Condition: IsPrd

実際にスタックを作成してみる。

  • 入力パラメータを"stg"にしてみる。
  • VPCとサブネットのみが作成された。
  • 次に作成されたスタックに対して、更新を行い、次は"prd"を選択
  • 今度はEC2インスタンスが作成された。

Outputsを利用して、レイヤーごとにテンプレートを分ける

構築するリソースが多くなってくると、テンプレートの行数が膨大になってくるため、AWSはCloudFormationテンプレートを複数に分け、スタックを分割することを推奨している。

https://aws.amazon.com/jp/blogs/news/webinar-bb-aws-cloudformation-2020/

AWSで推奨しているスタック分割の仕方は以下の通り。

レイヤー名 説明 代表例
Application Layer システムが直接利用するアプリケーションを記述する。 EC2, RDS, ELBなど
Security Layer ネットワークリソースのうち、ネットワークレイヤーに依存するセキュリティ関連のリソースを記述する。IAMは特に他のリソースに依存しないが、個々のレイヤーに記述する。 Security Group, IAMなど
Network Layer ネットワークレイヤーのリソースを記述する。ネットワークレイヤーは最も依存性の低いリソースであり、外部のリソースを必要としない。 VPC, Subnet, Internet Gatewayなど

スタックを分割すると、1つ問題が発生する。それは、Ref関数で同じテンプレート内のリソースを参照できなくなることである。

そこで登場するのがOutputセクションと、ImportValue関数である。Outputセクションで、リソースの情報を出力し、ImportValue関数で、他のテンプレートで作成されたリソースの情報を参照することができる。

以下でVPC, サブネット, セキュリティグループ、EC2を作成する具体例を見てみる。

network.yml

AWSTemplateFormatVersion: 2010-09-09

Resources: 

  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: MyVPC
  
  # ------------------------------------------------------------#
  #  Internet Gateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: My-InternetGateway
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref InternetGateway

  # ------------------------------------------------------------#
  #  Route Table
  # ------------------------------------------------------------#
  RouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref MyVPC
      Tags:
      - Key: Name
        Value: My-RouteTable
  Route:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  
  # ------------------------------------------------------------#
  #  Public Sunbet A
  # ------------------------------------------------------------#
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref MyVPC
      Tags:
      - Key: Name
        Value: PublicSubnetA
  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref RouteTable  

Outputs:
  VPC1:
    Value: !Ref MyVPC
    Export: 
      Name: VPCName
  SubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: SubnetName

詳細な説明は割愛するが、ここでのポイントは、OutputsセクションでVPCのリソース情報と、サブネットのリソース情報をエクスポートしている点である。Outputsセクションは以下のように記述する。

Outputs:
  # 論理ID
  Logical ID:
    Description: Information about the value
    # 返り値
    Value: Value to return
    Export:
      # エクスポート時に出力する名前
      Name: Value to export

このように、上記のテンプレートをCloudFormationで作成すると以下の画像ように出力タブで作成されたリソース情報を確認することができる。

キーの欄に指定した論理ID、値欄には出力されたVPCのIDやサブネットのIDが表示されている。さらに、エクスポート名の欄には指定したエクスポート名が表示されている。実際に他のテンプレートから参照する際にはこのエクスポート名を指定することになる。

次に、これらの情報を使ってセキュリティレイヤーを構築してみる。

security.yml
WSTemplateFormatVersion: 2010-09-09
Resources: 
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue VPCName
      GroupDescription: Allow SSH from Internet
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: WebServerSG

Outputs:
  MySecurityGroup:
    Value: !Ref MySecurityGroup
    Export: 
      Name: SecurityGroupName

ここで重要なのは、

VpcId: !ImportValue VPCName

の行であり、VPC IDを指定するときに、ImportValue関数を使っている点である。
この組み込み関数を利用することで、他のテンプレートで作成されたリソースを参照することができる。
そして、Newworkレイヤー同様、Outputsで作成したセキュリティグループIDを出力している。

さらにこれを使ってApplicationレイヤーを作成する。

application.yml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  KeyName:
    Description: The EC2 Key Pair
    Type: "AWS::EC2::KeyPair::KeyName"
  EC2AMIId:
    Description: AMI ID
    Type : String
    Default: ami-XXXXXXXXXXXXX

Resources: 
  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: !Ref KeyName
      ImageId: !Ref EC2AMIId
      InstanceType: t2.micro
      SubnetId: !ImportValue SubnetName
      SecurityGroupIds:
        - !ImportValue SecurityGroupName
      Tags:
        - Key: Name
          Value: WebServer

Securityレイヤー同様に、ImportValue関数でサブネットIDとセキュリティグループIDを参照している。

このように、OutputsセクションとImportValue関数を使うことで、CloudFormationのスタック分割を実現することができる。

まとめ

今回はAWSでIaCを実現できるサービスであるCloudFormationの基本的な活用方法ついて記載した。
ただ、Resourcesセクションに起動するAWSサービスを記述するだけでなく、より管理しやすい形でコードを記述する工夫がとても大事であることを学んだ。

最後に、

この記事はAWS初学者を導く体系的な動画学習サービス
「AWS CloudTech」の課題カリキュラムで作成しました。

https://aws-cloud-tech.com

ありがとうございました。

以上です。