【AWS】Terraformを使って、CloudFrontを構築する!

August 18, 2020

TerraformでCloudFrontを作成する方法を解説します。

目次

  • 環境
  • 処理の概要
  • 静的サイト公開用のS3バケットを作成
  • CloudFrontのアクセスログ格納用のS3バケットを作成
  • CloudFrontの作成
  • Terraformの実行

環境

Terraformを使用します。ソースコードの構成は以下の通り。

|-- main.tf ←モジュールを読み込むトップのファイル。
`-- modules
    |-- cloudfront.tf ←CloudFrontを設定するファイル。
    |-- iam.tf        ←バケットポリシーを設定するファイル。
    `-- s3.tf         ←S3バケットを設定するファイル。

GitHub

また、Terraformのトップファイルは以下の通りです。共通の設定のため解説は割愛します。
【AWS】AssumeRoleしてTerraformをより安全に実行する!

./main.tf

provider "aws" {
  region = "ap-northeast-1"
  assume_role {
    role_arn = "arn:aws:iam::XXXXXXXXXX:role/SystemAdmin"
  }
}

terraform {
  required_version = "0.12.24"
  backend "s3" {
    bucket   = "tfstate.mini-schna.com"
    region   = "ap-northeast-1"
    key      = "blog/public-s3.tfstate"
    encrypt  = true
    role_arn = "arn:aws:iam::XXXXXXXXXX:role/SystemAdmin"
  }
}

module "aws" {
  source = "./modules"
}

処理の概要

下記の図は、S3をオリジン(※)としてCloudFrontを使用する時の流れになります。
※オリジン: コンテンツを格納しているオリジナルサーバのこと。

CloudFront+S3 仕組み

① クライアントがブラウザを使って、Webページを要求する。

② CloudFrontのキャッシュに「存在しない or 期限が切れている」コンテンツがある場合に、S3(オリジン)からコンテンツを取得する。
※キャッシュにコンテンツが残っている場合は、この処理はスキップされ③に進みます。

③ CloudFrontがクライアントに「キャッシュした or S3から取得した」コンテンツを返します。


静的サイト公開用のS3バケットを作成

下記の記事を参考に、静的サイト公開用のS3バケットを作成します。
【所要時間10分】Terraformを使って、S3でWebサイトを公開する!

作成したコードは下記になります。

バケットポリシー

./modules/iam.tf

###############################################
# 静的サイト公開用バケットポリシー
###############################################
data "aws_iam_policy_document" "mini_schna_com_bucket" {
  statement {
    sid    = ""
    effect = "Allow"

    ## アクセス元の設定。
    principals {
      identifiers = ["*"] ## 誰でもアクセスできるように設定。
      type        = "*"
    }

    ## バケットに対して制御するアクションを設定する。
    actions = [
      "s3:GetObject" ## オブジェクトの読み取りアクション。
    ]

    ## アクセス先の設定。
    resources = [
      "arn:aws:s3:::mini-schna.com",  ## mini-schna.comバケットへのアクセス。
      "arn:aws:s3:::mini-schna.com/*" ## mini-schna.comバケット配下へのアクセス。
    ]
  }
}

S3バケット

./modules/s3.tf

###############################################
# 静的サイト公開用バケット
###############################################
resource "aws_s3_bucket" "mini_schna_com" {
  bucket = "mini-schna.com"
  policy = data.aws_iam_policy_document.mini_schna_com_bucket.json ## iam.tfで設定したポリシーを使用。

  ## バケットの削除設定。
  force_destroy = false ## バケットの中にオブジェクトが入っている場合にTerraformからバケットを削除できないようにする。

  ## Webサイト設定。
  website {
    ## バケットにアクセスした時にデフォルトで表示されるコンテンツを設定。
    index_document = "index.html"
  }

  ## オブジェクトのバージョン管理設定。
  versioning {
    enabled    = true
    mfa_delete = false ## オブジェクトへのアクセスにMFA(多段階認証)を使用しない。
  }

  ## バケットの料金を誰が支払うか設定。
  request_payer = "BucketOwner" ## 通常通り所有者が支払う。
}

