AWS CloudFormationでの試行錯誤を回避するためのメモ

CloudFormationは、ネットの解説記事を読むよりも、実際に自分でテンプレートを書いてみて試行錯誤を重ねたほうが理解が早い気がします（解説記事で完成形のテンプレートを見せられて心が折れた人は、僕も含めて少なくないのではないでしょうか）。　

ただ、テンプレートの記述に敷居の高さを感じている人は多いと思いますので、本記事では「はじめてCloudFormationを触る人がつまづきやすい点」をピンポイントに解説したいと思います。

なお、本記事ではテンプレートの記述にはYAML形式を採用しています。

#はじめての方へ
## CloudFormationへの慣れ方

これからCloudFormationに入門しようという時に、いきなりテンプレートの完成形を読んで理解するのは心理的なハードルが高いかと思います。

そこで最初は「VPCを作ること」を目標にして、VPCができたら次はサブネット、次はインターネットゲートウェイ、次はルートテーブルやセキュリティグループ…というように、ミニマムな目標からはじめてインクリメンタルに目標ラインを上げていくことで挫折を避けられるのではないかと勝手に思っています。

目安となるように、目標を三段階に設定してみました。

1. VPCだけ定義してみる。
2. サブネットの定義を追加してみる。組込み関数`!Ref`を使ってみる。
3. インターネットゲートウェイ、ルートテーブル、セキュリティグループ等を追加し、EC2インスタンスの足場を構築する。

ステップ3まで辿り着けば、あとは公式リファレンスを見ながら自走できるんじゃないかと思ってます。

…と偉そうに書いてますが、僕も今日はじめてCloudFormationを触って、1日かけてなんとかステップ3まで辿り着いた人間です（2020年5月31日記）。

## インデントについて
YAML形式では**タブインデントは使用できません**。スペースインデントのみです。VS Codeなどタブ入力をスペースに変換してくれるエディタで記述するのが賢明かもしれません。

## ミニマムなサンプル

はじめてテンプレートファイルを触る人のために、ミニマムなテンプレートのサンプルを載せておきます。

見ての通り、VPCだけ定義したサンプルです。YAML形式の読み方は、JSON形式と見比べればなんとなく理解できるかと思います。

```yaml:VPCのリソース定義（YAML形式）
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  MyVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.1.0.0/16
      Tags:
        - Key: Name
          Value: MyVpc
```

```json:VPCのリソース定義（JSON形式）
{
  "AWSTemplateFormatVersion": "2010-09-09",

  "Resources": {
    "MyVpc": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.1.0.0/16",
        "Tags": [
          { "Key": "Name", "Value": "MyVpc" }
        ]
      }
    }
  }
}
```

## リソース定義の記述パターン
リソースの定義は、以下のパターンで記述します。`Resources`セクション内に、リソースを列挙して記述します。
「リソースの論理名」など、日本語の部分はプレースホルダです。

```yaml:Resources
  Resources:
    リソースの論理名:
      Type: リソースタイプ
      Properties:
        リソースプロパティ名: 値
        リソースプロパティ名: 値

    リソースの論理名:
      Type: リソースタイプ
      Properties:
        リソースプロパティ名: 値
        リソースプロパティ名: 値

　　# （以下、繰り返し）
```

* 「リソースの論理名」とはリソースの識別子です（例「`MyVpc`」）。
* 「リソースタイプ」とはリソースの型のようなものです（例「`AWS::S3::EC2`」）。
* 「リソースプロパティ」とは、例えばVPCのCIDRブロックなどです。

# 試行錯誤を回避するためのメモ（共通編）
## 組込み関数「!Ref」について

[公式ドキュメントの解説](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html)にはこう書いてあります。

> 組み込み関数 Ref は、指定したパラメータまたはリソースの値を返します。
>
>  ・パラメータの論理名を指定すると、それはパラメータの値を返します。
>  ・リソースの論理名を指定すると、それはそのリソースを参照するために通常使用できる値を返します (物理 ID)。

