Rainの「生成AIによるCloudFormationテンプレート作成」機能を試す


# はじめに
生成AIの業務利用が進んでいる昨今、CloudFormationやTerraformといったIaCテンプレート作成への活用も耳にすることが増えました。
筆者自身、CloudFormationやTerraformのIaCテンプレートを生成AIの助けを得て作成することもかなり多いです。定義を間違えることも時々ありますが、複数リソース間での論理参照をやってくれたり結構便利で助かっています。

ところで、CloudFormationのツールとしてRainというものがあります。このRain、生成AIによるテンプレート作成機能を持っているとのことです。
ということで今回はRainの持つ生成AIテンプレート作成機能を試してみます。


# Rainとは

Rainは、AWS CloudFormationのテンプレートとスタックを操作するためのCLIツールです。
CloudFormationのStackの参照やデプロイ、Template操作の拡張的な機能を利用可能で、Stackを作成するプロセスで役立つことも多そうです。

AWSのリポジトリで開発されています。ツール自体は生成AIを前提としているものではなく、v1.0は4年前にリリースされていました。

https://github.com/aws-cloudformation/rain






# 試してみる
## インストール
今回はバージニア北部リージョンのAWS CloudShellで動作確認を行います。
生成AI機能ではバックグラウンドでBedrockの基盤モデルを利用するため、Bedrockの基盤モデルが有効化されているリージョンかつ利用権限のあるユーザで実行してください。

<br>

