【AWS】手動作成した既存のVPCをCloudFormationスタックにインポートする + EC2作成【CloudFormation】

# CloudFormation - 既存リソースのインポート方法メモ

## 概要

- すでに手動で作成してあるVPCをyamlファイル化(?)する
- CloudFormationを使って新たにEC2インスタンスをVPC上に構築する

## 参考

[ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import.html)
[インポートがサポートされているリソース](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html)

## 検証用に用意したもの

- 手動作成リソース
  - VPC
  - インターネットゲートウェイ
  - サブネット
  - ルートテーブル
  - セキュリティグループ

- 各リソースの識別子(ID)をメモしておく(インポート実行時に指定することになる)
  - VPC : vpc-xxxxxxxxxxxx
  - rtb : rtb-xxxxxxxxx
  - igw : igw-xxxxxxxxx
  - sg  : sg-xxxxxxxxx
  - sub : subnet-xxxxxxxxxxx

## 手順

### 1. 既存リソースからのスタック作成(VPC,igw,subnet,rt,sg)

- まずインポートするためのyamlファイルを作る

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: my imported VPC resources

Resources:
  # VPC
  myVPC:
    Type: AWS::EC2::VPC
    DeletionPolicy: Retain
    Properties: 
      CidrBlock: 192.168.0.0/16
      EnableDnsHostnames: false
      EnableDnsSupport: true
      InstanceTenancy: Default
      Tags: 
        - Key: Name
          Value: vpcName

  # InternetGateway
  myInternetGateway:
    Type: AWS::EC2::InternetGateway
    DeletionPolicy: Retain
    Properties: 
      Tags: 
        - Key: Name
          Value: igwName

  # Subnet
  mySubnet1:
    Type: AWS::EC2::Subnet
    DeletionPolicy: Retain
    Properties: 
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 192.168.1.0/24
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: publicSubnetName1
      VpcId:
        Ref: myVPC
  mySubnet2:
    Type: AWS::EC2::Subnet
    DeletionPolicy: Retain
    Properties: 
      AvailabilityZone: ap-northeast-1d
      CidrBlock: 192.168.2.0/24
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: publicSubnetName2
      VpcId:
        Ref: myVPC

  # Routetable
  myRoutetablePublic:
    Type: AWS::EC2::RouteTable
    DeletionPolicy: Retain
    Properties:
      Tags: 
        - Key: Name
          Value: publicRouteName
      VpcId:
        Ref: myVPC
  myRoutetablePrivate:
    Type: AWS::EC2::RouteTable
    DeletionPolicy: Retain
    Properties:
      Tags: 
        - Key: Name
          Value: privateRouteName
      VpcId:
        Ref: myVPC

  # SecuriryGroup
  mySecuritygroup:
    Type: AWS::EC2::SecurityGroup
    DeletionPolicy: Retain
    Properties:
        GroupDescription: allow-all-from-my-vm
        VpcId:
          Ref: myVPC
        SecurityGroupIngress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: ssh元のIP
        SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
