CloudFormation入門　～いろいろなS3バケットを作ってみる～

# 目的
　IaC(Infrastructure as Code)の一歩を踏み出すために、CloudFormationでS3バケットを作ってみました。
　CloudFormation入門でEC2インスタンスを作ることが多いような印象を受けますが、EC2インスタンスを立ち上げるにはVPCやセキュリティグループなど設定すべき項目が多いので、S3のほうが敷居が低いかなと個人的には感じます。
　いつもはマネジメントコンソールぽちぽち人間ですが、練習も兼ねてCLIを使ってみます！

# そもそもCloudFormationって何？
　JSONまたはYAML形式で記述されたテンプレートを用いてスタックを作成し、そのスタックからサービスやポリシーといった各種リソースを生成するAWSのサービスです。
　CloudFormation自体の利用は無料ですが、CloudFormationで生成されたリソースの利用料金は発生します。
　スタックから複数リソースを生成した場合でもまとめて削除されますので、リソースが不要になった場合にもすぐに対応できます。
　テンプレートを使ってリソースを生成すると様々なメリットがあります。
#### 1. 以前生成したリソースを簡単に再現できる
　前に作ったものとほとんど同じ設定のリソースを作るときに、一から作り直さなくてもOKです。
　また、別リージョンや別アカウントで同じ設定のリソースを使いたいときにも、CloudFormationを使えばパラメータ（※後で説明します）を変更するだけで対応できます。　　
#### 2. GUIの変更に左右されない
　GUIでの操作はとっつきやすいですが、AWSは結構GUIの変更が多いです。
　テンプレートという形で残っていると、ほかの人に引継ぎをするときなど恩恵を得られる機会も多いはずです。
#### 3. リソース生成前に設定内容を確認できる
　当たり前といえばそうですが、AWSでリソースを作るとき、1つチェックを忘れただけで、本来想定していた設定と違うものになってしまいます。リソース生成直前に確認画面があるのですが、そういったミスは往々にして生成後にやっと気づくものです。
　CloudFormationでリソースを作る場合は、テンプレートに設定内容が書いてあるので、リソース生成前にテンプレートを共有してほかの人の目で確認してもらうこともできます。

# CloudFormationのテンプレートについて
　テンプレートはJSON形式でもYAML形式でも同様の動作をします。YAMLだと短縮記法が使えるのですが、JSONのほうが慣れているという極めて個人的な理由でJSON形式のテンプレートを書いていきます。
　テンプレートを構成する要素は以下のとおりです。必須と書いてある項目以外は設定しなくてもスタックは作成できます。今回は入門編なので設定しない項目も多いです。

#### AWSTemplateFormatVersion（必須）
　AWS CloudFormationテンプレートバージョンを設定。
　現時点ではとりあえず`"2010-09-09"`という値を入れておく認識でOKです。

#### Description
　テンプレートの説明。
　どういう構成のテンプレートか、いつ使うテンプレートかなど、コメントのように使います。

#### Metadata
　CloudFormationに関するメタデータを定義。
　今回は使いません。

#### Parameters
　テンプレートに渡すパラメータを設定。
　スタック作成時にテンプレートのパラメータを参照します。プログラミングの変数のようなイメージです。

#### Rules
　テンプレートに渡されたパラメータまたはパラメータの組み合わせを検証するルールを設定。
　今回は使いません。

#### Mappings
　キーとそれに関連する値のマッピングを作成。
　作成したマッピングはCloudFormationの組み込み関数`"Fn::FingInMap"`を使って呼び出せます。
　今回は使いません。

#### Conditions
　条件式の定義。
　例えば、スタックがテスト環境用か本番環境用かで、生成するリソースを変更することができます。
　今回は使いません。

#### Transform
　サーバレスアプリケーションの場合、使用するAWS SAM（AWS Serverless Application Model）のバージョンを指定。
　SAMはCloudFormationの拡張機能で、サーバレスアプリケーションの作成に特化したものです。
　今回は使いません。

