AWSで静的サイトをさっくりと作る話
最近のお仕事ではサーバーサイドのアプリケーションを伴わない静的サイトのインフラをAWSで構築することがちょくちょくあります。シンプルなサイトから、APIサーバーが別にあるようなリッチなフロントのサイトまで、内容はそれぞれですが、基本的にはCloudFrontとS3で作ることがほとんどです。
CDNと言えば、アクセス数が非常に多いような限定的なサイトで利用されることが多いイメージがありますが、自分が静的サイトを作る際はアクセス規模の大小にかかわらずCloudFrontを前段に起きます。理由の大半はHTTPS対応のためで、Googleのランキングの影響であったり、HTTP/2に対応できるなどのメリットがあるため、POST等で暗号化しなければいけない情報を送信しない場合でもHTTPSには対応すべきかと思います。HTTPS以外の理由としては、アクセス数の大小によらず同じような構成が作りたいというのも理由の一つです。アクセス数からCloudFrontを利用するかどうかの判断をしなくてもいいので、設計時に考えることが減ります。
構成はこんな感じ。CloudFrontのオリジンとして直接S3のバケットを指定できますが、自分はS3のWeb Hostingを設定したうえで、そのURLをオリジンとして指定します。リクエストパスにファイル名が指定されなかった際にindex.htmlにアクセスさせるディレクトリインデックスの機能はWebサーバーに必須ですが、CloudFrontから直接S3をオリジンにするとこの機能が使えません。特定のファイルのみCloudFront経由にする場合は特に問題ないのですが、Webサイトをまとめてホスティングする場合は困ることが多いです。/
をリクエストされたら/index.html
を返して欲しいし、/news
をリクエストされたら/news/index.html
を返して欲しいです。これを実現するためにはS3側で単独のWebサイトとして動作して、ディレクトリインデックスの挙動をここで実現してもらう必要があるのです。
S3側に直接アクセスされないように、S3側はCloudFront以外からのアクセスは拒否したいところですが、S3へはCloudFrontの全世界にあるエッジロケーションからアクセスされるわけで、IPによるアクセス制限は不可能です。とりあえず自分はUserAgentによる制限をかけるくらいにしています。直接S3にアクセスされても、負荷がかかるわけでなければ困ることはそれほど無いので…。気になる人は追加でバケット名を推測されにくいものにすると、S3側のエンドポイントにアクセスされることはほとんど無くなるはず。だいたいこんな感じのバケットポリシー書きますかね。
{ "Id": "Policy1524134364829", "Version": "2012-10-17", "Statement": [ { "Sid": "Stmt1524134363397", "Action": [ "s3:GetObject" ], "Effect": "Allow", "Resource": "arn:aws:s3:::<バケット名>/*", "Condition": { "StringEquals": { "aws:UserAgent": "Amazon CloudFront" } }, "Principal": "*" } ] }
CloudFrontのTTLの設定ですが、静的サイトであれば更新頻度からDefault TTLを適切に設定しておけば問題は無いかと思います。S3のファイルを置き換えた後、5分くらいで更新が反映されれば良ければ、Default TTLに300秒を設定しておけばいいと思います。Minimum TTLやMaximum TTLはデフォルトのままで問題ないです。S3の設定で明示的にCache-ControlヘッダやExpiresヘッダを付与しない限りは影響しません。
HTTPSの証明書はCertificate Managerで作ります。CloudFrontで利用する場合はus-east-1リージョンで証明書を作らないとCloudFront側から認識されないので、その点だけ気をつけてください。CloudFront側ではHTTPにアクセスされたらHTTPSにリダイレクトする設定が出来るので、こちらを設定して強制的にHTTPSでのみアクセスさせるようにします。
上記の環境を作成するterraformの設定ファイルを作ったので、こちらを実行すればぽんぽんと静的サイトの環境が作れます。
自動化するに当たって、Route 53のHosted Zoneだけは手動で作る必要がある仕様にしました。同じドメインで複数のサイトを作るケースもあるので、ドメインの管理自体はterraformで管理しないようにしています。環境を削除する際にHosted Zoneごと消されると結構な事故になりそうな気もするので。
また、ステージング環境やリリース前本番環境など、IPによるアクセス制限をかけたい場合はWAFを使います。このあたりのお話も機会があれば。