0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DeNA 25 新卒Advent Calendar 2024

Day 4

【Terraform】S3+Cloudfront+独自ドメインで手軽にサイトをホスティングするためのインフラを作る

Last updated at Posted at 2025-01-21

とても遅刻してしまいましたが、この記事は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を作成して以下を記述します。

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に以下を記述しておきます。

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というエイリアスでバージニア北部のリージョンを用意しました。

modules/main.tf
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のところで、先ほどのバージニア北部のリージョンに作成するよう指定しています。

modules/acm.tf
resource "aws_acm_certificate" "acm" {
  domain_name = var.domain
  validation_method = "DNS"
  provider = aws.use1
}

また、そのドメインの持ち主であることを確認する方法としてvalidation_method = "DNS"を設定しています。後ほども書きますが、証明書発行のリクエストを実施後、各自のDNS上で設定が必要になります

S3の設定

続いて、ビルドしたファイルなどを格納するS3のバケットを作っていきます。ここでは、バケット名をドメイン名と同じにしています。

modules/s3.tf
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)
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を返すように設定しました。

modules/cloudfront.tf(抜粋)
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の良さを活かせていない感がありますが…)

modules/cloudfront.tf(抜粋)
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から「ディストリビューションドメイン名」へのレコードを設定します。

CloudFrontのディストリビューションドメイン名

CloudFlareでの設定例はこのような形です。

CloudFlareでの設定例

ここまで来れば、あとは該当のS3バケットにビルド後のファイルを配置し、該当ドメインへアクセスすればWebサイトが閲覧できるようになったかと思います。

複数サイトのデプロイ

複数のドメインをデプロイする際は、トップディレクトリのmain.tfに書いたモジュールを増やすだけです。

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する上記手順で実施してください。

これで、簡単に独自ドメインのサイトを増やすことができるようになりました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?