箇条書きのうち前者はともかく、後者が何を言っているか。

>  ・リソースの論理名を指定すると、それはそのリソースを参照するために通常使用できる値を返します (物理 ID)。

これは例えば`!Ref`の引数にリソースの論理名を指定すると、`!Ref リソースの論理名`の部分が物理IDに展開される、ということを言っています。

```yaml:Ref（展開前）
VpcId: !Ref MyVpc
```

```yaml:Ref（展開後のイメージ）
VpcId: vpc-053ffba26196d40c0
```

* 「リソースの論理名」とは、先述のサンプルでいうと`MyVpc`にあたるものです。
* 「物理ID」が何を指すかはコンテキストに依存しますが、リソースがVPCの場合は`VPC ID`（マネージメントコンソールでいうと以下の赤枠の値）に展開されます。つまり、`vpc-053ffba26196d40c0`のようなIDに展開されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/93d85d96-ef44-e5d7-2290-34f028ea95c7.png)

物理IDはリソースを構築するまでどんなIDが採番されるか分からないですし、既存の物理IDをハードコードするのはあり得ないので、こういう組込み関数が存在するのですね。

# 試行錯誤を回避するためのメモ（VPC編）
## セキュリティグループのインバウンド・アウトバウンドに該当するプロパティは？
* インバウンド　…　`SecurityGroupIngress`
* アウトバウンド　…　`SecurityGroupEgress`

です。あまり聞きなれない用語ですね。


## セキュリティグループのルールで「すべてのトラフィック」を定義するには？

```yaml:SecurityGroup
  MySecurityGroup1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1
```

と記述してリソースを作成すると、マネージメントコンソール上のエントリの内容は以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/c700d141-eaa8-401e-b8e1-da50a8302a28.png)

## ソース or 送信先にセキュリティグループを指定するには？

```yaml:SecurityGroup
  MySecurityGroup2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref MySecurityGroup1
          IpProtocol: -1
```

マネージメントコンソール上のエントリの内容は以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/29994df5-8c94-64fb-005b-6f681f30a65f.png)

## FromPort、ToPortって？
`SecurityGroupEgress`、`SecurityGroupIngress`の`FromPort`、`ToPort`はポート番号を範囲指定するプロパティです。
ソースポートやデスティネーションポートの指定ではありません。

例えば「TCPの80番ポート」を指定する場合、以下のように`FromPort`と`ToPort`に同じポート番号（`80`）を指定します。

```yaml:SecurityGroup
MySecurityGroup1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
```

マネージメントコンソール上のエントリの内容は以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/636857bc-472e-5e12-1dd0-e87dca07689e.png)

## 自動生成されるDHCPオプションセットのリソース定義

VPC作成時に自動生成されるDHCPオプションセットですが、そのリソース定義は以下です。`DomainName`の値は`リージョン.compute.internal`になります。自動生成されるけど使用しない、でも`Name`タグだけはつけておきたい...という時に使えるサンプルです。

```yaml:DHCPOptions
  MyDhcpOptions:
    Type: AWS::EC2::DHCPOptions
    Properties:
      DomainName: ap-northeast-1.compute.internal
      DomainNameServers:
        - AmazonProvidedDNS

  MyVPCDHCPOptionsAssociation:
    Type: AWS::EC2::VPCDHCPOptionsAssociation
    Properties:
      DhcpOptionsId: !Ref MyDhcpOptions
      VpcId: !Ref MyVpc
```

# この先は？

本記事は育っていく記事なので、また気づきがあれば随時追加していきます。

# 参考ドキュメント
* [AWS CloudFormation テンプレートリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-reference.html)
* [AWS リソースおよびプロパティタイプのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)　…　テンプレートでリソースを定義する際は、このリファレンスからサンプルをコピペしてカスタマイズする、というのが常套ですかね。ドキュメントを読むついでに各プロパティにも目を通しておくと、AWS認定試験の勉強にもなりそうです。