CloudFormationでリソース管理はじめよっ！

# 概要
ServerlessFrameworkを利用する中で、内部的にCloudFormationを利用していたので改めて調べてみたり、自分なりに「ここめっちゃいい！」と思ったところをまとめました
触り部分が多いので初心者向けの記事ですが読んでもらえればと思います :pray:

## CloufFormationって
CloudFormationで利用するテンプレート(JSONもしくはYAML)にAWSリソースを記述することでテンプレートにしたがってリソースの作成・更新削除といった管理ができるサービスです

たとえばEC2を作成して、Apacheサーバーを建てて、ユーザーからもアクセスできるように...といった内容をAWSコンソールを利用せずにCloudFormationテンプレートを作成し、そのテンプレートファイルをデプロイすることで実行できます

## 個人的に感じるメリット
これはCloudFormationに限った話ではありませんが、CloudFormationはIaC(Infrastructure as Code)なのでそれらで述べられている特徴を持っています

特筆して感じるメリットとしては
「**インフラ変更差分に対してメッセージを残しやすい**」ことです

具体的な仕事の例を上げると
前提: EC2を利用してWebアプリケーションを管理している
一般ユーザーからも閲覧できる本番環境と
本番環境と同じ構成で社内アクセスのみを許可したステージング環境が存在する
対応依頼: 新しく入った開発パートナーさんにもステージング環境にアクセスできるようにしたい
対応方針: セキュリティグループに許可するIPアドレスを追加する

CloudFormationならコード管理できるため、Gitなどのバージョン管理でコメントや変更意図を含めて管理できます
そのため数年後に「このIPアドレスって誰が使っているものなのか」と発見したときに`git log`などから追跡が可能です

また**再現性**が持てるところも素晴らしいです
大量のユーザーアクセスを捌けるようなAPIGateway + SQS + LambdaといったようなServerless構成を自分がCloudFormationを利用して作成しました
友人にも同様のシステムを提供しようと思ったときにCloudFormationのテンプレートを共有するだけで各々のアカウントで同一のAWS構成が作られます

この再現性の部分をAWSコンソールで行おうとしたときに1つのミスもなく、まったく同一の構成にするのはとても難しいです
これはメモを用いずに居酒屋の10人席へ注文を伺い1つの誤りもなく注文を聞き取り、伝えるくらい難易度が高いです

また各種CloudFormationのテンプレートを利用してデプロイする単位はスタックといいます
(スタックは複数のAWSリソースを管理する単位のようなものと認識するほうが考えやすい)

## どうやって使うのか
このスタックを作成する方法は大きく3つあります
1. CloudFormationのテンプレートを自分で作成する
2. サンプルテンプレートを利用する
3. CloudFormationのデザイナーを利用してGUIで構成を作成する

個人的には1の自身でテンプレートを作成することが多いかと思います

![スクリーンショット 2023-12-04 23.34.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199819/d126136e-b341-98e3-26d0-2182be157280.png)

## 実際に書いてみる
今回はサンプルとしてEC2上にapacheサーバーを建てて自分のパソコンからアクセスできるようにしてみましょう

下記のようなテンプレートを定義します
行っていることとしては
- Resourcesセクション内でAWSのリソースを作成している
  - MyVPC
    MySubnet
    MyInternetGatewayなど...
- 各種リソース名配下では[リソースタイプ名][リソースのプロパティ]を定義
  - リソースタイプ名は[ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)にすべて書いてある
  - またリソースのプロパティはリソースごとに異なるのでこちらもドキュメントを見るのが一番良い
- Outputsセクション内でスタックが出力する項目について出力している
  - 今回は MyVPCのvpcid MySubnetのsubnetIdを出力
- `!Ref`と記載することで同じテンプレート内で定義している他のリソースを参照
  - `VpcId: !Ref MyVPC`とすることでMyVPCのvpcIdに置き換わる
  - Ex) `VpcId: !Ref MyVPC` → `VpcId: vpc-xxxxxxxx`