はじめに[リリースノート](https://github.com/aws-cloudformation/rain/releases)から最新のバージョンのバイナリをダウンロードします。
今回は以下コマンドで1.8.1をダウンロードし、解凍しました。パスの追加も行います。
```bash
$ wget https://github.com/aws-cloudformation/rain/releases/download/v1.8.1/rain-v1.8.1_linux-amd64.zip
$ unzip rain-v1.8.1_linux-amd64.zip 
$ export PATH=$PATH:~/rain-v1.8.1_linux-amd64/
```
<br>

`rain`コマンドでヘルプが出ればOKです。
```bash
$ rain -h
Rain is a command line tool for working with AWS CloudFormation templates and stacks

Usage:
  rain [command]

Stack commands:
  cat         Get the CloudFormation template from a running stack
  cc          Interact with templates using Cloud Control API instead of CloudFormation
  deploy      Deploy a CloudFormation stack or changeset from a local template
  logs        Show the event log for the named stack
  ls          List running CloudFormation stacks or changesets
  rm          Delete a CloudFormation stack or changeset
  stackset    This command manipulates stack sets.
  watch       Display an updating view of a CloudFormation stack

Template commands:
  bootstrap   Creates the artifacts bucket
  build       Create CloudFormation templates
  diff        Compare CloudFormation templates
  fmt         Format CloudFormation templates
  forecast    Predict deployment failures
  merge       Merge two or more CloudFormation templates
  pkg         Package local artifacts into a template
  tree        Find dependencies of Resources and Outputs in a local template

Other Commands:
  completion  Generate the autocompletion script for the specified shell
  console     Login to the AWS console
  help        Help about any command
  info        Show your current configuration
```
<br>

Rainが利用しているプロファイル情報は`rain info`で確認することができます。以下のように表示されます。
```bash
$ rain info
Account:  000000000000
Region:   us-east-1
Identity: arn:aws:iam::000000000000:user/username
```


## AIによるCfnテンプレート生成

### 動作確認
Cfnテンプレート生成には`rain build`を使います。
生成AIに対してプロンプトを利用する場合は`-p`オプションです。

まずは単純な指示を試してみます。
```bash
$ rain build -p "simple VPC"
AWSTemplateFormatVersion: '2010-09-09'
Description: Simple VPC CloudFormation template
Resources:
  MyVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: MyVPC
```

特に細かい指示はしていませんが、テンプレートを出力してくれました。
バックグラウンドでは与えたプロンプトを使ってrainがBedrockのClaudeにリクエストを投げているそうです。

出力はデフォルトでは標準出力です。ファイル出力したい場合は、以下のようにリダイレクトを使います。（ドキュメントにはオプション`-o`で出力先指定と書いてったのですが、できませんでした）
```
$ rain build -p "simple VPC" > template1.yaml
```


### AI生成出力の確認
少し指示の難易度をあげて、出力される内容を確認します。
以下のプロンプトを与えてみました。

```bash 
$ rain build -p \
> "VPC with 2 public subnets and 2 private subnets. 
> Every public subnet have NAT gateway. Public subnet with route table that route to Internet gateway. 
> Private subnet with route table that route to NAT gateway.
> One EC2 t2.micro instance of Ubuntu 22.04 and output SSH Key Info.
> " > template2.yaml
```

完全な出力は長くなるので、記事の最後に記載しています。全部見たい方は参照ください。

ここでは、気になった部分だけ切り取って出力を確認します。
AIによる作成なので再現性があるかは微妙ですが、こんなこともあったというサンプル程度に見て頂ければと思います。
<br>

#### AIのメッセージが余計に出力されている
まずはじめに、テンプレートの先頭行にLLMの出力っぽい行がありました。
期待としてはCfnテンプレートの内容だけを出力してほしいのですが、こういったところは制御しきれていないようですね。

```yaml
Here is the AWS CloudFormation YAML template for the requested infrastructure:

AWSTemplateFormatVersion: '2010-09-09'
```


#### 論理参照はOK
以下はVPC Endpointの定義部分です。
```yaml
  VPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds: 
        - !Ref PublicRouteTable
        - !Ref PrivateRouteTable1
        - !Ref PrivateRouteTable2
      ServiceName: !Join 
        - ''
        - - com.amazonaws.
          - !Ref 'AWS::Region'
          - .s3
      VpcId: !Ref VPC
```
RouteTablesIdsの参照では`!Ref`を使って他のリソースを参照できています。
ServiceNameの部分でもリージョンをスタック実行リージョンのものと出来るように事前定義パラメータ`AWS::Region`を利用できています。
`!Sub "con.amazonaws.${AWS::Region}.s3"`とした方が可読性は高いですが、このくらいはご愛嬌と思えます。


#### キー情報をハードコード
KeyPairの定義部分です。

```yaml
  EC2KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: myKey
      PublicKeyMaterial: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com
      KeyType: rsa
```

自前で生成したキーをハードコードして記入するという斬新な方法をとっています。ここは値を指定せずAWSにキーペアを生成してもらうほうが主流だと思います。AIが考えだしたキーのためPrivateKeyを取得する方法はないです。
こういったことが起こりえるので、注意が必要そうです。


#### EC2のAMI IDは実在の物
EC2インスタンスの定義です。
```yaml
  EC2Instance: 
    Type: AWS::EC2::Instance
    Properties:
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      ImageId: ami-0557a15b87f6559cf
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds: 
        - !Ref InstanceSecurityGroup
```
ImageIdが`ami-0557a15b87f6559cf`と指定されていて、これも生成AIが創造した値かと疑って調べたところ、実在のUbuntuイメージでした。
必ずそうなるという訳ではありませんが、今回のUbuntu AMI IDのように多用される固有値は出力が可能化もしれません。

![2024-03-25-00-10-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115707/8d492e36-3102-f759-092d-83e179472bcd.png)


### AI生成出力の確認　日本語
Rainのツール自体は英語なのですが、テンプレート作成の部分はBedrockの基盤モデルを利用しているため、日本語でもある程度は出力してくれると見込んで日本語も試してみました。内容は先ほどの例と同じです。

```bash 
$ rain build -p \
> "2つのパブリックサブネットと2つのプライベートサブネットを持つVPC。
VPCにはインターネットゲートウェイ、S3用VPCエンドポイントがあります。
各パブリックサブネットにはNATゲートウェイがあります。パブリックサブネットにはインターネットゲートウェイへのルートテーブルがあります。プライベートサブネットにはNATゲートウェイへのルートテーブルがある。
Ubuntu 22.04のEC2 t2.microインスタンス1台とSSHキー情報。
> " > template3.yaml
```
こちらも結果全量は記事最後に記載しています。

結果としては、英語のプロンプトで生成したものとあまり差はありませんでした。使い込んでいくと差は出てくるかもしれませんが、日本語でもある程度使えそうな印象でした。


## diff機能
他の機能として、rainにはdiff機能があります。
以下のようなコマンドで差分を表示することができます。
```bash
$ rain diff {template1} {template2}
```

先ほどの英語例と日本語例を比較した結果例
![2024-03-25-00-30-56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115707/792f0e75-e719-f250-0487-61fdcfec8c85.png)

一般的な文字列のdiffだと、リソースの記述位置を移動したときに出力が多くなってしまいがちですが、こちらを使うとYAMLの構成的に差分があった部分だけ表示してくれているように見えます。
またリソースがまるごと削除された場合などはリソース名だけ表示するなど、出力は比較的見やすいです。


## forecast機能
実験的実装の段階ですが`rain forecast`というコマンドを使うと、テンプレートのデプロイ時にエラーが発生しそうな箇所を事前に検証してくれるようです。
先ほどの出力テンプレートに対して実行してみた結果が以下の通りです。

```bash
$ rain forecast --experimental template3.yaml 
Stormy weather ahead! 🌪
16 checks failed out of 42 total checks

7: AWS::EC2::VPC VPC - Insufficient permissions to create arn:aws:ec2:us-east-1:000000000000:vpc/rain-3609dc39-85fd-49f8-9dab-4af1c953ee3a
        ec2:DescribeVpcs not allowed on arn:aws:ec2:us-east-1:000000000000:vpc/rain-3609dc39-85fd-49f8-9dab-4af1c953ee3a

14: AWS::EC2::InternetGateway InternetGateway - Insufficient permissions to create arn:aws:ec2:us-east-1:000000000000:internet-gateway/rain-d0420f95-3c31-45d4-ac09-2b5f4a8b4517
        ec2:DescribeInternetGateways not allowed on arn:aws:ec2:us-east-1:000000000000:internet-gateway/rain-d0420f95-3c31-45d4-ac09-2b5f4a8b4517
～略～
```

42個のチェックが走り、16がfailedとなっています。実際のエラー内容を見ると16個が全て「作成のパーミッションが無い」というエラーでした。実行ユーザにはその権限はあるのでこのエラー自体は誤りです。

結果としてはエラーのバリエーションも精度もまだ有用なものではなさそうですが、今後に期待したいです。


# おわりに
CloudFormationのテンプレートを作る時、多くの人がLLMを使いながら作成している時代だとは思います。筆者もLLMにサポートを受けながらCfnテンプレートを作っていますが、もっと良いLLMの使い方があるんじゃないかと思いながら悶々としていたので、AWSのリポジトリにあったツールが気になり今回は動作確認をしてみました。
現段階では、自身でClaudeにCfnテンプレを書かせるのと大きな差はなさそうだったのですが、Rainはまだ改良段階ではあると思うので今後の改良も見守っていきたいと思います。



# （補足）出力サンプル
## rain build 例１
入力
> simple VPC

<details><summary>出力</summary>


出力
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Simple VPC CloudFormation template
Resources:
  MyVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: MyVPC
```

</details>

## rain build 例２
入力
> VPC with 2 public subnets and 2 private subnets.
VPC has Internet gateway, VPC Endpoint for S3.
.Every public subnet have NAT gateway. Public subnet with route table that route to Internet gateway. Private subnet with route table that route to NAT gateway.
One EC2 t2.micro instance of Ubuntu 22.04 and output SSH Key Info.

<details><summary>出力</summary>


出力
```yaml
Here is the AWS CloudFormation YAML template for the requested infrastructure:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to deploy VPC with public and private subnets, NAT Gateway, Internet Gateway, VPC Endpoint, EC2 instance

Resources:

  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
  
  InternetGateway:
    Type: AWS::EC2::InternetGateway

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
  
  PublicSubnet2: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
  
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC  
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.3.0/24
  
  PrivateSubnet2: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: 10.0.4.0/24
  
  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties: 
      AllocationId: !GetAtt NATIP1.AllocationId
      SubnetId: !Ref PublicSubnet1
  
  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NATIP2.AllocationId
      SubnetId: !Ref PublicSubnet2
  
  NATIP1: 
    Type: AWS::EC2::EIP
    Properties: 
      Domain: vpc
  
  NATIP2:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
  
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
  
  PublicRoute: 
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation  
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable
  
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable
      
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
  
  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1
  
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1
  
  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
  
  PrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0  
      NatGatewayId: !Ref NatGateway2
  
  PrivateSubnet2RouteTableAssociation: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

  VPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds: 
        - !Ref PublicRouteTable
        - !Ref PrivateRouteTable1
        - !Ref PrivateRouteTable2
      ServiceName: !Join 
        - ''
        - - com.amazonaws.
          - !Ref 'AWS::Region'
          - .s3
      VpcId: !Ref VPC
  
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0557a15b87f6559cf
      KeyName: !Ref EC2KeyPair
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"  
          GroupSet: 
            - !Ref InstanceSecurityGroup
          SubnetId: !Ref PublicSubnet1
  
  EC2KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: myKey
      PublicKeyMaterial: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com
      KeyType: rsa
  
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH traffic
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 22
          ToPort: 22
          IpProtocol: tcp

Outputs:

  EC2InstanceId:
    Value: !Ref EC2Instance
    Description: EC2 Instance Id
  
  EC2InstancePublicIp:
    Value: !GetAtt EC2Instance.PublicIp
    Description: EC2 Instance Public IP Address
  
  EC2InstancePublicDNS:
    Value: !GetAtt EC2Instance.PublicDnsName 
    Description: EC2 Instance Public DNS Name
  
  SSHKeyName:
    Value: !Ref EC2KeyPair
    Description: SSH key name to login into EC2 instance
```

</details>


## rain build 例３
入力
> 2つのパブリックサブネットと2つのプライベートサブネットを持つVPC。
VPCにはインターネットゲートウェイ、S3用VPCエンドポイントがあります。
各パブリックサブネットにはNATゲートウェイがあります。パブリックサブネットにはインターネットゲートウェイへのルートテーブルがあります。プライベートサブネットにはNATゲートウェイへのルートテーブルがある。
Ubuntu 22.04のEC2 t2.microインスタンス1台とSSHキー情報。


<details><summary>出力</summary>


出力
```yaml
Here is the AWS CloudFormation YAML template for the requested infrastructure:

AWSTemplateFormatVersion: '2010-09-09'
Description: VPC infrastructure with public and private subnets

Parameters:
  KeyName:
    Description: Name of an existing EC2 key pair
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription: Must be the name of an existing EC2 key pair.

Resources:

  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
  
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
  
  PublicSubnet2: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
  
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.3.0/24
  
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC  
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: 10.0.4.0/24
  
  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties: 
      Domain: vpc
  
  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
  
  NatGateway1: 
    Type: AWS::EC2::NatGateway
    Properties: 
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
  
  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2
  
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
  
  DefaultPublicRoute: 
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties: 
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation  
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1
  
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2
      
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
  
  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1
  
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1
  
  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  
  PrivateRoute2:
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2
  
  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds: 
        - !Ref PublicRouteTable
        - !Ref PrivateRouteTable1
        - !Ref PrivateRouteTable2
      ServiceName: !Join 
        - ''
        - - com.amazonaws.
          - !Ref 'AWS::Region'
          - .s3
      VpcId: !Ref VPC
  
  EC2Instance: 
    Type: AWS::EC2::Instance
    Properties:
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      ImageId: ami-0557a15b87f6559cf
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds: 
        - !Ref InstanceSecurityGroup
  
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: Enable SSH access via port 22
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 22
          ToPort: 22
          IpProtocol: tcp
      VpcId: !Ref VPC

Outputs:

  VPCId:
    Description: The ID of the VPC
    Value: !Ref VPC
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", VPCID ] ] 
  
  PublicSubnets:
    Description: A list of the public subnets
    Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]]
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", PublicSubnets ] ]
  
  PrivateSubnets:
    Description: A list of the private subnets
    Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]]
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", PrivateSubnets ] ]
  
  EC2InstanceId:
    Description: The ID of the EC2 instance
    Value: !Ref EC2Instance
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", EC2InstanceID ] ]
```

</details>