AWS CloudFormationでNAT構成を構築

AWS CloudFormationでNATインスタンスを含む構成を構築しました。
CloudFormationを使うと複数のリソースをコマンド1つで構築・破棄できて大変便利です。

# AWS CloudFormationとは
公式から分かりやすい図が公開されています。
YAMLかJSONで記述したテンプレートから、VPCやインスタンス等の様々なリソースをまとめて作成できます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/7149d6f4-ac21-c714-4d0d-dbac0659dfa0.png">
[AWS CloudFormation （設定管理とオーケストレーション） | AWS](https://aws.amazon.com/jp/cloudformation/)

1. YAMLかJSONにテンプレートを記述する
2. テンプレートをS3にアップロードする
3. テンプレートを実行する
4. スタックと呼ばれるリソースの集合がテンプレート通りに作られる
5. スタックを削除すればスタック内の全リソースが削除される

# 目指す構成
以前、マネージメントコンソールから構築したものと同じ構成です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/3cabb349-7f71-73c3-d858-6476fbdae29c.png" width=80%>

- 踏み台サーバー1台
- NATインスタンス1台
- 開発用サーバー1台

[AWSでNATインスタンス構築](https://qiita.com/predora005/items/81b93d4899bea1b47aca)

# 手順
**※ テンプレートファイルの設定を全て書くと長くなるので詳細は割愛します。**

## [1] デザイナーでテンプレート作成
CloudFormationを使うのは初めてということで、序盤はデザイナーを使ってテンプレートを作成しました。
デザイナーを使うと図面を作るような感覚でテンプレートを作成できます。
慣れたらデザイナーを使わずに済みますが、初めはデザイナーを使うと簡単かつ慣れたので使ってよかったです。

### [1-1] デザイナー画面を開く
[テンプレートのデザイン]を押すと、以下のような画面が開きます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/c4c2f317-c2e4-58c8-7d1d-5d63a011aeb7.png" width=80%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/0902a7a0-2b86-3696-59f7-0cbd91b24519.png" width=80%>

### [1-2] 部品を置く
左のメニューから部品を探しドラッグ&ドロップすると、置いた部品のテンプレートが自動で記述されます。
JSONとYAML形式のうち「YAML形式」を今回は使います。
ラジオボタンの選択でJSONとYAMLの変換はいつでも行えます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/482d280e-060e-ca41-0559-b314aa205495.png" width=80%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/27225b28-ccd0-70a0-4e6a-da32f20aef99.png" width=80%>

まずは、VPC, Subnet, RouteTable, InternetGatewayを配置しました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/a9b66748-aa98-ca57-a9a0-4bd255c28c58.png" width=80%>

### [1-3] 部品をつなげる
部品同士の関連を記述していきます。
このインターネットゲートウェイはこのVPCに属している、このサブネットはこのルートテーブルと関連付けしている、等です。
部品外枠のマルをドラッグして、繋げたい部品にドロップします。
以下は、インターネットゲートウェイとVPCをつなぐ例です。
"VPCGatewayAttachment"という要素がテンプレートに追加されました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/ee02d5ea-e164-373d-2e47-1779fa28ead7.png" width=80%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/51ad3e4c-7b83-57db-27d8-aa3c2b795d09.png" width=80%>

### [1-4] テンプレートの書き方
Resources直下の名称(下記ではNatVpc)は論理IDで、テンプレート内でユニークな必要があります。
他の部品との依存関係の記述などで使用されています。
半角英数字ならば、自由に変更できます。
デザイナーで変更すると、依存関係等の部分も自動で変更してくれます。

Propertiesに何を書けばよいかは、各部品の説明ページを見ると、ある程度分かります。
部品を右クリックしてハテナマークを選択すると、その部品の説明ページに飛びます。
VPCには、CIDRブロックと名称を追記しました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/21d18aa1-d292-be45-5eaa-e16cbfef71fd.png" width=80%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/71cd708c-8423-3e20-7d42-817805973659.png" width=80%>

### [1-5] 保存
左上のファイルメニューの[保存]を選択します。
ローカルとS3、どちらのも保存できます。
S3に保存すると、自動でバケットが作成されバケット内にテンプレートが保存されます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/d43f9671-ca8d-56ba-91d5-2203d5d08989.png" width=80%>


## [2] お試しでスタック作成
インスタンスやストレージはありませんが、この状態でスタックを作成してみます。
万が一失敗したりセキュリティが甘くても、惨事にならずに済むので、ひとまずこの状態で作成して問題がないか確認していきます。
### [2-1] 作成
[新しいスタックの作成]を選択します。
テンプレートの選択画面で先程保存したテンプレートのURLを入力します。
以降は特別な入力はなく作成まで行います。
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/f6a671e9-2b61-ef8c-4603-4562a8e7f1cc.png" width=80%>
<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/1e6d2c07-591d-a608-d187-c1a699d6c33d.png" width=80%>

### [2-2] 作成結果の確認
スタックを作成すると、テンプレートに記述したリソースが順次作られていきます。
失敗すると以下のように失敗の理由が表示されます。
失敗までに作成したリソースはロールバックされ削除されます。
![03.作成失敗.png](https://qiita-image-store.s3.amazonaws.com/0/345798/94d0035e-ec68-56df-7282-1686aefe2fbe.png)

成功すると、状況欄に"CREATE_COMPLETE"が表示されます。
マネージメントコンソールから確認すると、テンプレート通りに作成されていました。

## [3] デザイナー使用を諦める
ルートテーブルの設定やネットワークACL、セキュリティグループの追加していきますが、ここからはテンプレートファイルを直接編集しました。
デザイナーで追加しようとしましが、図面がゴチャゴチャして大変なことになりました。

## [4] テンプレートを直接編集する
公式のリファレンスをみながら編集していきました。
数値や名称は例ですので、状況に応じて変更可能です。

[Amazon EC2 リソースタイプのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-reference-ec2.html)
### [4-1] インターネットゲートウェイ
"InternetGateway", "VPCGatewayAttachment"を追加します。
ゲートウェイを追加するだけでなくアタッチメントも追加が必要です。
他の項目でも同様に、部品と部品をつなぐ部品が存在します。

```yaml:インターネットゲートウェイ
  NatIGW:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: nat-igw
```
```yaml:アタッチメント
  NatIGW2NatVpc:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      InternetGatewayId: !Ref NatIGW
      VpcId: !Ref NatVpc
```
 "!Ref"の後ろに論理IDを記述して別の部品を参照します。
VPCとインターネットゲートウェイを参照しています。

### [4-2] サブネット
サブネットにCIDRと名称を追加します。

```yaml:サブネット
  EntranceSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref NatVpc
      CidrBlock: 10.0.0.0/24
      Tags:
        - Key: Name
          Value: entrance-sb
```

### [4-3] ルートテーブル
ルートテーブルに名称を追加します。

```yaml:ルートテーブル
  EntranceRT:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref NatVpc
      Tags:
        - Key: Name
          Value: entrance-rt
```

"SubnetRouteTableAssociation"で、ルートテーブルとサブネットを接続します。

```yaml:ルートテーブルとサブネットを接続
  EntranceRT2EntranceSubnet:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref EntranceSubnet
      RouteTableId: !Ref EntranceRT
```
"Route"を使用して、ルートテーブルとインターネットゲートウェイを接続します。

```yaml:ルートテーブルとインターネットゲートウェイを接続
  EntranceRoute:
    Type: 'AWS::EC2::Route'
    Properties:
      GatewayId: !Ref NatIGW
      RouteTableId: !Ref EntranceRT
      DestinationCidrBlock: 0.0.0.0/0
```

### [4-4] ネットワークACL
ネットワークACLを作成します。

```yaml:ネットワークACL
  PublicNetworkAcl:
    Type: 'AWS::EC2::NetworkAcl'
    Properties:
      VpcId: !Ref NatVpc
      Tags:
        - Key: Name
          Value: public-nacl
```

次に、ネットワークACLのルール(InBound, OutBound)を作成します。
以下は、SSHのInboundと、ICMPのOutboundの例です。

```yaml:ネットワークACLのルール(SSHのInbound)
  InSshAclPublic:
    Type: 'AWS::EC2::NetworkAclEntry'
    Properties:
      NetworkAclId: !Ref PublicNetworkAcl
      RuleNumber: 100
      Protocol: 6
      PortRange:
        From: 22
        To: 22
      CidrBlock: 0.0.0.0/0
      Egress: false
      RuleAction: allow
```
```yaml:ネットワークACLのルール(ICMPのOutbound)
  OutIcmpAclPublic:
    Type: 'AWS::EC2::NetworkAclEntry'
    Properties:
      NetworkAclId: !Ref PublicNetworkAcl
      RuleNumber: 110
      Protocol: 1
      Icmp:
        Code: -1
        Type: -1
      CidrBlock: 0.0.0.0/0
      Egress: true
      RuleAction: allow
```

ネットワークACLとサブネットを関連付けます。

```yaml:ネットワークACLとサブネットを関連付け
  PublicNetworkAcl2EntranceSubnet:
    Type: "AWS::EC2::SubnetNetworkAclAssociation"
    Properties:
      NetworkAclId: !Ref PublicNetworkAcl
      SubnetId: !Ref EntranceSubnet
```

### [4-5] セキュリティグループ
セキュリテイグループを作成します。
ネットワークACLとは異なり、セキュリティグループのプロパティにルールを記述します。

```yaml:セキュリティグループ
  SpringboardSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupName: 'Springboard Security Group'
      GroupDescription: 'Springboard Security Group.'
      VpcId: !Ref NatVpc
      Tags:
        - Key: Name
          Value: springboard-sg
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: icmp
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 10.0.0.0/16
        - IpProtocol: icmp
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
```

### [4-6] インスタンス
最後にインスタンスを作成します。

```yaml:インスタンス
  SpringboardInstance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-0d7ed3ddb85b521a6  # Amazon Linux 2 AMI (HVM), SSD Volume Type
      InstanceType: t3.nano
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          GroupSet: 
            - Ref: SpringboardSecurityGroup
          SubnetId: 
            Ref: EntranceSubnet
          PrivateIpAddress: 10.0.0.10/24
      CreditSpecification: 
        CPUCredits: standard
      KeyName: springboard-keypair
      Tags: 
        - Key: Application
          Value: String
        - Key: Name
          Value: springboard-inst
```

**※ キーペアは予め作成しておく必要があります。**
作成済みのキーペア名を"KeyName"に設定します。

## [5] パラメータの追加
テンプレートファイルに"Parameters"を追加します。
これまで設定してきたのは"Resources"です。

"Parameters"はデフォルト値有りの引数のように使うことができ、スタック作成時に指定することが可能です。
テンプレートファイル内で"!Ref"を使って参照できます。

以下では、インスタンスのIPアドレス、インスタンスタイプ、キーペア名を定義しています。
書き方は[公式のサンプル](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/sample-templates-services-us-west-2.html)を参考にしました。

```yaml:パラメータ
  SpringboardInstanceIpAddress:
    Description: 'Springboard Instance IP Address.'
    Type: String
    Default: '10.1.0.10'
  SpringboardInstanceType:
    Description : 'Springboard Instance Type'
    Type: String
    Default: t3.nano
    ConstraintDescription : 'must be a valid EC2 instance type.'
  SpringboardKeyName:
    Description : 'Name of an existing EC2 KeyPair to enable SSH access to the instance'
    Type: AWS::EC2::KeyPair::KeyName
    Default: springboard-keypair
    ConstraintDescription : 'Can contain only ASCII characters.'
```

## [6] スタックの作成・削除
## [6-1]スタックの作成
2と同様の手順で作成します。
2とは異なり、5で追加したパラメータが設定できるようになっています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/3ca7b385-3779-f036-f4cd-97b02ee0bc65.png" width=80%>

## [6-2]スタックの削除
マネージメントコンソールより削除します。
削除すると、スタック内の全てのリソースが削除されます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/345798/e320f776-344f-0029-94d9-6401d7a757cf.png" width=80%>

# 終わりに
これでNAT構成の構築・破棄がいつでも簡単に行えるようになりました。
これだけでも十分に便利ですが、まだまだ使っていない機能が沢山あるので、どんどん使っていこうと思います。
以下、今回やらなかったこと。

- Availability Zoneの設定
- IAMの設定
- テンプレートファイルの分割
- 組み込み関数の使用
- ChangeSetの使用
- CLIから操作する

# 参考資料
[AWS CloudFormation （設定管理とオーケストレーション） | AWS](https://aws.amazon.com/jp/cloudformation/)
[AWS Black Belt Online Seminar 2016 AWS CloudFormation](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2016-aws-cloudformation)
[【CloudFormation入門1】5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築](https://dev.classmethod.jp/cloud/aws/cloudformation-beginner01/)
[How to create a functional VPC using CloudFormation](https://sookocheff.com/post/aws/how-to-create-a-vpc-using-cloudformation/)