resource "aws_s3_bucket_public_access_block" "mini_schna_com" {
  bucket                  = aws_s3_bucket.mini_schna_com.bucket
  block_public_acls       = true
  block_public_policy     = false ## バケットポリシーで制御したいため無効にする。
  ignore_public_acls      = true
  restrict_public_buckets = false ## バケットポリシーで制御したいため無効にする。
}

CloudFrontのアクセスログ格納用のS3バケットを作成

CloudFrontのアクセスログを格納するためのS3バケットを作成していきます。

CloudFrontのアクセスログ

バケットポリシー

下記が実現できるポリシーを作成していきます。

  • バケットの所有者以外でもログが確認できるようする。
  • CloudFrontがログを作成できるようにする。

./modules/iam.tf

###############################################
# CloudFrontのアクセスログ格納用バケットポリシー
###############################################
data "aws_iam_policy_document" "cloudfront_logging_bucket" {
  statement {
    sid    = ""
    effect = "Allow"

    principals {
      identifiers = ["*"]
      type        = "*"
    }

    actions = [
      "s3:ListBucket",
      "s3:PutObject",
      "s3:GetObject"
    ]

    resources = [
      "arn:aws:s3:::cloudfront-logging.mini-schna.com",
      "arn:aws:s3:::cloudfront-logging.mini-schna.com/*"
    ]
  }
}

それでは解説していきます。

actions = [
  "s3:ListBucket",
  "s3:PutObject",
  "s3:GetObject"
]

s3:ListBucketは、バケットのオブジェクトの一部またはすべて (最大 1000) が表示できるようになる。(開発者がログを確認する際に使用。)

s3:PutObjectは、S3にオブジェクトを追加できるようになります。(CloudFrontがアクセスログを追加するために使用。)

s3:GetObjectは、S3からオブジェクトを取得できるようになります。(開発者がログを確認する際に使用。)


S3バケット

./modules/s3.tf

###############################################
# CloudFrontのアクセスログ格納用バケット
###############################################
resource "aws_s3_bucket" "cloudfront_logging" {
  bucket = "cloudfront-logging.mini-schna.com"
  policy = data.aws_iam_policy_document.cloudfront_logging_bucket.json ## iam.tfで設定したポリシーを使用。
  force_destroy = false
  versioning {
    enabled    = true
    mfa_delete = false
  }

  ## オブジェクトのライフサイクル設定。
  lifecycle_rule {
    id      = "assets"
    enabled = true

    ## オブジェクトの保存期限。
    expiration {
      days = "365" ## 1年
    }

    ## 現在のオブジェクトの移行設定。
    transition {
      ## オブジェクトが作成されてから移行するまでの日数。
      days          = "93" ## 3ヶ月
      storage_class = "STANDARD_IA"
    }

    ## オブジェクトの以前のバージョンの保存期限。
    noncurrent_version_expiration {
      days = "1095" ## 3年
    }

    ## 古いのオブジェクトの移行設定。
    noncurrent_version_transition {
      ## オブジェクトが古いバージョンになってから移行するまでの日数。
      days          = "365" ## 1年
      storage_class = "GLACIER"
    }
  }

  request_payer = "BucketOwner"
}

