【AWS CloudFormation】開発環境を作ってみる

# ■ CloudFormation

CloudFormation は AWSリソースのプロビジョニングを行えるサービスで、YAML(またはJSON)でテンプレートを記述することができます。
今回は勉強がてら自分の開発環境を作ってみたいと思います。

![スクリーンショット 2021-09-07 005950.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/f57c2ea5-3f44-8f0f-a84a-5a32aff10972.png)


## ◎ テンプレート
```dev.yml
# 組み込み関数: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
# 疑似パラメータ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html
AWSTemplateFormatVersion: '2010-09-09'

# パラメータ定義: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html
Parameters:
  ProjectPrefix:
    Type: String
  KeyPairBastion:
    Description: The EC2 Key Pair for bastion server
    Type: AWS::EC2::KeyPair::KeyName

# アウトプット: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html
Outputs:
  PublicIpBastion:
    Value: !GetAtt BastionServer.PublicIp
  PrivateIpDev:
    Value: !GetAtt DevServer.PrivateIp

Resources:
  # vpc: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.91.0.0/16
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-vpc

  # サブネット: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.91.10.0/24
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false # このサブネットで起動されたインスタンスが起動時にパブリックIPを設定するか(default = false)
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-public-subnet-01
  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.91.11.0/24
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-private-subnet-01

  # インターネットゲートウェイ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-igw

  # VPCにインターネットゲートウェイをアタッチ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html
  AttachInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # ElasticIp: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html
  EipNgw:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-eip-ngw01
  EipBastion:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-eip-bastion

  # NATゲートウェイ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EipNgw.AllocationId
      SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-ngw-01

  # ルートテーブル: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachInternetGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-public-rt-01
  PrivateRouteTable01:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachInternetGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-private-rt-01

  # サブネットとルートテーブルの紐づけ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet-route-table-assoc.html
  PublicRouteAssoc01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01
  PrivateRouteAssoc01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable01
      SubnetId: !Ref PrivateSubnet01


  # ルート定義: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PrivateRoute01:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable01
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway


  # EC2用のセキュリティグループ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html
  SecurityGroupBastion:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for bastion server
      VpcId: !Ref VPC
      SecurityGroupIngress: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 124.41.88.40/32
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-sg-bastion
  SecurityGroupDev:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for dev server
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 0
          ToPort: 65535
          CidrIp: !GetAtt VPC.CidrBlock
        - IpProtocol: icmp # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html
          FromPort: 8
          ToPort: -1
          CidrIp: !GetAtt VPC.CidrBlock
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-sg-dev


  # 踏み台サーバー: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
  BastionServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-02892a4ea9bfa2192
      KeyName: !Ref KeyPairBastion
      InstanceType: t2.micro
      NetworkInterfaces: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html
        - NetworkInterfaceId: !Ref BastionXface01
          DeviceIndex: 0
      BlockDeviceMappings: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-mapping.html
        - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/
          Ebs: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html
            VolumeType: gp2
            VolumeSize: 10
            DeleteOnTermination: true
            Encrypted: true
      UserData: !Base64 |
        #!/bin/bash
        sudo yum install -y git tmux
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-ec2-bastion

  # パブリックサブネットに踏み台サーバー用のNicを作成: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html
  BastionXface01:
    Type: AWS::EC2::NetworkInterface
    Properties:
      SubnetId: !Ref PublicSubnet01
      GroupSet:
        - !Ref SecurityGroupBastion
      SourceDestCheck: true
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-xface-bastion

  # 踏み台サーバー用のNicにEipを紐づけ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip-association.html
  EipAssocBastionXface01:
    Type: AWS::EC2::EIPAssociation
    Properties:
      AllocationId: !GetAtt EipBastion.AllocationId
      NetworkInterfaceId: !Ref BastionXface01

  # 開発サーバー: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
  DevServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-02892a4ea9bfa2192
      KeyName: !Ref KeyPairBastion
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet01
      PrivateIpAddress: 10.91.11.10
      SecurityGroupIds:
        - !Ref SecurityGroupDev
      BlockDeviceMappings:
        - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/
          Ebs:
            VolumeType: gp2
            VolumeSize: 16
            DeleteOnTermination: false
            Encrypted: true
      UserData: !Base64 |
        #!/bin/bash
        sudo yum install -y git tmux
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-ec2-dev
```

# ■ テンプレート: Parametersセクション
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html

`Parameters` セクションでは、テンプレートで利用する変数を定義できます。

- ProjectPrefix  
作成リソースの名前のプレフィックスとして利用する文字列を定義
- KeyPairBastion  
キーペアを指定するためのパラメータを定義