```
インポートしたいリソースの設定内容をとりあえず入れていく
作成時デフォルトで作った場合、必須項目以外は記載無しでもデフォルトでインポートされる(...はず)

#### ==Point==

#### `DeletionPolicy: Retain` をすべてのリソースに設定する

これはスタックを削除しても実際のリソースはそのまま残るようにする設定
インポートに関してはこの設定がついてないとエラーで実行できない

#### セキュリティグループで「すべて」を指定したい場合

`FromPort`とかに「すべて」を指定したい場合は、"-1" にする
all とかにすると実行したときにFAILEDになる (Non numeric は サポートされていません みたいなエラー文だった)

#### インポートできないリソースもある

今回の場合、`AWS::EC2::Route`はインポート対象外といわれてインポートできなかった
他のリソースをインポートした後Routeの宣言を追加して、変更セット実行したらどうなる？？→要検証

### 2. スタック作成実行

1. スタックの作成プルダウンから、既存リソースを使用(リソースをインポート)を選択する

2. 必要なものを教えてくれる 次へ をクリック

3. テンプレートの指定でさっき作ったyamlファイルをアップロードする
  事前にS3にアップしておいてURL指定すると余計なバケットが作成されなくて済む

4. インポートするリソースのリソースIDの入力を求められたら、実際のリソースをコンソールなどで確認して、正しく入力する

5. スタックの名前 好きな名前を適当につけてOK

6. 「変更」欄で変更内容を確認して(importがずらっと並ぶはず) 「リソースをインポート」をクリック

7. 普通にスタック作成したときみたいにステータスが流れ始める
   エラーが出た場合はエラー文読んでデバッグしていく

### 3. ドリフト検出

ステータスが「IMPORT_COMPLETE」になったら、確認のためにドリフトを検出する

スタックを選択して「スタックアクション」プルダウンから「ドリフトの検出」→「ドリフト結果を表示」

全項目のドリフトステータスが「IN_SYNC」になってたらOK

- [参考](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html)

| ドリフトステータス | 説明                                                                                      |
| :----------------: | :---------------------------------------------------------------------------------------- |
|      DELETED       | リソースが削除されたため、リソースは意図したテンプレート設定と異なります。                |
|      MODIFIED      | リソースは意図したテンプレート設定とは異なります。                                        |
|    NOT_CHECKED     | CloudFormation は、リソースが意図したテンプレート設定と異なるかどうかを確認していません。 |
|      IN_SYNC       | リソースの現在の設定は、意図したテンプレート設定と一致します。                            |

#### yamlファイルの中身が間違っている場合

どうなるのか検証してみたところ、
`実際のリソースの設定に合わされるようにMODIFYされる`
ことがわかった

試しにVPCで `EnableDnsSupport: true → false` にした(わざと値を間違えた)ものを使ってimport実行してみたところ、
ドリフトを検出→ドリフトの結果→ドリフトの詳細を見ると、自動で true に修正されていることが分かった
これぞフォールトトレランス！！

### 3. スタックによるリソースの作成(EC2)

ここからおまけ

```yaml
  # EC2 Instance
  myEC2instance1:
    Type: AWS::EC2::Instance
    Properties: 
      BlockDeviceMappings: 
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp2
            DeleteOnTermination: true
            VolumeSize: 10
      ImageId: ami-0ddf5ff463ff76656
      InstanceType: t2.nano
      KeyName: my-key
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref mySubnet1
          GroupSet:
            - !Ref mySecuritygroup
      Tags: 
        - Key: Name
          Value: 
            Ref: InstanceName
```
- Ref: 
宣言したリソース名を指定することで特定のパラメータを戻り値で受け取ることができる。
戻り値の内容はリソースによって変わるので、ドキュメントを見て確認するしかない。
基本的にIDが返ってくる VPC→VPC ID

- VolumeType: gp2, gp3, io1, io2 とかを選ぶ gp2が一番標準のSSD

- gp2はiopsを指定できない (使用要領によってiopsが変動するらしい)

- ImageId: RockyLinux8を使用 一度インスタンスを作成して、概要からコピってきた
  [求]何もわからない状態からAMIを調べる方法

- NetworkInterfaces: インターフェース設定 この中でサブネットとセキュリティグループを指定する

- SubnetId: !Ref mySubnet1

!Ref mySubnet1とすることで SubnetID(subnet-xxxxxx) が Stringで返ってくる
Ref mySubnet1だとエラー起きるので、Stringではない何かが返ってきているようだ

- GroupSet:
    - !Ref mySecuritygroup

この書き方じゃないとうまくいかなかった
GroupSet(サブネット)はID(String型)じゃなくて、IDのリスト型じゃないとだめらしい(`List<AWS::EC2::SecurityGroup::Id>`)
```
エラー起きたときの出力↓
Value of property SecurityGroupIds must be of type List of String
```

#### 改良

- Parametersを使ってインスタンス名とセキュリティグループを指定できるようにした
- EIPをアタッチした

```yaml:import.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: my imported VPC resources

# Specify Parameter
Parameters:
  InstanceName:
    Description: Specify Instance Name.
    Type: String
  InstanceSecurityGroup:
    Description: Specify Secutiry Group.
    Type: List<AWS::EC2::SecurityGroup::Id>

Resources:

  ## 略 ##

  # EC2 Instance
  myEC2instance1:
    Type: AWS::EC2::Instance
    Properties: 
      BlockDeviceMappings: 
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp2
            DeleteOnTermination: true
            VolumeSize: 10
      ImageId: ami-0ddf5ff463ff76656
      InstanceType: t2.nano
      KeyName: my-key
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref mySubnet2
          GroupSet:
            Ref: InstanceSecurityGroup
      Tags: 
        - Key: Name
          Value: 
            Ref: InstanceName

  # Elastic IP
  myElasticIP1:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId:
        Ref: myEC2instance1
      Tags:
        - Key: Name
          Value: !Ref InstanceName
```

### 4. スタック更新実行

1. さっきインポートしたスタックを選択して、更新をクリック

2. 既存のテンプレートを置き換える → 修正したyamlファイルをアップロードして 次へ
3. パラメータを指定して 次へ
4. スタックオプションは特に指定なし 次へ
5. スタックの更新を実行 (不安なら 変更セットの表示)

### 5. 問題なければ完成！

EC2に指定したKeypairでsshできたら無事完成！