CloudFormationでEC2を構築してみた（※サンプルコード記載）

# はじめに
AWS CloudFormationでEC2を構築しました。
そこで学んだことを、記事にしています。

## 背景
現在参画している案件のシステムが重く、会社から付与していただいてるPCでは起動できず困っていました。（M1原因の可能性もあると思っています・・・）

そのため、なんとか手元で動かせるようにEC2を構築する必要がありました。
また、案件に参画しているメンバー全員が構築できる必要もあり、コードで管理できるCloudFormationで実装いたしました。

## CloudFormationとは？
**CloudFormation**とは、AWSのリソースをテンプレートで管理できるAWSが提供するサービスです。
テンプレートを作成すれば、そこに記述されているAWSリソースを設定してくれます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html

# 作成手順
:::note warn
事前に、EC2インスタンスに使用するキーペアを作成してください！
既に作成済みや既存のキーペアを使用する場合は、スルーで問題ありません。
:::

<details><summary>まだの方は、以下を確認してください。</summary>

1\. EC2コンソールを開く
2\. ナビゲーションペインで、**キーペア** を選択する
3\. **キーペアの作成**を選択する
4\. 以下を参照に項目の入力をする
- 名前：任意
- キーペアのタイプ：RSA
- プライベートキーファイル形式：.pem

![スクリーンショット 2022-09-20 16.49.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/e90328ee-332b-5757-cce5-d997e3a16eb0.png)


5\. **キーペアを作成**を押すと、作成される

:::note alert
作成時にキーがダウンロードされるので、無くさないようにしましょう！
:::

</details>

## 0. 全体のコード
CloudFormationテンプレートは`JSON`または`YAML`形式のテキストファイルです。
そのため、今回は`yamlファイル`で作成して以下に記載いたしました。（コードが多少長いため、折りたたみにしています。）

<details><summary>サンプルコード</summary>

```yaml:qiita-sample.yaml
Parameters:
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
    Default: xxxxxx # 作成するEC2に紐づくキーを記載する

  MyIP:
    Description: IP address allowed to access EC2
    Type: String
    Default: xxxxxx # IPアドレスを記載する

Resources:
  SecGrp:
    Type: AWS::EC2::SecurityGroup # セキュリティグループを作成するタイプを指定【AWS::EC2::SecurityGroup】
    Properties:
      GroupName: ec2-sg
      GroupDescription: Allow SSH and HTTP access only MyIP
      VpcId: vpc-00000000 # 使用するVPCのIDを記載する
      SecurityGroupIngress: # インバウンドルールの設定
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref MyIP

        - IpProtocol: tcp
          FromPort: 3000
          ToPort: 3000
          CidrIp: !Ref MyIP

  SampleEC2:
    Type: AWS::EC2::Instance # インスタンスを作成するタイプを指定【AWS::EC2::Instance】
    Properties:
      Tags:
        - Key: Name
          Value: "sample-ec2" # 表示する名前を記載する
      ImageId: ami-00000000000000000 # 使用するAMI IDを記載する
      KeyName: !Ref KeyName
      InstanceType: t3.xlarge # インスタンスタイプを指定
      InstanceInitiatedShutdownBehavior: stop
      NetworkInterfaces: # インターフェースの設定
        - AssociatePublicIpAddress: "true" # 自動割り当てパブリックIPの設定。trueで有効可。
          DeviceIndex: "0" # パブリックIPを自動で割り振り
          GroupSet:
            - !Ref SecGrp # 上のセキュリティグループを指定
      BlockDeviceMappings: # ストレージの設定
        - DeviceName: /dev/xvda # デバイス名
          Ebs:
            VolumeType: gp2 # ボリュームタイプ
            DeleteOnTermination: true # インスタンス終了時に削除するのか
            VolumeSize: 50 # ディスクサイズ（GiB）
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo yum update -y
          sudo yum install -y git

  ElaServer:
    Type: AWS::EC2::EIP # Elastic IP アドレスを作成するタイプを指定【AWS::EC2::EIP】
    Properties:
      InstanceId: !Ref SampleEC2 #【!Ref】で割り当てるインスタンス指定

```
</details>

今回のコードで作成するリソースは以下になります。
- セキュリティグループ
- EC2インスタンス
- ElasticIPアドレス

