AWS CloudFormation入門

# AWS CloudFormation入門

## 自己紹介

はじめまして!
株式会社LIFULLの吉永です。
2020年8月にLIFULLに入社し、はじめて任せていただいた仕事がとあるサイトのAWS構成の刷新業務だったので、AWSに関する知見を得る為、また自分自身の業務の忘備録として本記事を執筆しています。
AWSはLIFULL入社以前は少しだけ触った程度の浅い知識だったので、色々記述内容に不備があるかもしれませんが、その際はコメントなどでフィードバックいただけると幸いです。
CloudFormationって何？って方向けに、概要の理解、実際の例、実行してみるまでを一気に紹介していきたいと思います！

## CloudFormationとは？

- AWSのシステム構成をJSONやYAMLなどのコードで記述して、テンプレート化し、構成管理、修正、再利用を簡単にする為のサービス。
- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html)

## CloudFormationを利用するメリット

- AWSの多種多様なマネージドサービスの構成をコードで管理することで、AWS環境のテンプレート化が容易にできる。
- 構成をコード管理できるので、GitなどのVCSで履歴を容易に管理することができる。
- 環境構築手順書が基本的に不要になる。
- 環境の再現、別環境への再利用による再構築などの操作がとても簡単にできる。具体的には検証や開発を行う為のdevlop環境と本番環境であるproduction環境の構築を一つのテンプレートファイルで管理でき、それぞれの環境毎に異なる設定などもコード内に変数やパラメーターとして埋め込むことが可能になる。

## CloudFormationを利用するデメリット

- AWSマネージドサービスの構成管理をする為のサービスなので、GCPやAzureなどの他社クラウドサービスに構成管理コードの転用はできない。
- 公式ドキュメントは基本的にかなり親切に記載してあるが、細かい設定値などをどのようにコードで設定したら良いのかを探すのに少し時間がかかる。GUIの設定値とコード上の設定値の名前が必ずしも一致しているわけではないので、ある程度は内容を察して設定する必要がある場合もある。

## CloudFormation実用例

今回の業務で作成した構成をそのまま載せることはできないので、業務で得た知識から一部抜粋して、シンプルな構成の静的サイトをAWSで公開する為の構成のコードを載せたいと思います。

### 今回サンプルで作るAWS構成図

![AWS構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/42d4fb9c-3f87-8534-5ad6-e31c821a5565.jpeg)

今回はシンプルな静的サイトを公開する際のシンプルな構成にてCloudFormationのコードをYAMLで作成したいと思います。
CloudFormationはJSONでも作成できるのですが、YAMLで作成するとコメントが利用できるので、YAMLを用いたいと思います。

### 構成の細かな設定について

- S3はパプリックリードを全てブロックして、CloudFrontのOAI経由でのみアクセスできるようにする。
- 上記を実現する為に、S3のバケットポリシーとCloudFrontOriginAccessIdentityも一緒に作成する。

### CloudFormationのコード

```yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  BucketName:
    Type: String

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: LogDeliveryWrite
      BucketName: !Ref BucketName
      WebsiteConfiguration:
        IndexDocument: "index.html"
        ErrorDocument: "error.html"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: TRUE
        BlockPublicPolicy: TRUE
        IgnorePublicAcls: TRUE
        RestrictPublicBuckets: TRUE

  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    DependsOn:
      - S3Bucket
      - CloudFrontOriginAccessIdentity
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: 2008-10-17
        Statement:
          - Action:
              - s3:GetObject
            Effect: Allow
            Resource: !Sub "${S3Bucket.Arn}/*"
            Principal:
              AWS: !Sub "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}"

  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: "access identity"

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn:
      - S3Bucket
      - CloudFrontOriginAccessIdentity
    Properties:
      DistributionConfig:
        Enabled: true
        DefaultCacheBehavior:
          AllowedMethods:
            - HEAD
            - GET
            - OPTIONS
            - PUT
            - POST
            - PATCH
            - DELETE
          CachedMethods:
            - HEAD
            - GET
          DefaultTTL: 3600
          MaxTTL: 86400
          MinTTL: 0
          TargetOriginId: !Sub "${BucketName}-Origin"
          ViewerProtocolPolicy: https-only
          ForwardedValues:
            QueryString: false
        IPV6Enabled: false
        HttpVersion: http2
        DefaultRootObject: index.html
        ViewerCertificate:
          CloudFrontDefaultCertificate: true
        Origins:
          - Id: !Sub "${BucketName}-Origin"
            DomainName: !Sub "${BucketName}.s3.${AWS::Region}.amazonaws.com"
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
        CustomErrorResponses:
          - ErrorCachingMinTTL: 0
            ErrorCode: 403
            ResponseCode: 404
            ResponsePagePath: "/error.html"
          - ErrorCachingMinTTL: 0
            ErrorCode: 404
            ResponseCode: 404
            ResponsePagePath: "/error.html"
          - ErrorCachingMinTTL: 0
            ErrorCode: 500
            ResponseCode: 500
            ResponsePagePath: "/error.html"
```

## CloudFormation YAMLの実行手順

最後に作成したYAMLをAWSで実行する手順を下記にまとめます。

1. AWSコンソールにログインする。
1. トップ画面のサービス検索窓に「cloudformation」と入力して、候補からCloudFormationをクリックする。
![トップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/e857235f-c077-d0e5-ad53-40d79c931c8a.png)
※予めCloudFormationを実行するリージョンがどこになっているかは確認しておく！
1. スタックの作成を押す。
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/78999c33-3ce0-ddf5-d569-d0d5004cdb65.png)
1. 画面下部のテンプレートファイルのアップロード下のファイルを選択を押す。
※CloudFormationのYAMLファイルを予めローカルに保存しておく
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/734b019b-c281-7a3c-5d94-ae801cc04497.png)
1. 次へを押す。
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/ca6b87d8-2eaf-47f9-c4ec-0a708ba2f102.png)
1. スタックの名前、パラメータを入力して次へを押す。
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/9294f388-b85a-7caa-f858-535561ae0856.png)
1. 特に何も設定を変更せずに次へを押す。
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/f0ca5399-14fa-04e9-bf31-74f993b1c51f.png)
1. 次の画面でも、特に何も設定を変更せずに次へを押す。
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/d974255d-70bc-1981-b3f9-55aa6c8581f8.png)
1. 実行が完了するとキャプチャ画像のような表示になる。
![CloudFormationトップ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/c868e945-f5b3-e698-b0f2-30c47021158f.png)
仮にYAMLに何か不備があった場合はエラーメッセージが表示される。

## 最後に

CloudFormationはググると沢山の例が出てくるのと、[AWS CloudFormation のテンプレート](https://aws.amazon.com/jp/cloudformation/resources/templates/)のように公式ドキュメントにも沢山のテンプレートや設定例が載っているので、欲しい情報を探すのにはあまり苦労しません！
反面、少し細かな設定をコードでどうやったらいいのか？などは実際に設定して動かしてみないと分からないといったこともあるかと思うので、はじめのうちは検証環境で色々試して、動作確認をすることで、より理解が深まっていくと思います！
といったところで、本日は以上となります。
最後までご覧いただきありがとうございました！
それではまた次の記事でお会いしましょう。