CloudFormation であら便利に静的サイトを作る

[cloudpack あら便利カレンダー 2017](https://cloudpack.github.io/benri2017/) の18日目です。

AWS で静的ウェブサイトといえば S3 + CloudFront ですが、その構成を CloudFormation で作る際に**あら便利**な Tips を入れていこうという記事です。

# とりあえず起動してみる

AWS コンソールにログインした状態で、[このリンク](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?templateURL=https:%2F%2Fs3.amazonaws.com%2Fcfn-staticweb-example%2Fcfn.json)から Stack を起動できます。

Stack が起動したら、 `Outputs` の `URL` にアクセスすれば CloudFront/S3 に置かれた静的ページが閲覧できるはずです。

このテンプレートが置いてあるバケットは誰でも List/Get できるので、

```shell-session
$ aws s3 sync s3://cfn-staticweb-example/ .
```

すれば CloudFormation テンプレートとカスタムリソース用の Lambda ファンクションのソースが入った zip が落ちてきます。そのあとに `aws cloudformation create-stack` コマンドで起動するもよし、内容を見るも改造するもよしです。

さらに、このファイル一式を生成しているコードは　[y13i/cfn-staticweb-example](https://github.com/y13i/cfn-staticweb-example) にあります。 [Serverless Framework](https://serverless.com/) を使いつつ、 `serverless deploy` はせず、 `serverless package` して出力されたファイルを一部改変して S3 バケットに上げるということをしています。

# CFn でどんな**あら便利**があるの？

## CloudFront の利用する・しないの切り替え

CloudFront Distribution は作成にかなり時間が掛かります。このテンプレートでは CloudFront を使わないことも選べるようにしました。

CloudFormation の [Conditions](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html) を使い、 [`UseCloudFront` という Parameter](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L61) が [`true` の時のみ Distribution を作成](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L140)します。さらに S3 Bucket Policy で読み取りを許可する参照元は

- `UseCloudFront` が `true`
    - CloudFront からのみ可（Origin Access Identity を使用）
- `UseCloudFront` が `false`
    - どこからでも可

と[切り替わるように](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L98)しています。

## CloudFront の各種パラメーターの切り替え

[ACM (AWS Certificate Manager)](https://aws.amazon.com/jp/certificate-manager/) で発行した証明書を [`AcmCertificateArn` パラメーター](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L41)で[指定した場合](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L65)は Distribution で [`ViewerCertificate` として使用する](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L206)ようにしています。

指定しない場合（値が空文字）の場合は [`AWS::NoValue` 擬似要素](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html#cfn-pseudo-param-novalue)を使って、そもそも `ViewerCertificate` が指定されていないように振る舞うようになっています。

同様に、 [Distribution Alias も Parameter で指定された場合のみ設定する](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L192)テンプレートになっています。

## Origin Access Identity の作成

Origin Access Identity は普通には CloudFormation で作成できません！

なんで対応していないのか疑問に思うところですが、文句を言ってもしかたないので [Custom Resource](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) を[使って](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L108) Lambda Function に[作成させて](https://github.com/y13i/cfn-staticweb-example/blob/master/handlers/cfnOai.ts)います。

## インデックスドキュメントの生成

CloudFormation でサポートされている S3 のリソースは [Bucket](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) 、 [BucketPolicy](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-s3-policy.html) のみです。バケットの内容物は CloudFormation では管理できません。

しかし、前項と同様にカスタムリソースを[使えば](https://github.com/y13i/cfn-staticweb-example/blob/master/serverless.yml#L121)どうにでもなります。

ここでは、 Parameter で受け付けた `IndexDocumentTitle` と `IndexDocumentBody` を [ejs](https://www.npmjs.com/package/ejs) テンプレートに流し込む処理をして HTML を生成し、バケットに置くという[処理をさせてみました](https://github.com/y13i/cfn-staticweb-example/blob/master/handlers/putIndexObject.ts)。

# まとめ

Lambda とその周辺環境がそこそこ枯れてきたことで、 Lambda-backed Custom Resource がだいぶ使いやすくなったと感じます。つまり、 CloudFormation の使い勝手も増しているということです。

地味ながら進化を続ける CFn を**あら便利**に使っていきましょう。