```yaml:vpc-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to deploy an Apache server with a VPC.

Resources:
  MyVPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: MyVPC

  MySubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: MySubnet

  MyInternetGateway:
    Type: 'AWS::EC2::InternetGateway'

  GatewayAttachment:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref MyInternetGateway

  MyRouteTable:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref MyVPC

  MyRoute:
    Type: 'AWS::EC2::Route'
    Properties:
      RouteTableId: !Ref MyRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway

  SubnetRouteTableAssociation:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref MySubnet
      RouteTableId: !Ref MyRouteTable
Outputs:
  VPCId:
    Description: The ID of the VPC
    Value: !Ref MyVPC
  SubnetId:
    Description: The ID of the subnet
    Value: !Ref MySubnet
```

実際にデプロイしてみる
```bash
# create-stack で template-bodyにあるファイルを元にCloudFormationでスタックを作成
$ aws cloudformation create-stack --stack-name VPCStack --template-body file://vpc-template.yaml
```

AWSコンソール上ではどのリソースがどんなステータスでどういう状況となっているかが確認できます
create-stackが成功するとスタックのステータスが`CREATE_COMPLEATE`になり、コンソール上もしくは`describe-stacks`の実行結果で確認できます

![スクリーンショット 2023-12-04 23.56.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199819/d3143561-356f-9d26-8c20-eccf579b3a0e.png)

続いてEC2のデプロイを行いますがVPCのIDとSubnetのIDがほしいのでメモしておきます
`describe-stacks`を実行してOutputsするように設定した各種IDを控えておきます
VPCIdは`vpc-AAAAAAAAA`SubnetIdは`subnet-BBBBBBB`だとわかります
```bash
$ aws cloudformation describe-stacks --stack-name VPCStack
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXX:stack/VPCStack/XXXX-YYY3-XXXX-XXXX-ZZZZZ",
            "StackName": "VPCStack",
            "Description": "CloudFormation template to deploy an Apache server with a VPC.",
            "CreationTime": "2023-12-04T14:41:46.862000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "VPCId",
                    "OutputValue": "vpc-AAAAAAAAA",
                    "Description": "The ID of the VPC"
                },
                {
                    "OutputKey": "SubnetId",
                    "OutputValue": "subnet-BBBBBBB",
                    "Description": "The ID of the subnet"
                }
            ].....
```

続いてApache用のCloudformationのテンプレートファイルを作成します
VPCのテンプレートと異なる点としては**Parameters**セクションが追加されています

これはコマンド実行時のオプションに値を渡せます
今回は`VPCId`としているので `--parameters ParameterKey=VPCId,ParameterValue=vpc-AAAAAAAAA`とすることで`!Ref`で参照できます

```yaml:apache-server-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to deploy an Apache server with restricted access.

Parameters:
  VPCId:
    Description: The ID of the VPC
    Type: String

  SubnetId:
    Description: The ID of the subnet
    Type: String

  AllowedIPAddress:
    Description: IP address allowed to access the Apache server.
    Type: String

Resources:
  MyInstance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-012261b9035f8f938
      InstanceType: t2.micro
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo "<html><body><h1>Welcome to Apache Server</h1></body></html>" > /var/www/html/index.html

  InstanceSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable SSH and HTTP access on the configured port
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref AllowedIPAddress
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref AllowedIPAddress
      VpcId: !Ref VPCId
```

実際にデプロイしてみましょう
```bash
aws cloudformation create-stack \
  --stack-name ApacheServerStack \
  --template-body file://apache-server-template.yaml \
  --parameters ParameterKey=VPCId,ParameterValue=vpc-AAAAAAAAA \
                ParameterKey=SubnetId,ParameterValue=subnet-BBBBBBB \
                ParameterKey=AllowedIPAddress,ParameterValue=127.0.0.1/32 # ここだけ自分のIPアドレスに変更する
```


デプロイが完了したらブラウザからEC2にアクセスするかcurlなどを叩いて確認しましょう

![スクリーンショット 2023-12-05 0.14.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199819/4afb3d2b-d401-670e-403a-46fa6513797e.png)


またCloudFormationのテンプレートを変更した場合は`update-stack`, スタックで管理していたリソースが不要になったら`delete-stacks`を実行して適宜削除しましょう

## 終わり
ServerlessFramework経由でCloudFormationを利用することはありましたが、改めて確認してみました

各種templateはサンプルコードとして↓のリポジトリに載せています

https://github.com/yothio/cloudformation-sample