# S3 Public Access Block
## パブリックアクセスはしないため全て有効にする。
resource "aws_s3_bucket_public_access_block" "cloudfront_logging" {
  bucket                  = aws_s3_bucket.cloudfront_logging.bucket
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

【所要時間10分】Terraformを使って、S3でWebサイトを公開する!
上記の記事で出てきていない項目について解説していきます。

## オブジェクトのライフサイクル設定。
  lifecycle_rule {
    id      = "assets"
    enabled = true

    ## オブジェクトの保存期限。
    expiration {
      days = "365" ## 1年
    }

    ## 現在のオブジェクトの移行設定。
    transition {
      ## オブジェクトが作成されてから移行するまでの日数。
      days          = "93" ## 3ヶ月
      storage_class = "STANDARD_IA"
    }

    ## オブジェクトの以前のバージョンの保存期限。
    noncurrent_version_expiration {
      days = "1095" ## 3年
    }

    ## 古いのオブジェクトの移行設定。
    noncurrent_version_transition {
      ## オブジェクトが古いバージョンになってから移行するまでの日数。
      days          = "365" ## 1年
      storage_class = "GLACIER"
    }
  }

期間を設定してストレージクラスを変更することができるようになります。ストレージクラスを上手く活用することで利用料金を抑えることができます。

S3のライフサイクル設定

公式 - ストレージクラス


CloudFrontの作成

S3をオリジンとしたCloudFrontを作成していきます。

./modules/cloudfront.tf

resource "aws_cloudfront_distribution" "s3_distribution" {
  # distribution用のCNAME。
  # Route53でCloudFrontのエイリアスを設定する場合、この値とレコードがが等しくなる必要がある。
  aliases = ["mini-schna.com"]

  # Errorレスポンスのカスタマイズ設定。
  custom_error_response {
    error_caching_min_ttl = 300 # デフォルトの5分間を明示的に指定。
    error_code            = 500 # カスタマイズしたいエラーコードを指定する。
    response_code         = 200 # Viewerに返したいコードを指定する。
    response_page_path    = "/custom_404.html"
  }

  # キャッシュのデフォルト設定。
  default_cache_behavior {
    allowed_methods = ["HEAD", "OPTIONS", "GET", "PUT", "POST", "DELETE", "PATCH"] # CloudFrontで許可するメソッド。
    cached_methods  = ["HEAD", "OPTIONS", "GET"]                                   # キャッシュするメソッド。
    compress        = true                                                         # 高速化のためコンテンツ圧縮(gzip)を許可する。
    # Cache-Control or Expires がリクエストのヘッダーに無い時のデフォルトのTTL。
    # デフォルトの1日を明示的に指定。
    default_ttl = 86400

    max_ttl = 31536000 # デフォルトの365日を明示的に指定。
    min_ttl = 0        # デフォルトの0sを明示的に指定。

    # 転送する時にCookie等の扱い方の設定。
    forwarded_values {
      query_string = false # クエリ文字の設定。今回使用しないため無効。
      cookies {
        forward = "all" # 全てのCookieを転送する。
        # whitelisted_names = [] # whitelistの場合に設定する必要がある。
      }
    }

    # Lambdaで処理したい場合に設定する項目。まだ使用しないため無効。
    # lambda_function_association {
    #   event_type = ""
    #   lambda_arn = ""
    # }

    # MS Smooth Streaming形式のメディアは使用しないため無効。
    smooth_streaming = false

    target_origin_id = aws_s3_bucket.mini_schna_com.id ## S3バケットをオリジンとする。

    # 信頼された署名者を指定する設定。
    trusted_signers = []

    viewer_protocol_policy = "redirect-to-https" # HTTPS通信のみ許可する。
  }

  default_root_object = "index.html"

  enabled         = true
  is_ipv6_enabled = false   # ipv6は使用しないため無効。
  http_version    = "http2" # デフォルトのhttp2を明示的に指定。

  # アクセスログ設定。
  logging_config {
    bucket          = aws_s3_bucket.cloudfront_logging.bucket_domain_name
    include_cookies = true # Cookieもアクセスログに含めたいため有効。
    prefix          = "cloudfront/"
  }

  # 優先度を設定したキャッシュ設定。Precedence 0。
  # ordered_cache_behavior {
  #   forwarded_values {
  #     query_string = false
  #     cookies {
  #       forward = ""
  #     }
  #   }
  #   allowed_methods        = ["GET", "PUT", "POST", "DELETE"] # CloudFrontで許可するメソッド。
  #   cached_methods         = ["GET", "PUT", "POST", "DELETE"] # キャッシュするメソッド。
  #   path_pattern           = "/"
  #   target_origin_id       = var.origin_bucket_id # S3バケットをオリジンとする。
  #   viewer_protocol_policy = "redirect-to-https"  # HTTPS通信のみ許可する。
  # }

  origin {
    domain_name = aws_s3_bucket.mini_schna_com.bucket_domain_name
    origin_id   = aws_s3_bucket.mini_schna_com.id
    # カスタムヘッダー。
    # custom_header {
    #   name = ""
    #   value = ""
    # }
    # origin_path = "/" # オリジンのパスは変更しないため無効。

    # OriginにアクセスするためのIAM設定。
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.mini_schna.cloudfront_access_identity_path
    }
  }

  # Originフェイルオーバーを行うために必要なOriginグループの設定。
  # 今回は使用しないため無効。
  # origin_group {
  #   origin_id = ""
  #   failover_criteria {
  #     status_codes = []
  #   }
  #   member {
  #     origin_id = ""
  #   }
  # }

  # 価格クラスの設定。
  price_class = "PriceClass_All" # レイテンシーのみに基づいて使用するロケーションを決める方法を採用。

  restrictions {
    geo_restriction {
      restriction_type = "none"
      # locations        = [] # blacklist(or whitelist)の対象を設定していく。
    }
  }

  # SSL証明書の設定。
  viewer_certificate {
    cloudfront_default_certificate = false # ACMで作成した証明書を使用するため無効。
    acm_certificate_arn            = "arn:aws:acm:us-east-1:XXXXXXXXXXXXXX:certificate/XXXXXXXXXXXXXXXXXXXXXXXXXXX"
    minimum_protocol_version       = "TLSv1.2_2019" # SSLの最小バージョン。AWSの推奨値を採用。
    # SNI(名前ベース)のSSL機能を使用する。
    # https://aws.amazon.com/jp/cloudfront/custom-ssl-domains/
    ssl_support_method = "sni-only"
  }

  # WAFを使用する場合に設定が必要。まだ使用しないため無効。
  # web_acl_id = ""

  retain_on_delete = false # TerraformでCloudFrontを削除したいため無効。

  # ディストリビューションのステータスが「InProgress」→「Deployed」に変わることを待つかどうかの設定。
  wait_for_deployment = true
}