```dev.yml
Parameters:
  ProjectPrefix:
    Type: String
  KeyPairBastion:
    Description: The EC2 Key Pair for bastion server
    Type: AWS::EC2::KeyPair::KeyName
```

# ■ テンプレート: Outputsセクション
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html

`Outputs` セクションにはスタック作成後の出力値を定義します。

- PublicIpBastion  
踏み台サーバーのパブリックIP。sshで利用。
- PrivateIpDev  
開発サーバーのプライベートIP。sshで利用。

```dev.yml
Outputs:
  PublicIpBastion:
    Value: !GetAtt BastionServer.PublicIp
  PrivateIpDev:
    Value: !GetAtt DevServer.PrivateIp
```

### # 組み込み関数 `!GetAtt`
[Fn::GetAtt](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html)
CloudFormationにはテンプレート内で利用できる関数が定義されています。
`!GetAtt` はリソースのメンバとして定義されている情報を参照することができます。
`BastionServer.PublicIp` は BastionServer(後述)のパブリックIPを参照します。


# ■ テンプレート: Resourcesセクション
`Resources` セクションには作成するAWSリソースを定義します。

## ◎ VPCの定義
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html

- CidrBlock  
VPCで利用するプライベートネットワーク用のIPアドレス範囲をCIDR形式で指定します。

```dev.yml
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.91.0.0/16
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-vpc
```

### # 組み込み関数 `!Sub`
[Fn::Sub](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)
`!Sub` は変数の内容を文字列に埋め込むことができます。


## ◎ サブネット
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html

直接インターネットと通信可能なパブリックサブネット( `10.91.10.0/24` )と、NATを介してインターネットと通信するプライベートサブネット( `10.91.10.0/24` )、2つのサブネットを定義します。

- CidrBlock  
サブネットのIPアドレス範囲をCIDR形式で指定します。
- AvailabilityZone  
サブネットを作成するAZを指定します
- MapPublicIpOnLaunch  
サブネットで起動したインスタンスがパブリックIPを受け取るかを指定します。
- VpcId  
サブネットが属するVPCを指定します。

```dev.yml
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.91.10.0/24
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false # このサブネットで起動されたインスタンスが起動時にパブリックIPを設定するか(default = false)
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-public-subnet-01
  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.91.11.0/24
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-private-subnet-01
```

### # 組み込み関数 `!Ref`
[Ref](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html)
`!Ref` は指定したパラメータまたはリソースの値を返します。リソースを参照した場合の戻り値はリソースのドキュメントを確認する必要があります。


## ◎ インターネットゲートウェイ
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html

インターネットと通信するためのインターネットゲートウェイを定義します。

```dev.yml
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-igw
```

## ◎ インターネットゲートウェイをVPCにアタッチ
 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html

```dev.yml
  AttachInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
```

## ◎ ElasticIPの確保
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html

NATゲートウェイ用のグローバルIPアドレスと踏み台サーバー用のIPアドレスを取得します。

- Domain  
VPCのインスタンスで利用する場合は `vpc` を指定します。  
EC2-Classicのインスタンスで利用する場合は `standard` を指定します。

```dev.yml
  EipNgw:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-eip-ngw01
  EipBastion:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-eip-bastion
```

## ◎ NATゲートウェイ
 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html

プライベートサブネット内のインスタンスがインターネットと通信するためのNATゲートウェイを作成します。
※ インターネットゲートウェイとNATゲートウェイの説明はこちらがわかりやすかったです
https://milestone-of-se.nesuke.com/sv-advanced/aws/internet-nat-gateway/

- AllocationId  
NATゲートウェイに紐づけるElasticIPのAllocationIdを指定します。
- SubnetId  
NATゲートウェイを配置するサブネットを指定します。  
ここではパブリックサブネットを指定します。(プライベートサブネットではない！)

```dev.yml
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EipNgw.AllocationId
      SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-ngw-01
```

## ◎ ルートテーブル
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html

パブリックサブネットとプライベートサブネットのルーティングを定義するためのルートテーブルを作成します。

- VpcId  
ルートテーブルを配置するVPCを指定します。

```dev.yml
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachInternetGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-public-rt-01
  PrivateRouteTable01:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachInternetGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-private-rt-01
```

## ◎ ルートテーブルとサブネットの紐づけ
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet-route-table-assoc.html

先ほど作成したルートテーブルとサブネットを紐づけます。

- RouteTableId  
サブネットに紐づけるルートテーブルを指定します。
- SubnetId  
ルートテーブルに紐づけるサブネットを指定します。

```dev.yml
  PublicRouteAssoc01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01
  PrivateRouteAssoc01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable01
      SubnetId: !Ref PrivateSubnet01
```

## ◎ ルート定義
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html

**パブリックサブネット**:  デフォルトゲートウェイにインターネットゲートウェイを設定します。
**プライベートサブネット**: デフォルトゲートウェイにNATゲートウェイを設定します。

