AWS CloudFormationでEC2を構築

# はじめに
最近AWSを始めたので、勉強がてらCloudFormationを用いてEC2の構築を行いました。

# CloudFormationについて
CloudFormationはAWS内のインフラリソースを自動でプロビジョニングできるサービスです。
プロビジョニングしたいAWSリソースをコード(テンプレート)として記述し、目的のインフラ(スタック)を構築するというのがCloudFormationの流れになります。
(プロビジョニング : ネットワークやコンピューターリソースを準備すること)
参考:https://aws.amazon.com/jp/cloudformation/features/

# インフラ構成図
インフラ構成は非常に単純で下図にようになります。
VPCは東京リージョンに作成し、パブリックサブネットはap-northeast-1aに作成しています。
EC2へのアクセスは指定した固定IPからのSSH, HTTPアクセスのみ許可しています。
EC2から外部へのアクセスは全て許可しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380830/7eb5d714-1362-cf92-8bec-e8c31a175f95.png" width=50%>

# テンプレート記述の基本構文
テンプレートはJSONまたはYAML形式で記述することができ、今回はYAML形式で記述しています。
テンプレートの基本構文は以下のようになります。

```yaml
# テンプレート形式のバージョン
AWSTemplateFormatVersion:

# テンプレートに関する記述
Description:

# 入力パラメータ
Parameters: 

# 構築するリソースの記述
Resources:

# 出力パラメータ
Outputs:

```
### AWSTemplateFormatVersion
CloudFormationのテンプレート形式のバージョンで、2010-09-09が現時点での最新バージョンです。
2010-09-09が最新

### Description
テンプレートの内容に関する記述です。

### Parameters
記述したリソースに対し、パラメータを入力値として与えたい場合に記述します。
ここに記述したパラメータはテンプレートからスタックを作成する際に入力値として与えることになります。

### Resources
構築したいAWSのリソースを記述します。

### Outputs
ここに記述したパラメータはスタックの作成が終わったときに出力値として得られます。

# リソース記述の基本構文
```yaml
# リソースの論理名
(ResourceName):

  # リソースタイプ
  Type:

  # リソースのプロパティ
  Properties:
```
### リソースの論理名
テンプレート内でのリソース名を記述します。

### Type
リソースのタイプを指定します。
リソースタイプの参考:
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

### Properties
リソースのプロパティを指定します。

# テンプレート解説
```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Provision EC2
```
テンプレートファイルの始めに形式のバージョンとテンプレート内容を記述しています。

```yaml
Parameters: 
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
  
  MyIP:
    Description: IP address allowed to access EC2
    Type: String
```
次に、EC2へのアクセスのキーペア名とアクセス可能なIPアドレスを入力パラメータとして設定しています。このように記述することで、テンプレートファイル内にキーペア名、IPアドレスを直接書く必要がなくなります。

```yaml
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        - Key: Name
          Value: vpc-cf

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: igw-cf

  # IGWをVPCにアタッチ
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW
```
ここからResourcesの記述になります。
まず記述内容は順にVPCの作成、インターネットゲートウェイ(IGW)の作成、IGWをVPCにアタッチとなります。
リソースのTags属性でNameタグを作成しています。
またAttachGatewayのプロパティVpcId, InternetGatewayIdにはそれぞれVPC, IGWのIDを指定する必要があるのですが、テンプレートの組み込み関数Refを用いることでリソースの論理名からIDを取得しています。
!Refは組み込み関数Refの短縮表記です。
組み込み関数Refについて:
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html

```yaml
  PubSub:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      Tags:
        - Key: Name
          Value: pub-sub-a-cf

  PubSubRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: pub-sub-a-rt-cf

  # PubSub-インターネット間のルーティング
  PubSubToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PubSubRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW
  
  # ルートテーブルをサブネットに関連付け
  AssoPubSubRT:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PubSub
      RouteTableId: !Ref PubSubRT
```
次はパブリックサブネットの作成です。
1番目のリソースで先ほど作成したVPC内にサブネットを作成しています。
2番目以降のリソースではそれぞれ、ルーティングテーブルの作成、パブリックサブネット-インターネット間のルートの作成、ルートテーブルのパブリックサブネットへの関連付けを行っています。

