とても遅刻してしまいましたが、この記事はDeNA 25新卒 Advent Calendar 2024の4日目の記事です。他の方々の記事もぜひご覧ください!
Webサイトをホスティングする場合、いろいろな手段が考えられます。例えばFirebase HostingやGitHub Pages、Vercelなどなど。そのうちの1つにAWSのCloudFrontとS3を使った方法が考えられます。
S3のパブリックアクセスでファイルを配信する方法もありますが、この記事ではCloudFront経由でアクセスする方法にします。
CloudFrontを使う分、追加で料金がかかる可能性があります。詳細な料金体系は、AWS公式ドキュメントなどをご確認ください。
この記事では、その構成で手軽にWebサイトを量産する(サイト数を増やす)ために、IaCツールであるTerraformで構成を用意していきたいと思います。
先にまとめ
コードの全文を、以下のリポジトリに公開しています。
実装
実装方針
まず、今回の構成では独自ドメインはAWS以外(CloudFlareやお名前.comなど)で管理しているものと想定しています。
また、配信するWebサイトはClient Side Rendering(CSR)やStatic Site Generation(SSG)のような形式を想定しており、記事内の設定はいわゆるSPAを想定して記述しています。他の構成などで利用する際は、適宜該当箇所を修正してください。
実装の説明は以下の手順で進めます。
- Terraformの基本設定
- HTTPS配信ためのSSL証明書の設定(ACM)
- S3の設定
- CloudFrontの設定
- applyの手順
Terraformの基本設定
まずはトップディレクトリにmain.tf
を作成して以下を記述します。
module "example_site" {
source = "./modules"
domain = "example.mochi-yu.com"
env = "prod"
}
terraform {
backend "s3" {
region = "ap-northeast-1"
encrypt = false
bucket = "example"
key = "static-site/prod"
}
}
普通、ここにproviderの設定なども書くかと思いますが、今回は後述のとおりリージョンを複数使う関係で、ここには書かずモジュール内にproviderの設定を書くようにしました。
それから実装の本体を置くmodules
フォルダを作成しておき、variables.tf
に以下を記述しておきます。
variable "domain" {
type = string
}
variable "env" {
type = string
}
SSL証明書の設定(ACM)
配信するサイトはHTTPSでの通信に対応したいため、まずはSSL証明書を発行します。CloudFrontでACMのSSL証明書を使用する場合には、証明書をバージニア北部のリージョンで発行する必要があります。
ただ、S3などは東京リージョンで作ったほうが、他のリソースの管理とも同じで済むので個人的には嬉しいです。そこで、ACMのSSL証明書発行だけはバージニア北部のリージョンで行い、他のリソースは東京リージョンで作成するように設定します。
リージョンの設定は、modules/main.tf
に以下を記述します。use1
というエイリアスでバージニア北部のリージョンを用意しました。
terraform {
required_version = ">=1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
provider "aws" {
region = "us-east-1"
alias = "use1"
}
ACMの証明書の発行は以下の内容で記述します。provider = aws.use1
のところで、先ほどのバージニア北部のリージョンに作成するよう指定しています。
resource "aws_acm_certificate" "acm" {
domain_name = var.domain
validation_method = "DNS"
provider = aws.use1
}
また、そのドメインの持ち主であることを確認する方法としてvalidation_method = "DNS"
を設定しています。後ほども書きますが、証明書発行のリクエストを実施後、各自のDNS上で設定が必要になります。
S3の設定
続いて、ビルドしたファイルなどを格納するS3のバケットを作っていきます。ここでは、バケット名をドメイン名と同じにしています。
resource "aws_s3_bucket" "frontend" {
bucket = var.domain
}
resource "aws_s3_bucket_policy" "frontend_policy" {
bucket = aws_s3_bucket.frontend.id
policy = data.aws_iam_policy_document.frontend_policy_doc.json
}
data "aws_iam_policy_document" "frontend_policy_doc" {
statement {
sid = "AllowCloudFrontServicePrincipal_${var.domain}"
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.frontend.iam_arn]
}
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.frontend.arn}/*"]
}
}
バケットポリシーとして、この後作成するCloudFrontに"s3:GetObject"
の権限を渡しておきます。
CloudFrontの設定
次にCloudFrontを作成していきます。以下に全体のコードを載せて、特徴的な部分のみ抜粋して以下で説明します。
コードの全文(modules/cloudfront.tf)
resource "aws_cloudfront_origin_access_identity" "frontend" {}
resource "aws_cloudfront_distribution" "frontend" {
aliases = [ var.domain ]
enabled = true
default_root_object = "index.html"
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/"
}
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/"
}
origin {
origin_id = aws_s3_bucket.frontend.id
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.frontend.cloudfront_access_identity_path
}
}
default_cache_behavior {
target_origin_id = aws_s3_bucket.frontend.id
viewer_protocol_policy = "redirect-to-https"
allowed_methods = [ "GET", "HEAD", "OPTIONS" ]
cached_methods = [ "GET", "HEAD", "OPTIONS" ]
// FIXME: 何もキャッシュしない設定になっているので注意
cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" // CachingDisabled
}
viewer_certificate {
cloudfront_default_certificate = false
acm_certificate_arn = aws_acm_certificate.acm.arn
minimum_protocol_version = "TLSv1.2_2021"
ssl_support_method = "sni-only"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
is_ipv6_enabled = true
wait_for_deployment = false
}
SPA用のパスの設定
以下では、SPA(Single Page Application)用にパスの設定をしています。
SPAでは特定のパス(例:/top
)にアクセスした時も/index.html
を読み込む必要があります。ただ、デフォルトの設定では、/top
にアクセスすると、/top
と言うパスのファイルにアクセスしようとしますので、403エラーなどになってしまいます。
そこで、その403などのエラー時にはトップディレクトリにあるindex.htmlを返すように設定しました。
default_root_object = "index.html"
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/"
}
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/"
}
キャッシュの設定
以下のブロックではキャッシュの設定を記述しています。この例では一旦、キャッシュを一切しない設定になっていますので、必要に応じて皆さんの要件で修正するようにしてください。
キャッシュの設定は考慮することや仕組みが複雑で、私自身まだあまり理解ができていないため、一旦無視している形です。(Cloudfrontの良さを活かせていない感がありますが…)
default_cache_behavior {
target_origin_id = aws_s3_bucket.frontend.id
viewer_protocol_policy = "redirect-to-https"
allowed_methods = [ "GET", "HEAD", "OPTIONS" ]
cached_methods = [ "GET", "HEAD", "OPTIONS" ]
// FIXME: 何もキャッシュしない設定になっているので注意
cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" // CachingDisabled
}
applyの手順
そのままterraform apply
を実行してしまうと、おそらくCloudFrontの作成が終わらず以下のようなメッセージが続くかと思います。
module.example_site.aws_cloudfront_distribution.frontend: Still creating... [20s elapsed]
これは、ACMの証明書の発行に先立って、そのドメインの持ち主が本当に自分であることを確認する必要があり、その確認が済むまで処理を待っているためと考えられます。
ですので、一度にCloudFrontまで全てをapplyしてしまうのではなく、先にACMだけapplyし、ドメインの確認を実施してから全体をapplyするという手順を踏みたいと思います。
まずは以下のコマンドでACMだけをデプロイします。モジュール名は適宜修正してください。複数サイトを同時に新規デプロイする際は、サイトの分だけこのコマンドを実行します。
# terraform apply -target=module.{モジュール名}.aws_acm_certificate.acm
terraform apply -target=module.example_site.aws_acm_certificate.acm
これでACMだけが作成されました。AWSのWebコンソールからACMのページにアクセスします。このACMはバージニア北部のリージョンで作成していますので、Webコンソール上でも右上がバージニア北部になっていることをご確認ください。
すると、証明書の一覧に該当のドメインのものがあり、ステータスが「保留中の検証」となっているかと思います。
その証明書の証明書IDをクリックし、「ドメイン」という部分からCNAME名とCNAME値を確認します。この内容を、お使いのドメインの管理ページでDNSに設定することで、このドメインの管理者が自分であることを確認できます。
DNSに設定し少し待てば、先ほどの証明書一覧のステータスが「発行済み」に変わるかと思います。
これでACM証明書の準備が完了しました。あとは、全体をapplyするだけですので、terraform apply
をそのまま実行します。
DNSでのCNAMEの設定
ここまでで、AWS側の環境をすべて作ることができました。最後に、独自ドメインの名前解決ができるように、DNSへの設定をします。こちらも、一旦はTerraformを使わずに進めます。
DNSでは今回設定したドメイン名にアクセスすることで、CloudFrontの「ディストリビューションドメイン名」のサイトを開くように設定します。したがって、ドメインの別名を設定できるCNAMEレコードを使って、example.mochi-yu.com
から「ディストリビューションドメイン名」へのレコードを設定します。
CloudFlareでの設定例はこのような形です。
ここまで来れば、あとは該当のS3バケットにビルド後のファイルを配置し、該当ドメインへアクセスすればWebサイトが閲覧できるようになったかと思います。
複数サイトのデプロイ
複数のドメインをデプロイする際は、トップディレクトリのmain.tf
に書いたモジュールを増やすだけです。
module "example_site" {
source = "./modules"
domain = "example.mochi-yu.com"
env = "prod"
}
+ module "example1_site" {
+ source = "./modules"
+ domain = "example1.mochi-yu.com"
+ env = "prod"
+ }
terraform {
backend "s3" {
region = "ap-northeast-1"
encrypt = false
bucket = "example"
key = "static-site/prod"
}
}
増やした時のapplyも、まず最初にACMだけをapplyする上記手順で実施してください。
これで、簡単に独自ドメインのサイトを増やすことができるようになりました!