resource "aws_cloudfront_origin_access_identity" "mini_schna" {
  comment = "blog"
}

それでは解説していきます。(結構行数があるため、コメントで補足しきれていない設定に絞ります。)

# distribution用のCNAME。
# Route53でCloudFrontのエイリアスを設定する場合、この値とレコードがが等しくなる必要がある。
aliases = ["mini-schna.com"]

Route53でCloudFrontをエイリアス先に設定することで、既に管理しているドメインでCloudFrontにアクセスすることができます。
また、その時Route53に登録したレコードと aliases に設定する値を同じにする必要があります。


# Errorレスポンスのカスタマイズ設定。
custom_error_response {
  error_caching_min_ttl = 300 # キャッシュ時間。デフォルトの5分間を明示的に指定。
  error_code            = 500 # カスタマイズしたいエラーコードを指定する。
  response_code         = 200 # Viewerに返したいコードを指定する。
  response_page_path    = "/custom_404.html"
}

「オリジン → CloudFront → クライアント」通常、このような流れでオリジンからクライアントに通信が返ります。この時、オリジン→CloudFrontでエラーが出た場合に、その「エラーコード」、「返すページ」をカスタマイズすることができます。


# キャッシュのデフォルト設定。
default_cache_behavior {
  allowed_methods = ["HEAD", "OPTIONS", "GET", "PUT", "POST", "DELETE", "PATCH"] # CloudFrontで許可するメソッド。
  cached_methods  = ["HEAD", "OPTIONS", "GET"]                                   # キャッシュするメソッド。
  compress        = true                                                         # 高速化のためコンテンツ圧縮(gzip)を許可する。
  # Cache-Control or Expires がリクエストのヘッダーに無い時のデフォルトのTTL。
  # デフォルトの1日を明示的に指定。
  default_ttl = 86400

  max_ttl = 31536000 # デフォルトの365日を明示的に指定。
  min_ttl = 0        # デフォルトの0sを明示的に指定。

  # 転送する時にCookie等の扱い方の設定。
  forwarded_values {
    query_string = false # クエリ文字の設定。今回使用しないため無効。
    cookies {
      forward = "all" # 全てのCookieを転送する。
      # whitelisted_names = [] # whitelistの場合に設定する必要がある。
    }
  }
  smooth_streaming = false

  target_origin_id = aws_s3_bucket.mini_schna_com.id ## S3バケットをオリジンとする。

  # 信頼された署名者を指定する設定。
  trusted_signers = []

  viewer_protocol_policy = "redirect-to-https" # HTTPS通信のみ許可する。
}

