CloudFormationをとりあえず実行してみたい人のためのチュートリアル

#はじめに
この記事は[LIFULL Advent Calendar2018その２](https://qiita.com/advent-calendar/2018/lifull)の22日目の記事です。
AWSでインフラ構築をしたことはあるけど、CloudFormationという単語はなんとなく避けておりました。
ですが、意を決して取り組んでみると思いの外簡単・便利だったので、とりあえず言葉だけ知っていて試してみたいと思っている方向けに、簡単に実行できるチュートリアルを作りました。
※テンプレートの説明は反響があれば別記事で書こうと思います。

#CloudFormationで構築する環境
今回、CloudFormation（CFNと略すらしいです）を使って構築するインフラ構成は下図のようなシンプルなものにしました。

<img width="453" alt="構成図.png" src="https://qiita-image-store.s3.amazonaws.com/0/157858/515dff08-93c5-b1b0-81d2-bca47d9a6b95.png">

#準備
まず、AWSをcliで実行するためのインストールを行います。

```bash
$ sudo easy_install pip
$ sudo pip install awscli
$ aws --version
```

次に、AWSコンソール上で[アクセスキーを発行](https://qiita.com/miwato/items/291c7a8c557908de5833)し、下記の通り登録を行います。

```bash
$ aws configure --profile cfn
AWS Access Key ID [None]: 発行したアクセスキー
AWS Secret Access Key [None]: 発行したシークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]:
```
また、インスタンスを作成する際に必要な[キーペアをsampleという名前で作成](http://shomi3023.com/2017/08/13/post-90/)してダウンロードしておきます。


#実行手順
テンプレートファイルは、ページ下部のものをコピーしてください。（行数がすごいですが殆どサブネット関連の定義の繰り返しだったりします）
下記のコマンドでローカルからcfnを実行します。
※file://のパスはテンプレートを配置した場所によって異なります

```bash
$ ls -al
total 24
-rw-r--r--  1 shodak  shodak  10170 12 22 17:59 sample-template.yml
$ aws cloudformation create-stack --stack-name sample --template-body file://sample-template.yml --profile cfn
```

下記のリンクから実際にスタックが実行されているところが見れると思います。
https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks

構成図の通りにできているかと思います。
試しにbastionサーバーとapplicationサーバーにsshログインしてみます。
※面倒なので.ssh/configに下記の設定を追加しました。

```conf:~/.ssh/config
Host sample-bastion
  User ec2-user
  HostName 18.182.92.135
  Port 22
  Identityfile ~/.ssh/sample.pem

Host sample-application
  User ec2-user
  HostName 172.31.254.253
  Port 22
  Identityfile ~/.ssh/sample.pem
  ProxyCommand ssh -W %h:%p sample-bastion
```

```bash
$ mv Downloads/sample.pem ~/.ssh/
# 鍵の権限変更を忘れずに
$ chmod 400 ~/.ssh/sample.pem
$ ssh sample-bastion

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
15 package(s) needed for security, out of 16 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-172-31-0-65 ~]$
```
bastionサーバーにログインできたので、applicationサーバーも試します。

```bash
$ ssh sample-application

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-31-254-103 ~]$ curl http://google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
```

applicationサーバーにもログインでき、外部にも疎通していることが確認できました。
一通り確認したらstackを削除しましょう。
stackを削除することで、cloudformationによって生成されたリソースが全て削除されるため、お試しで環境を作りたい時に撤収が捗ります。

#今回実行したサンプルテンプレート

```yaml:sample-template.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Sample-template
Resources:
  VpcTemplate:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 172.31.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: sample
  BastionEIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId:
        Ref: BastionInstance
      Domain:
        Ref: VpcTemplate
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain:
        Ref: VpcTemplate
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: sample-igw
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
        - NatGatewayEIP
        - AllocationId
      SubnetId:
        Ref: PublicSubnet1a
      Tags:
        - Key: Name
          Value: sample-nat-gateway
  VpcIgwAttach:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: VpcTemplate
      InternetGatewayId:
        Ref: IGW
  PublicSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VpcTemplate
      CidrBlock: 172.31.0.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: public-subnet-1a
  PublicSubnet1c:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VpcTemplate
      CidrBlock: 172.31.1.0/24
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: public-subnet-1c
  PrivateSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VpcTemplate
      CidrBlock: 172.31.254.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: private-subnet-1a
  PublicSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VpcTemplate
      Tags:
        - Key: Name
          Value: public-igw
  PrivateSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VpcTemplate
      Tags:
        - Key: Name
          Value: private-nat
  PublicSubnetRouteTableAssociationA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PublicSubnet1a
      RouteTableId:
        Ref: PublicSubnetRouteTable
  PublicSubnetRouteTableAssociationC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PublicSubnet1c
      RouteTableId:
        Ref: PublicSubnetRouteTable
  PrivateSubnetRouteTableAssociationA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet1a
      RouteTableId:
        Ref: PrivateSubnetRouteTable
  RouteTableIGWAssociation:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId:
        Ref: PublicSubnetRouteTable
      GatewayId:
        Ref: IGW
  RouteTableNatGatewayAssociation:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId:
        Ref: PrivateSubnetRouteTable
      NatGatewayId:
        Ref: NatGateway
  AclPublic:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId:
        Ref: VpcTemplate
      Tags:
        - Key: Name
          Value: public
  AclPrivate:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId:
        Ref: VpcTemplate
      Tags:
        - Key: Name
          Value: private
  AclPublicInSsh:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      Protocol: 6
      PortRange:
        From: 22
        To: 22
      RuleAction: allow
      RuleNumber: 100
      NetworkAclId:
        Ref: AclPublic
  AclPublicInHttp:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      Protocol: 6
      PortRange:
        From: 80
        To: 80
      RuleAction: allow
      RuleNumber: 200
      NetworkAclId:
        Ref: AclPublic
  AclPublicInHttps:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      Protocol: 6
      PortRange:
        From: 443
        To: 443
      RuleAction: allow
      RuleNumber: 300
      NetworkAclId:
        Ref: AclPublic
  AclPublicEphemeralIn:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      Protocol: 6
      PortRange:
        From: 1024
        To: 65535
      RuleAction: allow
      RuleNumber: 400
      NetworkAclId:
        Ref: AclPublic
  AclPublicIn:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 172.31.0.0/23
      Egress: false
      Protocol: -1
      RuleAction: allow
      RuleNumber: 500
      NetworkAclId:
        Ref: AclPublic
  AclPublicOutSsh:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 22
        To: 22
      RuleAction: allow
      RuleNumber: 100
      NetworkAclId:
        Ref: AclPublic
  AclPublicOutHttp:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 80
        To: 80
      RuleAction: allow
      RuleNumber: 200
      NetworkAclId:
        Ref: AclPublic
  AclPublicOutHttps:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 443
        To: 443
      RuleAction: allow
      RuleNumber: 300
      NetworkAclId:
        Ref: AclPublic
  AclPublicEphemeralOut:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 1024
        To: 65535
      RuleAction: allow
      RuleNumber: 400
      NetworkAclId:
        Ref: AclPublic
  AclPublicOut:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 172.31.0.0/23
      Egress: true
      Protocol: -1
      RuleAction: allow
      RuleNumber: 500
      NetworkAclId:
        Ref: AclPublic
  AclPrivateInSsh:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 172.31.0.0/23
      Egress: false
      Protocol: 6
      PortRange:
        From: 22
        To: 22
      RuleAction: allow
      RuleNumber: 100
      NetworkAclId:
        Ref: AclPrivate
  AclPrivateInHttp:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 172.31.0.0/23
      Egress: false
      Protocol: 6
      PortRange:
        From: 80
        To: 80
      RuleAction: allow
      RuleNumber: 200
      NetworkAclId:
        Ref: AclPrivate
  AclPrivateInHttps:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 172.31.0.0/23
      Egress: false
      Protocol: 6
      PortRange:
        From: 443
        To: 443
      RuleAction: allow
      RuleNumber: 300
      NetworkAclId:
        Ref: AclPrivate
  AclPrivateEphemeralIn:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      Protocol: 6
      PortRange:
        From: 1024
        To: 65535
      RuleAction: allow
      RuleNumber: 400
      NetworkAclId:
        Ref: AclPrivate
  AclPrivateOutHttp:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 80
        To: 80
      RuleAction: allow
      RuleNumber: 200
      NetworkAclId:
        Ref: AclPrivate
  AclPrivateOutHttps:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 443
        To: 443
      RuleAction: allow
      RuleNumber: 300
      NetworkAclId:
        Ref: AclPrivate
  AclPrivateEphemeralOut:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      Protocol: 6
      PortRange:
        From: 1024
        To: 65535
      RuleAction: allow
      RuleNumber: 400
      NetworkAclId:
        Ref: AclPrivate
  PublicSubnet1aAclAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Ref: PublicSubnet1a
      NetworkAclId:
        Ref: AclPublic
  PublicSubnet1cAclAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Ref: PublicSubnet1c
      NetworkAclId:
        Ref: AclPublic
  PrivateSubnetAclAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet1a
      NetworkAclId:
        Ref: AclPrivate
  BastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId:
        Ref: VpcTemplate
      GroupDescription: SG for Bastion
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: bastion-sg
  ApplicationSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId:
        Ref: VpcTemplate
      GroupDescription: SG for Application
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId:
            Ref: BastionSecurityGroup
      Tags:
        - Key: Name
          Value: bastion-sg
  BastionInstance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId:
          Ref: BastionInstanceLaunchTemplate
        Version:
          Fn::GetAtt: 'BastionInstanceLaunchTemplate.LatestVersionNumber'
      SubnetId:
        Ref: PublicSubnet1a
      Tags:
        - Key: Name
          Value: bastion
  ApplicationInstance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId:
          Ref: ApplicationInstanceLaunchTemplate
        Version:
          Fn::GetAtt: 'ApplicationInstanceLaunchTemplate.LatestVersionNumber'
      SubnetId:
        Ref: PrivateSubnet1a
      Tags:
        - Key: Name
          Value: application
  BastionInstanceLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        SecurityGroupIds:
          - Ref: BastionSecurityGroup
        InstanceInitiatedShutdownBehavior: stop
        KeyName: sample
        ImageId: ami-0a2de1c3b415889d2
        Monitoring:
          Enabled: false
        CreditSpecification:
          CpuCredits: standard
        InstanceType: t2.micro
        BlockDeviceMappings:
          - DeviceName: /dev/xvda
            Ebs:
              VolumeSize: 8
              VolumeType: gp2
              DeleteOnTermination: true
  ApplicationInstanceLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        SecurityGroupIds:
          - Ref: ApplicationSecurityGroup
        InstanceInitiatedShutdownBehavior: stop
        KeyName: sample
        ImageId: ami-0a2de1c3b415889d2
        Monitoring:
          Enabled: false
        CreditSpecification:
          CpuCredits: standard
        InstanceType: t2.micro
        BlockDeviceMappings:
          - DeviceName: /dev/xvda
            Ebs:
              VolumeSize: 8
              VolumeType: gp2
              DeleteOnTermination: true
```

#最後に
cfnで何かのリソースを定義したい時は、[公式のリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)をみつつ、具体例が欲しいところはググって調べています。
sampleのテンプレートではパラメータを活用していなかったり、NACLの定義のところが冗長だったりするので、もっと簡潔にかけるようにこれから努力したいと思います。