- RouteTableId  
ルーティングを定義したいルートテーブルを指定します。
- DestinationCidrBlock  
宛先をCIDR形式で指定します。
- GatewayId  
宛先がDestinationCidrBlockにマッチする場合のゲートウェイを指定します。


```dev.yml
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PrivateRoute01:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable01
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway
```


## ◎ セキュリティグループ
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html

踏み台サーバーと開発サーバーへのアクセスを制限するためのセキュリティグループを定義します。
**踏み台サーバー**: すべてのアクセス元からポート22へのtcpアクセスを許可します
**開発サーバー**: ローカルネットワークから全ポートへのtcpアクセス、icmpパケットを許可します。
※ 外に出ていく通信に関してはデフォルトで制限はないので、特に設定する必要はありません。

- VpcId  
セキュリティグループを定義するVPCを指定します
- SecurityGroupIngress  
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-rule-1.html  
インバウンドルールを定義するセクションです。
    - IpProtocol  
    `tcp` , `udp` , `icmp` など対象となる通信プロトコルを指定します。
    - FromPort  
    対象となるポート範囲の、開始ポートを指定します。
    - ToPort  
    対象となるポート範囲の、終了ポートを指定します。
    - CidrIp  
    対象となるIPアドレス範囲をCIDR形式で指定します。
- SecurityGroupEgress  
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-rule.html  
アウトバウンドルールを定義するセクションです。

```dev.yml
  SecurityGroupBastion:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for bastion server
      VpcId: !Ref VPC
      SecurityGroupIngress: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-sg-bastion
  SecurityGroupDev:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for dev server
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 0
          ToPort: 65535
          CidrIp: !GetAtt VPC.CidrBlock
        - IpProtocol: icmp # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html
          FromPort: 8
          ToPort: -1
          CidrIp: !GetAtt VPC.CidrBlock
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-sg-dev
```

## ◎ 踏み台サーバー
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html

踏み台サーバーインスタンスを作成します。
踏み台サーバーは直接ログインできる必要があるため、パブリックサブネットに配置し、グローバルIPを付与します。

- ImageId  
AMI IDを指定します。今回はAmazon Linux 2を利用。
- KeyName  
キーペアを指定します。
- InstanceType  
インスタンスタイプを指定します。
- NetworkInterfaces  
インスタンスにアタッチするネットワークインターフェースを定義します。  
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html
    - NetworkInterfaceId  
    既存の[NetworkInterface](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html) を指定します。
    - DeviceIndex  
    NetworkInterfaceのアタッチ順序です。プライマリネットワークの場合は `0` を指定する必要があります。  
    インスタンス起動時にNetworkInterfaceを作成する場合は必須指定です。
- BlockDeviceMappings  
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-mapping.html  
インスタンスにアタッチするブロックデバイスを定義します。
    - DeviceName  
    デバイス名を定義します。  
    <font color="red">※ ルートボリュームを操作する場合は `/dev/xvda` を指定します。</font>  
    https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/
    - Ebs  
    https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html  
    インスタンス起動時にEBSボリュームをセットアップするためのセクションです。
        - VolumeType  
        ボリュームタイプを指定します
        - VolumeSize  
        ボリュームサイズをGB単位で指定します。
        - DeleteOnTermination  
        `true` ならインスタンス終了時にボリュームを削除します。
        - Encrypted  
        `true` ならボリュームを暗号化します
        - Iops  
        iopsの指定が可能なボリュームタイプにおいてiopsを指定します。 `gp3` なら 3000 ~ 16000。
- UserData  
インスタンス起動時に実行されるスクリプトを定義します。(base64エンコードされている必要があります)

```dev.yml
  # 踏み台サーバー: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
  BastionServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-02892a4ea9bfa2192
      KeyName: !Ref KeyPairBastion
      InstanceType: t2.micro
      NetworkInterfaces: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html
        - NetworkInterfaceId: !Ref BastionXface01
          DeviceIndex: 0
      BlockDeviceMappings: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-mapping.html
        - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/
          Ebs: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html
            VolumeType: gp2
            VolumeSize: 10
            DeleteOnTermination: true
            Encrypted: true
      UserData: !Base64 |
        #!/bin/bash
        sudo yum install -y git tmux
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-ec2-bastion
```

## ◎ 踏み台サーバー用のNetworkInterfaceを作成
 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html

パブリックサブネットに踏み台サーバー用のNetworkInterfaceを作成します。
※ `BastionServer` セクションの `NetworkInterfacesId` で指定されているリソースです。

- SubnetId  
NetworkInterface を紐づけるサブネットを指定します。
- GroupSet  
NetworkInterface に紐づけるセキュリティグループを指定します。
- SourceDestCheck  
受信時にインスタンスが送信元・受信先のいずれかであることをチェックする。