## 1. Parameters
```yaml
Parameters:
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
    Default: xxxxxx # 作成するEC2に紐づくキーを記載する

  MyIP:
    Description: IP address allowed to access EC2
    Type: String
    Default: xxxxxx # IPアドレスを記載する
```
**Parameters**とは、スタックを作成または更新するたびにテンプレートにカスタム値を入力できます。
今回で言うと、`KeyName`と`MyIP`というパラメーターで宣言しています。（この名前は自由に設定できます！）
- KeyName：EC2に紐づくキーペアを設定
- MyIP：IPアドレスを設定

宣言することによって、`!Ref MyIP`のように記述を省略することができます。（この後に出てきます。）
各プロパティに関しては、以下にまとめました。
- Description：パラメーターについて説明する最大 4000 文字の文字列。
- Type：パラメーターのデータ型。
- Default：スタックの作成時に値を指定しなかった場合に、テンプレートで使用される適切な型の値。
    - <font color="red">デフォルトを設定する場合は、必ず値を記載すること。</font>

:::note info
IPアドレスなど、コード上で何度も使用するものに関して設定しておくと、記載が省略できるので個人的には大きいメリットかと思います！
:::

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

## 2. Resources
```yaml
Resources:
  SecGrp:
    Type: AWS::EC2::SecurityGroup # セキュリティグループを作成するタイプを指定【AWS::EC2::SecurityGroup】
    Properties:
      GroupName: ec2-sg
      GroupDescription: Allow SSH and HTTP access only MyIP
      VpcId: vpc-00000000 # 使用するVPCのIDを記載する
      SecurityGroupIngress:
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref MyIP

        - IpProtocol: tcp
          FromPort: 3000
          ToPort: 3000
          CidrIp: !Ref MyIP

  SampleEC2:
    Type: AWS::EC2::Instance # インスタンスを作成するタイプを指定【AWS::EC2::Instance】
    Properties:
      Tags:
        - Key: Name
          Value: "sample-ec2" # 表示する名前を記載する
      ImageId: ami-00000000000000000 # 使用するAMI IDを記載する
      KeyName: !Ref KeyName
      InstanceType: t2.micro # インスタンスタイプを指定
      InstanceInitiatedShutdownBehavior: stop
      NetworkInterfaces: # インターフェースの設定
        - AssociatePublicIpAddress: "true" # 自動割り当てパブリックIPの設定。trueで有効可。
          DeviceIndex: "0" # パブリックIPを自動で割り振り
          GroupSet:
            - !Ref SecGrp # 上のセキュリティグループを指定
      BlockDeviceMappings: # ストレージの設定
        - DeviceName: /dev/xvda # デバイス名
          Ebs:
            VolumeType: gp2 # ボリュームタイプ
            DeleteOnTermination: true # インスタンス終了時に削除するのか
            VolumeSize: 50 # ディスクサイズ（GiB）
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo yum update -y
          sudo yum install -y git

  ElaServer:
    Type: AWS::EC2::EIP # Elastic IP アドレスを作成するタイプを指定【AWS::EC2::EIP】
    Properties:
      InstanceId: !Ref SampleEC2 #【!Ref】で割り当てるインスタンス指定
```
**Resources**とは、スタックに含める Amazon EC2 インスタンスや Amazon S3 バケットなどの AWS リソースを宣言します。
今回は先ほども記載しましたが、以下のリソースを作成します。
- セキュリティグループ
- EC2インスタンス
- ElasticIPアドレス

そのため、`SecGrp`・`SampleEC2`・`ElaServer`という名前で指定しています。（ここも自由に設定できます。）

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

### セキュリティグループ
```yaml
SecGrp:
  Type: AWS::EC2::SecurityGroup # セキュリティグループを作成するタイプを指定【AWS::EC2::SecurityGroup】
  Properties:
    GroupName: ec2-sg
    GroupDescription: Allow SSH and HTTP access only MyIP
    VpcId: vpc-00000000 # 使用するVPCのIDを記載する
    SecurityGroupIngress: # インバウンドルールの設定
      # ssh
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: !Ref MyIP
        
      - IpProtocol: tcp
        FromPort: 3000
        ToPort: 3000
        CidrIp: !Ref MyIP
```
**今回のセキュリティグループ**では、ポート22と3000で接続できるように設定しています。
ポートに関しては、各自必要な数だけ設定してください。

各プロパティに関しては、以下にまとめました。
- GroupName：セキュリティグループの名前。
- GroupDescription：セキュリティグループの説明。
- VpcId：使用するVPCのID。

