はじめてのAWS CloudFormationチュートリアル

# 本記事の対象者

- AWS初心者の方

- AWS初心者向けのチュートリアル、[ハンズオン](https://qiita.com/MayForBlue/items/c96674fb6aac5852a978)などでマネジメントコンソール画面を用いてシンプルな構成を構築した経験のある方

- 上記をマネジメントコンソールではなく、ファイルを使って構築することに関心のある方

# 本記事で行うこと

AWSのサービスのひとつであるCloudFormation用のテンプレートファイルを作成し、パブリックサーバー、プライベートサーバーを持つシンプルな構成を構築していきます。

# テンプレートファイルとは

CloudFormationは、テンプレートファイルというものを読み込ませることでAWSのリソースを一括して構築してくれます。

テンプレートファイルの形式はJSONかYAMLを選択できますが、今回はYAMLで記述することにします。

# エディタ

私は普段JetBrainsのIDEを使用しているのですが、CloudFormationのプラグインが存在するので今回導入しました。

プラグインを導入することで、リソースタイプやプロパティを補完してくれるようになります(リソースタイプやプロパティがそもそもどういったものなのかについては後述)。

![スクリーンショット 2019-10-04 18.20.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317369/f3bd0b59-0959-6f0d-8636-3c69a1975428.png)

![スクリーンショット 2019-10-04 18.23.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317369/2ba36edd-b1f4-f131-3132-ff7f62f95307.png)

また、2つ目のスクリーンショットでは`Propeties`にエラー(赤い下線)が表示されていますが、これは各AWSリソースタイプに対して必須のプロパティが記述されているかどうかをチェックしてくれています。

# 1. テンプレートファイルの作成

では、ここからテンプレートファイルの作成に入ります。

## 1.1. テンプレートフォーマットバージョンの指定

まず、テンプレートのフォーマットバージョンを指定します。
指定可能な値は`2010-09-09`のみです。

```yaml:
AWSTemplateFormatVersion: 2010-09-09
```

なお、この行の記述は任意なので、無かったとしてもCloudFormationはエラーにはなりません。

- [参考： 形式バージョン - AWSドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/format-version-structure.html)

## 1.2. リソースの記述方法

ここからVPC等、各種のAWSリソースを作成するための記述を行なっていきます。

リソースの記述方法は以下になります。

```yaml:
Resources:
  <Logical ID>:
    Type: <Resource type> 
    Properties:
      <Set of properties>
```

### Logical ID

`Logical ID`は、そのリソースに付ける名前です。
同一テンプレートファイル内で、他と被らない一意なものにする必要があります。

### Resource Type

`Resource Type`の部分には、作成するAWSリソースの種類を指定します。
具体的にはリソースタイプ識別子というものを指定します。

リソースタイプ識別子は、

- `service-provider::service-name::data-type-name`

という形式になっていて、例えば

- VPCだったら、`AWS::EC2::VPC`
- サブネットだったら`AWS::EC2::Subnet`

となっています。

一覧は以下から確認できます。

- [AWS リソースおよびプロパティタイプのリファレンス - AWSドキュメント] (https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)

### Set of propeties

`Set of propeties`の部分には、各リソースのプロパティを記述します。
リソースの種類に応じて必須のプロパティと任意のプロパティがあります。

## 1.3. VPCの定義

まず、はじめにVPCを定義します。

```yaml:
# 最終行に以下を追加
Resources:
  CfVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        -
          Key: Name
          Value: CfVpc
```

### マネジメントコンソールの場合

上記は、マネジメントコンソールの`VPCの作成`において、下記の指定でVPCを作成するのと同等です。

|項目|設定内容|
|:--|:--|
|名前タグ|CfVpc
|IPv4 CIDRブロック|10.0.0.0/16
|IPv6 CIDRブロック|IPv6 CIDRブロックなし
|テナンシー|デフォルト

# 2. テンプレートファイルを使って実際にAWSリソースを作成してみる

まだVPCしか定義はしていませんが、いったんこの段階でCloudFormationを使ってAWSリソースを作成してみます。

## 2.1. スタックの作成

CloudFormationでは、テンプレートファイルで定義されたAWSリソース群をスタックという単位で管理します。

テンプレートファイルを使ってAWSリソースを作成するには、CloudFormationの画面でまず`スタックの作成`を選択します。

![ap-northeast-1.console.aws.amazon.com_cloudformation_home_region=ap-northeast-1 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317369/79f7d9da-829f-ebf8-3840-68d35316f8e6.png)

## 2.2. テンプレートの指定

`テンプレートの準備完了` > `テンプレートのファイルのアップロード` > `ファイルの選択` を選び、作成したテンプレートファイルをアップロードしたら、`次へ`を押します。

![ap-northeast-1.console.aws.amazon.com_cloudformation_home_region=ap-northeast-1 (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317369/e72ac6fa-1be8-343b-40f9-fd0d04bd9ce2.png)

## 2.3. スタックの詳細を指定

`スタックの名前`を決めます。ここでは`training`としておきます。

`パラメータ`は本記事では使わないので、そのまま`次へ`を押します。

![ap-northeast-1.console.aws.amazon.com_cloudformation_home_region=ap-northeast-1 (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317369/be73d2fa-a3b0-08ae-3c06-45a4d42d9d13.png)

## 2.4. スタックオプションの設定/レビュー

続いて、スタックオプションの設定画面が表示されるので、そのまま`次へ`を押します。

最後に、レビュー画面(確認画面)が表示されるので、`スタックの作成` を押します。

## 2.5. 各リソースの作成状況の確認

`スタックの作成`を押すと、スタックの詳細画面のイベントタブが表示された状態になります。

画面下部のイベント欄に、リソースの作成状況が順次表示されていきます(丸矢印ボタンで更新できます)。

緑色の`CREATE_COMPLETE`が表示されれば、そのリソースは作成完了です。

最終的にスタック名(今回の場合であれば`training`)で、`CREATE_COMPLETE`が表示されれば全てのリソースの作成が正常に完了したことになります。

![ap-northeast-1.console.aws.amazon.com_cloudformation_home_region=ap-northeast-1 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317369/d7a9c745-2038-e79b-47fb-498e14b8dd07.png)

## 2.6. スタックの削除

スタック画面か、もしくはスタックの詳細画面画面上部の`削除`を押し、さらに確認のダイアログで表示された`スタックの削除`を押すと、このスタックに紐付く全てのリソースが一括削除されます。

# 3. テンプレートファイル作成の続き

CloudFormationでのリソースの作成方法はわかったので、テンプレートファイルの作成を再開します。

本記事では割愛しますが、この先でリソースを追記したテンプレートファイルは、追記の都度CloudFormationの画面でアップロードして、実際にリソースを作成できるか確認してみても良いかと思います。(リソースを作成した後は、適宜スタックごと削除してください)。

## 3.1. サブネットの定義

VPCの次は、サブネットを2つ定義します。

1つ目はパブリックサブネット、2つ目はプライベートサブネットとします。

```yaml:
# 最終行に以下を追加
  CfPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.5.0/24
      MapPublicIpOnLaunch: true
      VpcId: !Ref CfVPC
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: CfPublicSubnet
  CfPrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.10.0/24
      MapPublicIpOnLaunch: false
      VpcId: !Ref CfVPC
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: CfPrivateSubnet
```

### マネジメントコンソールの場合

1つ目のパブリックサブネットの内容については、マネジメントコンソールの各画面で以下を指定した場合と同等です(2つ目のプライベートサブネットについては割愛)。

#### サブネットの作成画面

|項目|設定内容|
|:--|:--|
|名前タグ|CfPublicSubnet
|VPC|CIDRが`10.0.0.0/16`である、作成済みのVPC
|アベイラビリティゾーン|ap-northeast-1a
|IPv4 CIDR ブロック|10.0.5.0/24

#### アクション > 自動割り当てIP設定

| 項目|設定内容|
|:--|:--|
|パブリックIPv4アドレスの自動割り当てを有効にする|チェックを入れる

### MapPublicIpOnLaunchについて

YAML内の`MapPublicIpOnLaunch`は、パブリックIPアドレスの自動割り当てに該当します。

>```yaml:
MapPublicIpOnLaunch: true
```

### !Refについて

YAML内の`!Ref`についてはCloudFormationで使用できる組み込み関数のひとつです。

>```yaml:
Resources:
  CfVPC: 
    # 略
  CfPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      # 略
      VpcId: !Ref CfVPC
```

`!Ref リソースの論理ID`とすることで、そのリソースの物理IDが返ります。 

マネジメントコンソール画面で順次リソースを作成していく場合と異なり、CloudFormationで一括してリソースを定義する場合には、このサブネットが属するVPCの物理IDがまだ決まっていませんので、このように`!Ref`関数を使います。

### !Refによるリソース作成順序への影響

また、`!Ref`を使用することで、参照先のリソースが先に作成されるようになります。

例えば、

>```yaml
Resources:
  CfPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      # 略
      VpcId: !Ref CfVPC
      # 略
  CfVPC:
    Type: AWS::EC2::VPC
    # 略
```

のようにテンプレートファイル中でのリソース定義順序が

1. サブネット
2. VPC

の順だったとしても、サブネットがVPCを`!Ref`で参照していると、リソースの作成順序は

1. VPC
2. サブネット

の順となります。

- [参考: Ref - AWSドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html)

## 3.2. インターネットゲートウェイの定義とVPCへのアタッチ

続いて、インターネットゲートウェイを定義し、VPCへアタッチします。

```yaml
# 最終行に以下を追加
  CfInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: CfInternetGateway
  CfAttachCfInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId : !Ref CfInternetGateway
      VpcId: !Ref CfVPC
```

- まず`Type`に`AWS::EC2::InternetGateway`を指定し、インターネットゲートウェイを定義します。

- 次に`Type`に`AWS::EC2::VPCGatewayAttachment`を指定し、インターネットゲートウェイをVPCにアタッチします。
  - `InternetGatewayId`と`VpcId`で、どのインターネットゲートウェイをどのVPCにアタッチするのか指定しています。

### マネジメントコンソールの場合

上記は、マネジメントコンソールにおいて、下記の指定でインターネットゲートウェイを作成するのと同等です。

#### インターネットゲートウェイの作成画面

|項目|設定内容|
|:--|:--|
|名前タグ|CfInternetGateway

#### アクション > VPCにアタッチ の画面

|項目|設定内容|
|:--|:--|
|VPC|CIDRが`10.0.0.0/16`である、作成済みのVPC


## 3.3. パブリックサブネット用ルートテーブルの定義およびルートの定義

```yaml
# 最終行に以下を追加
  CfRouteTableForPublicSubnet:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CfVPC
      Tags:
        - Key: Name
          Value: CfRouteTableForPublicSubnet
  CfRouteForPublicSubnet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref CfRouteTableForPublicSubnet
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref CfInternetGateway
  CfAssocciateRouteTableForPublicSubnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref CfRouteTableForPublicSubnet
      SubnetId: !Ref CfPublicSubnet
```

- まず`Type`に`AWS::EC2::RouteTable`を指定し、ルートテーブルを作成しています。

- 次に`Type`に`AWS::EC2::Route`を指定し、ルートを追加しています。
  - `DestinationCidrBlock`(送信先)を、`0.0.0.0/0`(フルオープン)としています。
  - ターゲット(`GatewayId`)に、定義済みのインターネットゲートウェイ(`!Ref CfInternetGateway`)を指定しています。

- 続いて`Type`に`AWS::EC2::SubnetRouteTableAssociation`を指定し、ルートテーブルをサブネットに関連付けしています。
  - `RouteTableId`と`SubnetId`で、どのルートテーブルをどのサブネットに関連付けするのか指定しています。

### マネジメントコンソールの場合

マネジメントコンソールの場合、下記の指定でルートテーブルを作成するのと同等です。

#### ルートテーブルの作成画面

|項目|設定内容|
|:--|:--|
|名前タグ|CfRouteTableForPublicSubnet
|VPC|CIDRが`10.0.0.0/16`である、作成済みのVPC

#### アクション > ルートの編集 > ルートの追加 の画面

|項目|設定内容|
|:--|:--|
|送信先|`0.0.0.0/0`
|ターゲット|名前タグ(Name)が`CfInternetGateway`であるインターネットゲートウェイ

#### アクション > サブネットの関連付けの編集 の画面

- 名前タグ(Name)が`CfPublicSubnet`であるサブネットを選択して、`保存`

## 3.4. パブリックサーバー用セキュリティグループの定義

```yaml
# 最終行に以下を追加
  CfSecurityGroupForPublicServer:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: CfSecurityGroupForPublicServer
      GroupDescription: CfSecurityGroupForPublicServer
      VpcId: !Ref CfVPC
      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
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: CfSecurityGroupForPublicServer
```

- セキュリティグループを作成するため、`Type`に`AWS::EC2::SecurityGroup`を指定します。

- インバウンドルールは、`SecurityGroupIngress`プロパティに指定します。
    - ここでは、SSH(22), HTTP(80), HTTPS(443)でのアクセスを許可しています。

### マネジメントコンソールの場合

マネジメントコンソールの場合、下記の指定でセキュリティグループを作成するのと同等です。

#### セキュリティグループの作成画面

|項目|設定内容|
|:--|:--|
|セキュリティグループ名|CfSecurityGroupForPublicServer
|説明|CfSecurityGroupForPublicServer
|VPC|CIDRが`10.0.0.0/16`である、作成済みのVPC

#### アクション > インバウンドのルールの編集画面

以下のルールを設定

|タイプ|プロトコル|ポート範囲|ソース
|:--|:--|:--|:--|
|SSH|TCP|22|0.0.0.0/0
|HTTP|TCP|80|0.0.0.0/0
|HTTPS|TCP|443|0.0.0.0/0

## 3.5. パブリックサーバーの定義

```yaml
# 最終行に以下を追加
  CfPublicServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0ff21806645c5e492
      InstanceType: t2.micro
      KeyName: cf-key-pair
#      SubnetId: !Ref CfPublicSubnet
#      SecurityGroupIds:
#        - !Ref CfSecurityGroupForPublicServer
      NetworkInterfaces:
        - SubnetId: !Ref CfPublicSubnet 
          GroupSet:
            - !Ref CfSecurityGroupForPublicServer
          AssociatePublicIpAddress: true
          DeviceIndex : 0
      Tags:
        - Key: Name
          Value: CfPublicServer
```

- EC2を作成するため、`Type`に`AWS::EC2::Instance`を指定します。

- `ImageId`でAMI(Amazonマシンイメージ)を指定します。
  - ここでは、`Amazon Linux 2(x86)`のAMIを指定しています。 
  - 具体的にどんなIDを指定するかについてですが、AMI IDはマネジメントコンソールのEC2作成画面の途中に表示されているので、今回は事前にそちらを確認して指定しています。

- `KeyName`にはキーペア名を指定します。ここでは事前にマネジメントコンソール画面で作成しておいたキーペア名を指定しています。
    - なお、キーペアは、マネジメントコンソールの`EC2 > キーペア > キーペアの作成`で作成できます。 

### 自動割り当てパブリックIPの設定を有効にする場合の注意点

>```yaml
#      SubnetId: !Ref CfPublicSubnet
#      SecurityGroupIds:
#        - !Ref CfSecurityGroupForPublicServer
```

`SubnetId`と`SecurityGroup`がコメントアウトしてありますが、自動割り当てパブリックIPを有効に**しない**のであれば、コメントアウト前の記述で特に問題ありません。

今回のように自動割り当てパブリックIPを有効にする場合は、以下のように`NetworkInterfaces`プロパティ内の`Subnet`プロパティと`GroupSet`プロパティに、サブネットとセキュリティグループそれぞれを記述する必要があります。

>```yaml
     NetworkInterfaces:
        - SubnetId: !Ref CfPublicSubnet 
          GroupSet:
            - !Ref CfSecurityGroupForPublicServer
          AssociatePublicIpAddress: true
          DeviceIndex : 0
```

もし、上記のように記述しなかった場合、以下のエラーになります。

>Network interfaces and an instance-level subnet ID/security groups may not be specified on the same request.(ネットワークインターフェイスとインスタンスレベルのサブネットID/セキュリティグループは、同じリクエストで指定できません)

自動割り当てパブリックIPの有効化そのものは`AssociatePublicIpAddress`を`true`とすることで設定できます。

また、`NetworkInterfaces`プロパティ内の配列の各要素(つまり個々の`NetworkInterface`)には、`DeviceIndex`プロパティが必須となっているので、これも指定しています。

なお、`DeviceIndex`が無いと、
>Property DeviceIndex cannot be empty

というエラーになります

- 
[参考: NetworkInterface - AWSドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html)

## 3.6. Elastic IPの定義

パブリックサーバーのパブリックIPアドレスについて、起動のたびに変更されることが無いよう、Elastic IPを定義し、パブリックサーバーに関連付けします。

```yaml
# 最終行に以下を追加
  CfElasticIpForPublicServer:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref CfPublicServer
```

- Elastic IPを作成するため、`Type`に`AWS::EC2::EIP`を指定します。

- このElastic IPをパブリックサーバーに関連付けするため、`InstanceId`にパブリックサーバーを指定します。

## 3.7. プライベートサーバー用セキュリティグループの定義

```yaml
# 最終行に以下を追加
  CfSecurityGroupForPrivateServer:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: CfSecurityGroupForPrivateServer
      GroupDescription: CfSecurityGroupForPrivateServer
      VpcId: !Ref CfVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref CfSecurityGroupForPublicServer
      Tags:
        - Key: Name
          Value: CfSecurityGroupForPrivateServer
```

- インバウンドルールを、`SecurityGroupIngress`プロパティに指定します。

  - パブリックサーバーからのみプライベートサーバーにアクセスできるよう、ソース(`SourceSecurityGroupId`)には、パブリックサーバー用のセキュリティグループを指定しています。

### マネジメントコンソールの場合

マネジメントコンソールの場合、下記の指定でインバウンドのルールを編集するのと同等です。

#### アクション > インバウンドのルールの編集画面

以下のルールを設定

|タイプ|プロトコル|ポート範囲|ソース
|:--|:--|:--|:--|
|SSH|TCP|22|パブリックサーバー用セキュリティグループのグループID

## 3.8. プライベートサーバーの定義

```yaml
# 最終行に以下を追加
  CfPrivateServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0ff21806645c5e492
      InstanceType: t2.micro
      KeyName: cf-key-pair
      SubnetId: !Ref CfPrivateSubnet
      SecurityGroupIds:
        - !Ref CfSecurityGroupForPrivateServer
      Tags:
        - Key: Name
          Value: CfPrivateServer
```

- `SubnetId`でプライベートサブネットを、`SecurityGroupIds`でプライベートサーバー用セキュリティグループを指定しています。

- パブリックサーバーの場合は、自動割り当てパブリックIPアドレスを有効にする関係で、サブネットとセキュリティグループを以下のように`NetworkInterfaces`プロパティの下に指定する必要がありましたが、今回はそのようにする必要はありません。

>```yaml
     NetworkInterfaces:
        - SubnetId: !Ref CfPublicSubnet 
          GroupSet:
            - !Ref CfSecurityGroupForPublicServer
          AssociatePublicIpAddress: true
          DeviceIndex : 0
```

# 4. もう一度テンプレートファイルを使ってAWSリソースを作成する

ここまで作成してきたテンプレートファイルを使って、実際にAWSリソースを作成してみます。

- なお、テンプレートファイル全体はこちら:point_down:
  - https://github.com/shonansurvivors/CloudFormation-training/blob/master/training.yml

CloudFormation上の操作の流れは、`2. テンプレートファイルを使って実際にAWSリソースを作成してみる`と同じです。

スタックの作成開始から完了までにかかる時間は、1分半ほど。
☕️でも飲んで待ちましょう。

# 最後に

以上で、本記事でのCloudFormationによるパブリックサーバー/プライベートサーバーの構築は完了です。

パラメータなど、CloudFormationの機能をまだまだ活用はできていないので、もし第二弾的な記事を書くことがあればその辺りにも触れたいと思います。

- [参考: パラメータ - AWSドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html)

また、TypeScriptやPythonのコードでCloudFormationのテンプレートファイルを生成できる、AWS CDKについても今後記事を書ければと思います。

# 参考

- [AWS CloudFormation ユーザーガイド](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html)

- [【CloudFormation入門1】5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築 - Developers.IO](https://dev.classmethod.jp/cloud/aws/cloudformation-beginner01/)

- [CloudFormationで自動割り当てパブリックIPを有効にしてインスタンスを構築する - かべぎわブログ](https://www.kabegiwablog.com/entry/2017/10/17/201613)

- [【AWS初心者向け】AWSでWebサーバーとデータベースを構築して、ブラウザにデータを表示する【ハンズオン】](https://qiita.com/MayForBlue/items/c96674fb6aac5852a978)