08. CloudFront / Route 53 / ACM
独自ドメインで HTTPS の静的サイトを公開する標準構成。このサイト自身(hcl-guide.com)がまさにこの章のコード でデプロイされています。
この章の目次
構成図と登場リソース
www AWS Account
│
[Browser https://hcl-guide.com]
│ ① DNS 解決
↓
┌──────────────────┐
│ Route 53 (Zone) │ → A レコード (alias) → CloudFront
└──────────────────┘
│ ② 名前解決後、CloudFront に到達
↓
┌──────────────────────────────────┐
│ CloudFront (Edge, グローバル) │
│ - Viewer Cert (ACM, us-east-1) │
│ - OAC (origin への署名) │
└──────────────────────────────────┘
│ ③ キャッシュなし or 期限切れ → Origin へ
↓
┌──────────────────┐
│ S3 Bucket (private) │ ← OAC 経由でのみアクセス許可
│ - index.html │
│ - assets/... │
└──────────────────┘
リクエストの流れ
- ブラウザが
hcl-guide.comを Route 53 で名前解決 - Route 53 の A レコード(alias) が CloudFront を指す
- CloudFront のエッジが応答。キャッシュにあればそのまま返す
- 無ければ OAC で署名 して S3 にアクセス、ファイルを取得して返す(同時にキャッシュ)
- S3 はバケットポリシーで 「この CloudFront からの署名つきリクエストだけ許可」
S3 をプライベートに保ったまま、世界中から HTTPS で配信できる、というのがこの構成のキモです。
Route 53 のホストゾーン
ドメインを Route 53 で取った場合、ホストゾーンは自動で作られています。data ソースで参照するのが楽。
data "aws_route53_zone" "this" {
name = "hcl-guide.com"
}
外部レジストラ(お名前.com 等)で取ったドメインを使う場合は、aws_route53_zone リソースで作って、NS レコード 4 つをレジストラ側に登録 する必要があります。
ACM 証明書(us-east-1 必須)
CloudFront に貼る ACM 証明書は 必ず us-east-1 リージョン で発行する必要があります(CloudFront がグローバルサービスで、内部的に us-east-1 を見ているため)。Terraform 側はプロバイダ alias で対応:
provider "aws" {
region = "ap-northeast-1"
}
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
resource "aws_acm_certificate" "site" {
provider = aws.us_east_1
domain_name = "hcl-guide.com"
validation_method = "DNS"
subject_alternative_names = ["www.hcl-guide.com"]
lifecycle {
create_before_destroy = true
}
}
# 検証用 DNS レコードを Route 53 に作る
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.site.domain_validation_options :
dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
record = dvo.resource_record_value
}
}
zone_id = data.aws_route53_zone.this.zone_id
name = each.value.name
type = each.value.type
records = [each.value.record]
ttl = 60
}
# 検証完了を待つ
resource "aws_acm_certificate_validation" "site" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.site.arn
validation_record_fqdns = [for r in aws_route53_record.cert_validation : r.fqdn]
}
S3 バケットを「プライベートのまま」
resource "aws_s3_bucket" "site" {
bucket = "hcl-guide-site"
}
resource "aws_s3_bucket_public_access_block" "site" {
bucket = aws_s3_bucket.site.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "site" {
bucket = aws_s3_bucket.site.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
CloudFront OAC(Origin Access Control)
OAC は CloudFront が S3 にアクセスする際の 署名つきリクエスト の仕組み。旧来の OAI(Origin Access Identity)の後継で、2024 年以降の新規構築では OAC が必須。
resource "aws_cloudfront_origin_access_control" "site" {
name = "hcl-guide-site"
description = "OAC for hcl-guide.com"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
S3 バケットポリシー(CloudFront だけ許可)
data "aws_iam_policy_document" "site" {
statement {
sid = "AllowCloudFrontOAC"
effect = "Allow"
actions = ["s3:GetObject"]
principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}
resources = ["${aws_s3_bucket.site.arn}/*"]
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [aws_cloudfront_distribution.site.arn]
}
}
}
resource "aws_s3_bucket_policy" "site" {
bucket = aws_s3_bucket.site.id
policy = data.aws_iam_policy_document.site.json
}
CloudFront ディストリビューション
resource "aws_cloudfront_distribution" "site" {
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
aliases = ["hcl-guide.com", "www.hcl-guide.com"]
price_class = "PriceClass_200" # 北米+欧州+アジア(日本は含まれる)
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = "s3-site"
origin_access_control_id = aws_cloudfront_origin_access_control.site.id
}
default_cache_behavior {
target_origin_id = "s3-site"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
compress = true
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" # AWS Managed CachingOptimized
}
custom_error_response {
error_code = 404
response_code = 404
response_page_path = "/404.html"
error_caching_min_ttl = 60
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.site.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
# 関連 SaaS と同じく、変更で再作成されると配信が止まるので保護
lifecycle {
create_before_destroy = false
}
}
Route 53 alias レコード
独自ドメインを CloudFront に向けます。alias レコードは AWS 内部リソースを指す特別な A レコードで、料金がかからない。
resource "aws_route53_record" "apex" {
zone_id = data.aws_route53_zone.this.zone_id
name = "hcl-guide.com"
type = "A"
alias {
name = aws_cloudfront_distribution.site.domain_name
zone_id = aws_cloudfront_distribution.site.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "www" {
zone_id = data.aws_route53_zone.this.zone_id
name = "www.hcl-guide.com"
type = "A"
alias {
name = aws_cloudfront_distribution.site.domain_name
zone_id = aws_cloudfront_distribution.site.hosted_zone_id
evaluate_target_health = false
}
}
この章の全体構成
ここまでの 6 つのリソース(Route 53 + ACM + S3 + 公開遮断 + OAC + CloudFront + バケットポリシー)を 1 つのモジュールにまとめると、独自ドメインの静的サイト がワンコマンドで立ち上がります。
module "static_site" {
source = "./modules/static-site"
domain_name = "hcl-guide.com"
alternative_names = ["www.hcl-guide.com"]
source_dir = "${path.module}/../public"
providers = {
aws = aws
aws.us_east_1 = aws.us_east_1
}
}
このサイトの実物
hcl-guide.com の Terraform コード一式は本リポジトリの
terraform/site/ にあります。terraform apply 1 発で再現可能。