#### Resources（必須）
　スタック生成時に起動するリソースに関する設定。
　テンプレートの中心部ともいえる項目です。
　S3の場合、バケット名も、バケットポリシーも、ライフサイクルルールもすべてこの項目で設定します。

　Resourcesの項目は必須項目なので、さらに掘り下げていきます。
　Resourcesセクションの構文は以下のとおりです。
```
"Resources" : {
    "Logical ID" : {
        "Type" : "Resource type",
        "Properties" : {
            ～各種プロパティの設定～
        }
    }
}
```

##### Logical ID
　テンプレート内で一意のIDを設定します。
　"S3Bucket"や"S3WebhostingPolicy"など、どういった内容のリソースを設定するかわかるようにします。
##### Type
　Resource typeは、`"AWS::S3::Bucket"`や`"AWS::IAM::Role"`など、決まった形式で記述します。
##### Properties
　Logical IDに応じて詳細な設定を行います。
　例えば、"S3Bucket"の場合はバケット名やライフサイクルルールを設定し、"S3WebhostingPolicy"の場合はポリシー名やポリシーの内容を設定します。

##### Outputs
　スタックのプロパティを確認すると返される値を設定します。
　例えば、スタックから生成されたS3バケット名を返すことができます。




# いろいろなS3バケットを作ってみる
### シンプルなバケットを作る
　必須項目である`"AWSTemplateFormatVersion"`と`"Resources"`のセクションだけ記述して、バケットを作ってみます。
　テンプレートは以下のとおりです。特別な設定はないプライベートなバケットが作成できます。
```:s3_simplebucket.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "S3Bucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccessControl": "Private",
        "BucketName": "cfn-simple-buckets-20220909"
      }
    }
  }
}
```
　このテンプレートを使ってCLIからCloudFormationを実行します。CLIコマンドはこちらです。

```:CloudFormationを実行するCLIコマンド
aws cloudformation deploy --stack-name s3-simplebucket ^
--template テンプレートファイルのパス/s3_simplebucket.json
```
〇 `deploy`：スタックを新規作成時および更新時に使うコマンド
〇 `--stack-name`：スタック名を指定するオプション。必須
〇 `--template`：テンプレートファイルのパスを指定するオプション。必須