```yaml
  EC2: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-00d101850e971728d
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces: 
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PubSub
          GroupSet:
            - !Ref EC2SG
      UserData: !Base64 |
        #!/bin/bash
        sudo yum install -y git
      Tags:
          - Key: Name
            Value: ec2-a-cf
```
次にEC2インスタンスの作成です。
EC2のAMIにはAmazon linux2を選択しています。今回は東京リージョンを選択していますが、リージョンが変わるとAMIのIDも変わるので、適宜変更する必要があります。テンプレート構文のMappingを使うことでリージョンについて条件判定を行った上で適切なAMIのIDを記述できるようです。
Mappingの利用:
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/gettingstarted.templatebasics.html
(ページ中程)

EC2にはパブリックIPを自動で割り振ります。
その場合ネットワークインターフェースはデバイスインデックスが0である必要があるのでNetworkInterfaces下の設定を行っています。
EC2のパブリックIP自動割り振りについて：
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html

UserDataに記述した内容はインスタンスの作成時に実行されるので、プログラムのインストールなど初期設定を行うことができます。
何もしないのは寂しいのでgitをインストールしています。
UserData属性にはBase64エンコードされたテキストを渡す必要があるので、組み込み関数!Base64を使用しています。
(!Base64は入力文字列のBase64表現を返します。)


```yaml
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: ec2-sg-cf
      GroupDescription: Allow SSH and HTTP access only MyIP
      VpcId: !Ref VPC
      SecurityGroupIngress:
        # http
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref MyIP
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref MyIP
```
次にEC2インスタンスのセキュリティーブループを記述しています。
自分のIPアドレスからのみHTTP, SSH接続を許可しています。

```yaml
Outputs:
  EC2PublicIP:
    Value: !GetAtt EC2.PublicIp
    Description: Public IP of EC2 instance
```
最後に出力パラメータを記述しています。

# スタックの作成
AWSにログインし、サービスメニューからCloudFormationのページに移ります。
次にCloudFormationページ内の"スタックの作成"ボタンをクリックすると次のような画面に移ります。
<img width="1440" alt="スクリーンショット 2019-05-26 19.00.46ーテンプ選択.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380830/349d2eb7-0e0b-bfb9-8b1c-ece95ff90c40.png">
テンプレートの選択項目のテンプレートをAmazon S3にアップロードを選択し、テンプレートファイルを選択して次へをクリックします。

<img width="1440" alt="スクリーンショット 2019-05-26 19.00.46ーテ<img width="1440" alt="スクリーンショット 2019-05-26 19.00.59ー詳細指定.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380830/0efa3921-2358-9051-65d0-29697bf9fe1d.png">
詳細指定の画面に移ります。テンプレートファイルのParametersに記述したパラメータをここで指定することができます。
次のページはタグやアクセス権限のページです。必要ならば入力して次の確認ページに進み、問題がなければ作成ボタンをクリックします。

<img width="1440" alt="スクリーンショット 2019-05-26 19.11.03ー作成済み.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380830/d88c8415-efed-d7a5-57e2-e045f439e94a.png">
スタックの作成画面に移ります。作成に多少時間がかかりますが、正常に完了すると状態の項目にCREATE_COMPLETEと表示されます。
また、下の出力タブ内にテンプレートのOutputsに記述したEC2のパブリックIPが表示されていることがわかります。
指定したIPでSSHで接続を行うことで設定どおりスタックが作成されていることが確認できます。

# 終わりに
- テンプレートファイルのParametersやOutputsで入力パラメータや出力パラメータを設定できるのは非常に便利でした。いろいろと有効活用ができそう。
- サブネットのルーティングの設定の部分はややコードが煩雑になってしまったので、もっと良い記述方法を探していきたいと思います。
- 構築したスタック内のリソースを一括で削除できるのはすごく楽です。

# 参考
AWS CloudFormation公式ドキュメント
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html