:::note warn
今回はVPCを作成していないので、以下3パターンの中から使用してください！
- 新しく作成する
- 既存のVPC
- デフォルトのVPC

個人的には、デフォルトのVPCで十分かと思っています。
（VPCは、各リージョンにデフォルトで1つは必ずあります。）
:::

<details><summary>VpcIdの探し方</summary>

1\. VPCコンソールを開く
2\. ナビゲーションペインで、**お使いのVPC** を選択する
3\. 画面上に記載がある`vpc-xxx...`が**VPC ID**となる

![スクリーンショット 2022-09-20 18.32.00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/3ff66a71-9ed6-38bc-8d9f-de75630dedf2.jpeg)

</details>

- SecurityGroupIngress：インバウンドルールの設定。
    - IpProtocol：IPプロトコル名（tcp、udp、icmp、icmpv6）または番号。
    - FromPort：ポート範囲の開始番号。
    - ToPort：ポート範囲の終了番号。
    - CidrIp：IPv4 アドレス範囲。
        - 今回は、自身が使用しているIPアドレスのみしか通信できないようにしている。
        - IPアドレスの調べ方は、[こちら](https://www.cman.jp/network/support/go_access.cgi)。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html

#### Ref
ここで先ほどパラメーターで設定した`MyIP`を、以下のように`!Ref MyIP`とすることで、指定した値を返すことができます。
```yaml
CidrIp: !Ref MyIP
```
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html

### EC2インスタンス
```yaml
SampleEC2:
  Type: AWS::EC2::Instance # インスタンスを作成するタイプを指定【AWS::EC2::Instance】
  Properties:
    Tags:
      - Key: Name
        Value: "sample-ec2" # 表示する名前を記載する
    ImageId: ami-00000000000000000 # 使用するAMI IDを記載する
    KeyName: !Ref KeyName
    InstanceType: t3.xlarge # インスタンスタイプを指定
    InstanceInitiatedShutdownBehavior: stop
    NetworkInterfaces: # インターフェースの設定
      - AssociatePublicIpAddress: "true" # 自動割り当てパブリックIPの設定。trueで有効可。
        DeviceIndex: "0" # パブリックIPを自動で割り振り
        GroupSet:
          - !Ref SecGrp # 上のセキュリティグループを指定
    BlockDeviceMappings: # ストレージの設定
      - DeviceName: /dev/xvda # デバイス名
        Ebs:
          VolumeType: gp2 # ボリュームタイプ
          DeleteOnTermination: true # インスタンス終了時に削除するのか
          VolumeSize: 50 # ディスクサイズ（GiB）
    UserData:
      Fn::Base64: |
        #!/bin/bash
        sudo yum update -y
        sudo yum install -y git
```
**今回のEC2インスタンス**では、t3.xlargeで、EBSのボリュームを50GiBとかなりサイズを大きくして作成しています。

:::note warn
このままで作成すると**有料**となってしまうので、インスタンスタイプなど無料枠なのかどうか、しっかり確認しましょう！
:::

各プロパティに関しては、以下にまとめました。

- Tags：インスタンスに追加するタグ。
    - `Key`を`Name`とすることで、コンソール上に表示する名前を指定することができる。
- ImageId：AMI の ID。

:::note warn
個人的には、このAMI IDを指定するのに苦労しました・・・。
今回は無料枠でもある、`Amazon Linux 2 AMI`を指定しました。
:::

<details><summary>AMI IDの探し方（Amazon Linux 2 AMI の場合）</summary>

1\. EC2コンソールを開く
2\. ナビゲーションペインで、**インスタンス** を選択する
3\. **インスタンスを起動**を選択する
4\. **アプリケーションおよび OS イメージ (Amazon マシンイメージ)**より、表示されているのが**AMI ID**となる

![スクリーンショット 2022-09-20 19.21.16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/6f9199a2-2046-466d-0a3a-be707d22bcaf.jpeg)

</details>

- KeyName：キーペアの名前。
    - 今回は`Parameters`で定義しているため、`!Ref KeyName`としている。
- InstanceType：インスタンスのタイプ。
- InstanceInitiatedShutdownBehavior：インスタンスからシャットダウンを開始したときに、インスタンスが停止または終了するかどうかを示す。
- NetworkInterfaces：インスタンスに関連付けるネットワーク インターフェイス。
    - AssociatePublicIpAddress：インスタンスにパブリック IPv4 アドレスを割り当てるかどうかを示す。
    - DeviceIndex：接続順序におけるネットワークインターフェイスの位置。
    - GroupSet：セキュリティグループの ID。
        - 今回は`SecGrp`という名前で作成済みなので、`!Ref SecGrp`で指定している。
- BlockDeviceMappings：起動時にインスタンスに接続するブロックデバイスを定義。
    - DeviceName：デバイス名。
    - Ebs：インスタンスの起動時に EBSボリュームを自動的にセットアップするために使用されるパラメーター。
        - VolumeType：ボリュームタイプ。
        - DeleteOnTermination：インスタンスの終了時に EBSボリュームが削除されるかどうかを示す。
        - VolumeSize：ボリュームのサイズ (GiB 単位)。
- UserData：インスタンスで使用できるようにするユーザーデータスクリプト。
    - インスタンス起動時に、記載のあるコマンドを実行してくれる。
    - 複数のコマンドを実行したい場合は、改行して記載する。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html

### ElasticIPアドレス
```yaml
ElaServer:
  Type: AWS::EC2::EIP # Elastic IP アドレスを作成するタイプを指定【AWS::EC2::EIP】
  Properties:
    InstanceId: !Ref SampleEC2 #【!Ref】で割り当てるインスタンス指定
```
今回は、先ほど作成したインスタンスにElasticIPアドレスを付与しています。
各プロパティに関しては、以下にまとめました。

- InstanceId：インスタンスのID。
    - 今回は`SampleEC2`という名前で作成済みなので、`!Ref SampleEC2`で指定している。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html

# AWS上の操作
これでコードは実装できたので、実際にCloudFormationを利用して、AWSリソースを作成していきます。

## 0. CloudFormationを検索
「CloudFormation」と検索して、CloudFormationコンソールを開く。

![スクリーンショット 2022-09-20 21.09.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/5bfe2daa-4104-8e88-81a9-6154fe7af843.png)

## 1. 作成

1\. **スタックの作成**から、**新しいリソースを使用（標準）**
![スクリーンショット 2022-09-20 21.12.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/eace06ec-1043-02d9-2a21-963e8b56e99e.png)

2\. **テンプレートの準備完了**から、**テンプレートファイルのアップロード**から作成したファイルをアップロードして、**次へ**を押す
![スクリーンショット 2022-09-20 21.18.58.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/3eb1574e-c817-9e28-4158-c9107e1abe10.jpeg)

3\. **スタックの名前**を任意で記載し、各パラメータを設定して、**次へ**を押す
![スクリーンショット 2022-09-20 21.37.23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/14a7e86e-cd75-f80d-c2e4-cbc0446dfb40.jpeg)

**パラメータ**は、作成手順で説明した**Parameters**を記述したことによって、記載することができるようになっています。
デフォルトで設定しておくと、自動で表示されるのでおすすめです。

4\. **スタックオプションの設定**では、特に記載を変更することなく、**次へ**を押す
![スクリーンショット 2022-09-20 21.44.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/716044df-63bc-f8c0-110a-a65ee18d84a3.png)

5\. 最後に内容を確認して、問題なければ**スタックの作成**を押す。
![スクリーンショット 2022-09-20 21.46.18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/74407421-9fef-d30a-897b-ec906bee91ba.jpeg)

6\. 無事、全てのリソースが作成できたら完了！

これで、コードの実装から実際のCloudFormationを触ってAWSリソースを作成するところまで、終了いたしました\:tada:\:tada:
お疲れ様でした。

## 補足
### エラーでうまく作成できなかった場合
途中でエラーが出てリソースが作成できない場合も、スタックとして残ってしまうため、一度削除してからもう一度試してください。
ただ削除して、もう一度試しても解決はできないと思うので、エラー内容をよく見て調べましょう！

# おわりに
案件でCloudFormationが必要になった時、自分は **そもそも、CloudFormationってなんだ？** というところからスタートしました。
ただそこからドキュメントなりいろいろ調べていく中で、CloudFormationの概要からセキュリティグループやAMIなど、仕組みの部分がふわっとした知識から、少しは固められたかなと思います。

CloudFormationでAWSリソースを構築する記事はたくさん出てくるので、その中の1つとしてこの記事も皆さんのお役に立てれば嬉しいです。

記事をご覧いただき、ありがとうございました。