　ちなみに、`^`はコマンドが長いので入れている記号です。OSによって記号が異なるので注意です。(参考：[AWS CLI：長いコマンドを途中で改行する方法](https://blog.toyokky.com/entry/2021/04/18/210259)）


### バケット名をパラメータで指定する
　先ほどはとりあえずCloudFormationを使ってみようということで、テンプレートに最低限の項目を記述しましたが、テンプレートに直接バケット名を記述してしまうと使い回せません。そこで、テンプレートの要素の一つである`"Parameters"`セクションを記述していきます。

　テンプレートはこうなります。
```:s3_simpleparam.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "S3BucketName": {
      "Type": "String"
    }
  },
  "Resources": {
    "S3Bucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccessControl": "Private",
        "BucketName": {
          "Fn::Sub": "${S3BucketName}"
        },
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": "True",
          "BlockPublicPolicy": "True",
          "IgnorePublicAcls": "True",
          "RestrictPublicBuckets": "True"
        }
      }
    }
  },
  "Outputs": {
    "BucketName": {
      "Value": {
        "Ref": "S3Bucket"
      }
    }
  }
}
```

#### Parametersセクション
　Parametersセクションでは、パラメータの型などを設定します。パラメータの型として設定できるものはいろいろとありますが、上記テンプレートのようにString型を設定すると、自由な文字列をパラメータに渡せます。
　参考：[CloudFormationのParametersの入力方法の違いをデータ型ごとに一覧にしてみた](https://dev.classmethod.jp/articles/list-of-cloudformation-parameters-by-data-type/)

#### CloudFormation組み込み関数

##### Fn::Sub
　CloudFormation実行時にパラメータに渡された値をそのまま使う関数です。

##### Ref
　リソースの論理名（ResourcesセクションのLogical ID）を指定します。リソースの種類によって、Refの戻り値が異なります。今回のテンプレートではS3を指定しているので、S3のバケット名が返ってきます。
　参考：[Refの公式リファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html)


#### Outputsセクション
　今回のテンプレートでは作成したS3バケットのバケット名をOutputsの値として設定しています。CloudFormationの「出力」タブから確認できます。
![s3-outputs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/f2824973-fd97-e56e-03ef-afa61c895dce.png)



#### PublicAccessBlockConfiguration
　Parametersセクションに追加されている要素ですが、マネジメントコンソールでいうところの下記画像の設定です。
![S3-block-public-access.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/7a44ac70-4a2f-ee7c-e5ee-691b61e5eb38.png)

　CLIのコマンドはこうなります。
```
aws cloudformation deploy --stack-name [スタック名] ^
--template [テンプレートファイルのパス]/s3_simpleparam.json ^
--parameter-overrides S3BucketName=cfn-simpleparam-20220909
```
〇 `--parameter-overrides`：「 パラメーターのキー = 値 」という形で、パラメーターに値を渡すオプション。


### 複数のバケットを作る
　1つのテンプレートから複数のバケットを作ることもできます。

　テンプレートはこちら。`${AWS::AccountId}`は、AWSのアカウントID(数字12桁)を参照します。
```:s3_two_buckets.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "two s3 buckets template",
  "Parameters": {
    "Param1": {
      "Type": "String"
    },
    "Param2": {
      "Type": "String"
    }
  },
  "Resources": {
    "S3Bucket1": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccessControl": "Private",
        "BucketName": {
          "Fn::Sub": "${Param1}-${AWS::AccountId}"
        }
      }
    },
    "S3Bucket2": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccessControl": "Private",
        "BucketName": {
          "Fn::Sub": "${Param2}-${AWS::AccountId}"
        }
      }
    }
  },
  "Outputs": {
    "S3BucketName1": {
      "Value": {
        "Ref": "S3Bucket1"
      }
    },
    "S3BucketName2": {
      "Value": {
        "Ref": "S3Bucket2"
      }
    }
  }
}
```

　CLIのコマンドはこちらです。
```
aws cloudformation deploy --stack-name [スタック名] ^
--template [テンプレートファイルのパス]/s3_simpleparam.json ^
--parameter-overrides Param1=first Param2=second
```
　複数のパラメータを渡すときもカンマなどで区切らず、スペースを入れるだけでOKです。


### 静的WebホスティングできるS3バケットを作る

　テンプレートはこちらです。

```:s3_webhosting.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "simple s3 webhosting template",
  "Parameters": {
    "BucketName": {
      "Type": "String"
    }
  },
  "Resources": {
    "S3Bucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccessControl": "PublicRead",
        "BucketName": {
          "Ref": "BucketName"
        },
        "WebsiteConfiguration": {
          "IndexDocument": "index.html",
          "ErrorDocument": "error.html"
        }
      }
    },
    "BucketPolicy": {
      "Type": "AWS::S3::BucketPolicy",
      "Properties": {
        "PolicyDocument": {
          "Id": "WebhostingPolicy",
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "PublicReadBucketObjects",
              "Action": "s3:GetObject",
              "Effect": "Allow",
              "Resource": {
                "Fn::Sub": "arn:aws:s3:::${BucketName}/*"
              },
              "Principal": "*"
            }
          ]
        },
        "Bucket": {
          "Ref": "S3Bucket"
        }
      }
    }
  },
  "Outputs": {
    "WebsiteURL": {
      "Value": {
        "Fn::GetAtt": [
          "S3Bucket",
          "WebsiteURL"
        ]
      },
      "Description": "URL for website hosted on S3"
    }
  }
}
```

#### ResourcesセクションのProperties下の設定
① "AccessControl"を"PublicRead"にします。
② "WebsiteConfiguration"にURLを叩いたときに表示されるページの設定をします。
　今回の場合は、URLを叩いてすぐに「index.html」を、URLを叩いてエラーだった場合に「error.html」を表示させます。

#### バケットポリシーを設定
　GetObjectを許可します。（マネジメントコンソールやCLIから静的Webホスティングをするバケットを作成する場合と同じ内容です）

#### 組み込み関数Fn::GetAtt
　["リソースの論理ID", "リソース固有の属性"]の形で、リソースの属性ごとに定められた戻り値を返す組み込み関数です。S3のWebsiteURL属性は静的Webホスティングする際に使うURLを返します。




### ライフサイクルルールを設定したバケットを作る
```:s3_lifecycle.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "S3BucketName": {
      "Type": "String"
    },
    "BucketLabel": {
      "Type": "String"
    }
  },
  "Resources": {
    "S3Bucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccessControl": "Private",
        "BucketName": {
          "Fn::Sub": "${S3BucketName}"
        },
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": "True",
          "BlockPublicPolicy": "True",
          "IgnorePublicAcls": "True",
          "RestrictPublicBuckets": "True"
        },
        "LifecycleConfiguration": {
          "Rules": [
            {
              "Id": {
                "Fn::Sub": "${BucketLabel}-life-cycle-rule"    
              },
              "Status": "Enabled",
              "ExpirationInDays": 365,
              "Transitions": [
                {
                  "StorageClass": "STANDARD_IA",
                  "TransitionInDays": 30
                }
              ]
            }
          ]
        }
      }
    }
  },
  "Outputs": {
    "BucketName": {
      "Value": {
        "Ref": "S3Bucket"
      }
    }
  }
}
```

#### LifecycleConfiguration
　このテンプレートでは、S3にオブジェクトがアップロードされてから30日後に標準IAストレージに移行して、365日後に削除されます。


### バージョニングを有効化したバケットを作る
　テンプレートのResourcesセクションのProperties以下に`"VersioningConfiguration": {"Status": "Enabled"}`という項目を追加します。




# 感想
　慣れるまで少し時間がかかりましたが、いくつかバケットを作ってみてCloudFormationでのリソース生成の流れを把握できました。CloudFormationはとんでもなく難しいイメージがありましたが、簡単な構成であればどうにかテンプレートを作れそうだなと自信になりました。
　今後CloudFormationの理解を深めるにあたって、各サービスやそもそもインフラ周りの知識をつけていく必要があるなとは感じましたが、それに加えて英語のリファレンスをすらすらと読む力もいるなぁと思いました。(公式のサンプルテンプレートが日本語対応していないとは…)
　いろいろと課題はありますが、EC2やRDS、さらにSAMを使ってサーバレスアプリケーションもテンプレートから作ってみたいです！
　


# 参考資料
#### 公式リファレンス
・[AWS::S3::Bucketに関する公式リファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html)
・[CloudFormationの組み込み関数に関する公式リファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)
・[テンプレートの分析に関する公式リファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-anatomy.html)

#### CloudFormationでS3バケットを作成する方法
・[CloudFormationでいろいろなS3バケットを作成してみた](https://dev.classmethod.jp/articles/cloudformation-s3bucket-type/)
・[[小ネタ]S3のライフサイクル設定例をCloudFormationでやってみる](https://dev.classmethod.jp/articles/s3-lifecycle-example-cfn/#toc-3)

#### CloudFormationを操作するCLIコマンド
・[CFnのテンプレート開発時に便利なAWS CLIコマンドについて](https://blog.serverworks.co.jp/2020/08/14/190000)
・[CloudFormationの全てを味わいつくせ！「AWSの全てをコードで管理する方法〜その理想と現実〜」 #cmdevio](https://dev.classmethod.jp/articles/aws-all-iac/)