```dev.yml
  BastionXface01:
    Type: AWS::EC2::NetworkInterface
    Properties:
      SubnetId: !Ref PublicSubnet01
      GroupSet:
        - !Ref SecurityGroupBastion
      SourceDestCheck: true
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-xface-bastion
```
## ◎ 踏み台サーバー用のNetworkInterfaceにグローバルIPを紐づけ
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip-association.html

先ほど作成した認証サーバー用のNetworkInterfaceにElasticIPを紐づけます。

- AllocationId  
NetworkInterface に紐づけるElasticIPのAllocationIdを指定します。
- NetworkInterfaceId  
ElasticIPに紐づけるNetworkInterfaceIdを指定します。

```dev.yml
  EipAssocBastionXface01:
    Type: AWS::EC2::EIPAssociation
    Properties:
      AllocationId: !GetAtt EipBastion.AllocationId
      NetworkInterfaceId: !Ref BastionXface01
```

## ◎ 開発サーバー
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html

開発サーバーを作成します。
開発サーバーは踏み台サーバー経由でログインするので、グローバルIPは必要ありません。プライベートサブネットに配置し、一応ローカルIPを指定しておきます。

- SubnetId  
インスタンスを起動するサブネットを指定します。  
※ NetworkInterfaceセクションを指定している場合は、ここではなくNetworkInterfaceセクション内でセキュリティグループを指定する必要があります。
- PrivateIPAddress  
プライマリIPv4アドレスを指定します。IPはサブネットの範囲で指定しなければなりません。
- SecurityGroupIds  
セキュリティグループを指定します。  
※ NetworkInterfaceセクションを指定している場合は、ここではなくNetworkInterfaceセクション内でセキュリティグループを指定する必要があります。

```dev.yml
  DevServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-02892a4ea9bfa2192
      KeyName: !Ref KeyPairBastion
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet01
      PrivateIpAddress: 10.91.11.10
      SecurityGroupIds:
        - !Ref SecurityGroupDev
      BlockDeviceMappings:
        - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/
          Ebs:
            VolumeType: gp2
            VolumeSize: 16
            DeleteOnTermination: false
            Encrypted: true
      UserData: !Base64 |
        #!/bin/bash
        sudo yum install -y git tmux
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-ec2-dev
```


# ■ キーペアの作成
AWSコンソールのEC2の画面からsshで利用するキーペアを作成します。
秘密鍵(hogehoge.pem)はローカル環境の `~/.ssh/` 配下に配置します。 (パーミッションを `400` に変更するのを忘れずに)

# ■ スタックの作成
AWSコンソールからテンプレートをアップロードして、パラメータ( `ProjectPrefix` `KeyPairBastion` ) を指定してスタックを作成します。

![スクリーンショット 2021-09-08 004729.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/8f9266d9-86e2-da4d-3b1a-4debb1ad5087.png)
![スクリーンショット 2021-09-08 005127.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/e08d5475-0e05-c072-ab55-6361e6478b7c.png)


# ■ sshの設定

```./ssh/config
Host bastion
  HostName xxx.xxx.xxx.xxx　# Outputsで出力されるPublicIpBastionの値
  User ec2-user
  IdentityFile ~/.ssh/secret.pem # キーペアの作成で作成した秘密鍵を指定

Host dev
  HostName 10.91.20.10
  User ec2-user
  IdentityFile ~/.ssh/secret.pem # キーペアの作成で作成した秘密鍵を指定
  ProxyCommand ssh -W %h:%p bastion # 開発サーバーは踏み台サーバーをプロキシして接続する
```

```bash
ssh dev
```

# ■ 参考記事
- [AWS CloudFormation User Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html)
- [CloudFormationを使ってNAT Gatewayを構築する | Qiita](https://qiita.com/okubot55/items/23edb9ae97678fd5fad2)
- [初学者のためのAWS入門(3)-CloudFormation入門2 | Qiita](https://qiita.com/mshinoda88/items/65e7e145c7ec943af403)
- [【CloudFormation入門】5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築 | DevelopersIO](https://dev.classmethod.jp/articles/cloudformation-beginner01/)
- [CloudFormation の参照周りで意識すべきポイント・Tips | DevelopersIO](https://dev.classmethod.jp/articles/cloudformation-tips-focused-on-refs/)
- [CloudFormation入門 – ハマリポイント・注意点](https://techblog.raccoon.ne.jp/archives/1601887183.html)
- [【図解/AWS】インターネットGWとNAT-GWの違い〜各メリット、パブリックサブネットとは〜](https://milestone-of-se.nesuke.com/sv-advanced/aws/internet-nat-gateway/)