キャッシュのデフォルト設定。(後続の設定 ordered_cache_behavior{} で優先したいキャッシュを設定することができます。)

compress : 圧縮して配信することで高速化することができるます。

default_ttlmin_ttlmax_ttl : キャッシュのTTL設定。オブジェクトに付与されたヘッダーと関連して考えないといけないため少し複雑です。そのため、少しでも分かりやすくするために表にまとめました。

CloudFrontのTTL ※CloudFrontとブラウザでキャッシュの挙動が少し異なります。

forwarded_values {} : Cookieやクエリ文字もオリジンに転送する場合の設定。今回は、Cookieのみ転送したいので、cookies { forward = "all" } と設定しています。

smooth_streaming = false : MS Smooth Streaming形式のメディアは使用しないため無効。

viewer_protocol_policy = "redirect-to-https" : クライアントとCloudFrontの間でHTTP通信をHTTPSにリダイレクトします。他にも「allow-all(HTTP, HTTPS両方可能)」、「https-only(HTTPSのみ)」が設定できます。


# アクセスログ設定。
logging_config {
  bucket          = aws_s3_bucket.cloudfront_logging.bucket_domain_name
  include_cookies = true # Cookieもアクセスログに含めたいため有効。
  prefix          = "cloudfront/"
}

CloudFrontのアクセスログをS3に格納するための設定。


# 優先度を設定したキャッシュ設定。Precedence 0。
# ordered_cache_behavior {
#   forwarded_values {
#     query_string = false
#     cookies {
#       forward = ""
#     }
#   }
#   allowed_methods        = ["GET", "PUT", "POST", "DELETE"] # CloudFrontで許可するメソッド。
#   cached_methods         = ["GET", "PUT", "POST", "DELETE"] # キャッシュするメソッド。
#   path_pattern           = "/"
#   target_origin_id       = var.origin_bucket_id # S3バケットをオリジンとする。
#   viewer_protocol_policy = "redirect-to-https"  # HTTPS通信のみ許可する。
# }

優先度を設定したキャッシュの設定。今回は使用しないため無効にしているのですが、パスパターン毎にキャッシュの設定を行うことができます。


# OriginにアクセスするためのIAM設定。
s3_origin_config {
  origin_access_identity = aws_cloudfront_origin_access_identity.mini_schna.cloudfront_access_identity_path
}

OAI(オリジンアクセスアイデンティティ)の設定。オリジンにCloudFrontからアクセスするための特別なユーザーを作成することで、ユーザーはS3バケットから直接ではなく、CloudFront経由でのみファイルにアクセスできるようになります。


restrictions {
  geo_restriction {
    restriction_type = "none"
    # locations        = [] # blacklist(or whitelist)の対象を設定していく。
  }
}

CloudFrontにアクセスする地域を制限することができます。今回は特に設定していません。


resource "aws_cloudfront_origin_access_identity" "mini_schna" {
  comment = "blog"
}

OAIの作成。


Terraformの実行

それでは作成したコードを元にTerraformを実行して、S3バケット、CloudFrontを作成していきます。

$ cd /infra/terraform/cloudfront
$ terraform init
$ terraform apply

※Dockerコンテナ内での実行を前提にしています。


以上で、CloudFrontの作成が完了です。

最後までご覧頂きありがとうございました。