コンテンツにスキップ

cacheable_frontend pattern

概要

cacheable_frontend patternは、Amazon S3(以下、S3とする)に配置されたファイルを配信するためのモジュールです。 Amazon CloudFront(以下、CloudFrontとする)とAmazon Route 53(以下、Route 53とする)、そして前述のS3を利用します。 S3バケットに配置されたファイルを世界中のエッジロケーションでキャッシュすることにより、効率的にコンテンツを配信できるようになります。

環境構築後のS3バケットへのファイルのデプロイはcd_pipeline_frontendモジュールをご利用ください。

想定する適用対象環境

cacheable_frontend patternは、Runtime環境で使用されることを想定しています。

依存するpattern

cacheable_frontend patternは、事前に実行の必要なpatternはありません。ただし、AWS上で事前に以下を実施する必要があります。

  • 独自ドメインの取得
  • Route 53で上記ドメイン名のホストゾーン作成

⚠️ CloudFrontでAWS WAFを利用したい場合は事前にAWS WAFを作成しておく(またはAWS WAF作成後にCloudFrontの設定変更をする)必要があります。 AWS WAFの作成はwebacl patternを利用することで行なえます。

構築されるリソース

このpatternでは、以下のリソースが構築されます。

リソース名 説明
S3バケット CloudFrontで配信するファイルを保存するバケット
CloudFront Distribution 世界中のエッジロケーションでファイルをキャッシュし、効率的にコンテンツを配信するサービス
CloudFront Origin Access Identity CloudFront DistributionがS3バケットにアクセスするためのリソース
Route 53 レコード CloudFront Distributionに名前でアクセスできるようにするためのDNSレコード
AWS Certificate Manager Route 53ホストゾーンのレコードに対するSSL/TLS証明書を発行するサービス
Lambda@Edge CloudFrontのイベントに応じて実行されるLambda関数
Amazon CloudWatch Logs Lambda関数のログが置かれるロググループ
AWS IAM Lambda関数に付与する権限の設定

⚠️ 以下のリソースは構築されません。事前に手動での作成が必須になります。

リソース名 説明
Route53 ホストゾーン 事前取得済みのドメインに関するルーティングのレコードを管理するコンテナ

モジュールの理解に向けて

CloudFrontを利用すると、S3バケットに配置されたファイルやELBでアクセス可能なコンテンツをキャッシュして効率的にコンテンツを配信できます。 具体的な配信の仕組みに関しては公式ドキュメントをご参照ください。

Eponaでは、ビルドした静的ファイルをS3バケットに配置し、Route 53とCloudFrontを使って独自ドメインでコンテンツを配信する利用方法を想定しています。 そのため、本patternを実行するためには事前に独自ドメインの取得が必須になります。

構成図

複数のリージョンへの対応

CloudFrontの利用には、SSL/TLS証明書が必要となります。WebサイトはHTTPSで公開するのが一般になりつつありますが、
CloudFrontの仕様として要求されます。

⚠️ 仮にHTTPで配信したい場合でも、CloudFrontにはSSL/TLS証明書が必要となることに注意してください。

cacheable_frontend patternには、下記のようなHTTPSでのWebサイト公開および、デプロイを踏まえた対応が組み込まれています。

CloudFrontとAWS Certificate Manager

cacheable_frontend patternでは、SSL/TLS証明書をAWS Certificate Manager(以下ACMとする)で発行するように構成しています。 CloudFrontでACMの証明書を使う場合の要件として、バージニア北部リージョンで証明書を発行した証明書である必要があります。


静的コンテンツの更新

配信した静的コンテンツは、サービスの成長、改善にともない更新されていくでしょう。

Eponaでは、静的コンテンツの配信をcd_pipeline_frontend patternにより構築されるCodePipelineで行います。

cd_pipeline_frontend pattern

ここで構築されるパイプラインは、デプロイ先のS3バケットが同一リージョンにあることを想定しています。


cacheable_frontend patternでは、これらの背景を踏まえた形で構築しています。

  • CloudFrontとACMに関する要件
  • cd_pipeline_frontend patternとの連携

つまりcacheable_frontend patternの利用にあたっては、リソースを配置するリージョンを意識する必要があります。
具体的には、cacheable_frontend patternでは2つのProviderを使い分けを行います。用途は、それぞれ以下となります。

  • cache_provider … CloudFrontに関連するリソースを扱う。バージニア北部リージョンが指定されることを想定
  • origin_provider … オリジンに関連するリソースを扱う。デフォルトリージョンが指定されることを想定

具体的な指定方法については、サンプルコードを参照してください。

CloudFrontのパラメータについて

CloudFrontは設定可能なパラメータの数が多いため、Terraform実行時に指定する変数の数も多くなっています。
各パラメータの意味や設定値に関しては公式ドキュメントを参照し、 Terraform実行時の変数の指定方法に関しては本ドキュメント下部のサンプルコード入出力リファレンスをご参照ください。

HTTPヘッダーについて

昨今のWebサイトはセキュリティの観点から特定のHTTPヘッダーをレスポンスヘッダーに含めることが推奨されています。 Eponaでもこの対応をCloudFrontに対して行えるようになっており、 AWS公式ドキュメント に従ってLambda@Edgeを利用することで実現しています。
HTTPヘッダーについてはMDN Web Docsをご参照ください。

EponaではAWS公式のチュートリアル を参考にして以下をLambdaのコードとしてデプロイしています。
デフォルトでLambda@Edgeにデプロイされるコード

ただし、Eponaの提供するLambdaでは最低限のセキュリティヘッダーしか付与しません。これは、各サービスにより必要なHTTPヘッダーも異なり、画一的な付与が困難なためです。サービスごとに必要なヘッダを検討の上、独自のLambdaのコードとしてデプロイすることを強く推奨します。 これを行う場合はviewer_response_lambda_source_dirにコードを配置したローカルディレクトリのパスを指定してください。
相対パスで指定する場合は、Terraformを実行しているディレクトリをカレントディレクトリとして指定してください。

Single Page Application(SPA)のコンテンツを公開する

SPAではURLの変化に応じて表示するコンテンツを変化させるといった擬似的なページ遷移がよく使われます。 しかしCloudFrontの仕様1により、ファイルが存在しないURLへアクセスすると、返却されるHTTPステータスが404(NoSuchKey)になってしまうという事象が発生します。

これは、例えばステータスコードが404の場合にHTTPステータス200(OK)でエントリーポイントのコンテンツのパスを返すといったカスタムエラーレスポンスを設定することで回避できます。 以下に本patternを使ってこれを設定するサンプルを示します。

cloudfront_custom_error_responses = [
  {
    error_code            = 404
    error_caching_min_ttl = 300
    response_code         = 200
    response_page_path    = "/"
  }
]

アクセスログの収集を行う

CloudFrontではアクセスログをS3バケットへ収集可能です。 これはオプションのため、明示的にロギングを設定しない場合は収集が行われません。
本patternを使ってアクセスログ収集を設定したい場合は、実行時に以下のように指定してください。

⚠️
prefixinclude_cookiesオプションに関しての詳細は、それぞれ下記ページの「ログのプレフィックス」「Cookieのログ作成」をご参照ください。

cloudfront_logging_config = {
  bucket_name     = "アクセスログ収集先とするS3バケット名"
  prefix          = "sample"
  include_cookies = true
}

地理的制限の有効化

CloudFrontでは国ごとにアクセスを制限する設定を行うことが可能ですが、本patternの現バージョンではこの設定は行えない仕様になっています。

Terraformでのリソース削除について

terraform applyで作成したリソースは通常terraform destroyでまとめて削除できます。
ただし、本patternでは一度目のterraform destroyで必ず以下のエラーが発生し、一部のリソースが残ってしまう仕様になっています。

Error deleting Lambda Function: InvalidParameterValueException: Lambda was unable to delete arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:static-site-proxy:3 because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functions and Replicas.
status code: 400, request id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

これはCloudFrontが完全にエッジロケーションから削除されるまでLambda@Edgeの削除を行えないというAWSの仕様によるものです。
Terraform公式の Issue#1721 でもあがっており、現状では回避不可能となっています。
一度目のterraform destroy後、数十分~数時間待ってから再度terraform destroyを実行すると正常に削除できます。

サンプルコード

cacheable_frontend patternを使用したサンプルコードを、以下に記載します。

# Origin
provider "aws" {
  alias  = "origin_provider"
  assume_role {
    role_arn = "[TerraformExecutionRoleのARN]"
  }
}

# CloudFrontやACMを配置するAWS Providerの定義(必ず、regionにはus-east-1を指定してください。)
provider "aws" {
  alias  = "cache_provider"     # 任意の文字列
  region = "us-east-1"  # バージニア北部
  assume_role {
    role_arn = "[TerraformExecutionRoleのARN]"
  }
}

module "cacheable_frontend" {
  source                  = "git::https://gitlab.com/eponas/epona.git//modules/aws/patterns/cacheable_frontend?ref=v0.2.6"

  # patternで利用するAWS Providerを定義
  providers = {
    # cache_providerには、CloudFrontに関連するリソースを扱うAWS Providerを定義
    aws.cache_provider = aws.cache_provider
    # origin_providerには、オリジンに関連するリソースを扱うAWS Providerを定義(ここではデフォルトProviderを指定)
    aws.origin_provider = aws.origin_provider
  }

  s3_frontend_bucket_name = "test-epona-runtime-frontend-static"
  zone_name               = "example.com"
  record_name             = "test.example.com"

  tags = {
    Owner              = "john"
    Environment        = "runtime"
    RuntimeEnvironment = "development"
    ManagedBy          = "epona"
  }

  # カスタマイズしたResponse Header付与コードを使用する場合に指定
  # 「customize_security_header」ディレクトリに配置した「sample.js」の「exports.handler」メソッドが実行される
  viewer_response_lambda_source_dir            = "./customize_security_header"
  viewer_response_lambda_handler               = "sample.handler"
  viewer_response_lambda_log_retention_in_days = 90

  cloudfront_default_root_object = "index.html"
  cloudfront_http_version        = "http2"
  cloudfront_is_ipv6_enabled     = false
  cloudfront_price_class         = "PriceClass_All"
  cloudfront_web_acl_id          = try(data.terraform_remote_state.webacl.outputs.webacl.webacl_arn, null)  # webacl patternで作成したWebACLのARNを参照

  cloudfront_origin = {
    origin_path = "/hoge"
    custom_headers = [
      {
        name : "foo"
        value : "bar"
      },
      {
        name : "test"
        value : "sample"
      }
    ]
  }

  cloudfront_default_cache_behavior = {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = null
    default_ttl            = null
    max_ttl                = null
    compress               = false
  }

  cloudfront_viewer_certificate = {
    minimum_protocol_version = "TLSv1.2_2019"
  }

  cloudfront_custom_error_responses = [
    {
      error_code            = 404
      error_caching_min_ttl = 300
      response_code         = 200
      response_page_path    = "/"
    }
  ]

  cloudfront_logging_config = {
    bucket_name           = "test-epona-runtime-frontend-logging"
    prefix                = "sample"
    include_cookies       = false
  }
}

関連するpattern

cacheable_frontend patternに関連するpatternを、以下に記載します。

pattern名 説明
cd_pipeline_frontend Runtime環境へのデプロイメントパイプラインを構築する
webacl Runtime環境へのAWS WAFリソースを作成する

ログの集約

cacheable_frontend patternでは、CloudFrontおよびS3に対するアクセスログを保存します。
保存場所は、どちらもS3となります。

これらのログは、datadog_log_trigger patternを使用することでDatadogに集約できます。

入出力リファレンス

Requirements

Name Version
terraform ~> 0.14.10
aws >= 3.37.0, < 4.0.0

Inputs

Name Description Type Default Required
cloudfront_default_root_object ルートへのアクセス時に表示させるファイル名(例: index.html string n/a yes
record_name アプリケーションのエンドポイントとなるレコード名 string n/a yes
s3_frontend_bucket_name フロントの静的ファイルを配置する場所として作成するS3バケット名 string n/a yes
zone_name 事前作成済みのホストゾーン名 string n/a yes
cloudfront_custom_error_responses CloudFrontのエラーページアクセス時の動作設定
詳細はカスタムエラー応答の生成をご参照ください。
[
{
error_code = 403 # Required
error_caching_min_ttl = 300 # Optional
response_code = 200 # Optional
response_page_path = "/" # Optional
}
]
list(map(string)) [] no
cloudfront_default_cache_behavior CloudFrontのデフォルトのキャッシュ動作設定 any
{
"allowed_methods": [
"GET",
"HEAD"
],
"cached_methods": [
"GET",
"HEAD"
],
"compress": false,
"default_ttl": null,
"max_ttl": null,
"min_ttl": null,
"viewer_protocol_policy": "redirect-to-https"
}
no
cloudfront_http_version CloudFrontでサポートする最大のHTTPのバージョン(http1.1 or http2 string null no
cloudfront_is_ipv6_enabled CloudFrontでのIPv6有効化 bool null no
cloudfront_logging_bucket_force_destroy destroy時にbucketとともに保存されているデータを強制的に削除可能にする。create_cloudfront_logging_bucketをtrueにしたときにのみ有効。 bool false no
cloudfront_logging_config CloudFrontのアクセスログの設定
アクセスログを収集したい場合はbucket_nameの指定が必須
CloudFormationでの指定方法と同様なため、詳細はCloudFormationのドキュメントをご参照ください。
map(string)
{
"bucket_name": null,
"include_cookies": null,
"prefix": null
}
no
cloudfront_origin CloudFrontの配信ソースの設定
{
origin_path = 配信ソースとするバケットのスラッシュから始まるディレクトリパス(デフォルトでは「/」)
custom_headers = [
{
name : レスポンスヘッダーに付与するヘッダーの名前
value : レスポンスヘッダーに付与するヘッダーの値
}
]
}
any {} no
cloudfront_price_class CloudFrontで利用する価格クラス(PriceClass_All or PriceClass_200 or PriceClass_100 string null no
cloudfront_viewer_certificate CloudFrontのSSL化の設定 map(string)
{
"minimum_protocol_version": "TLSv1.2_2019"
}
no
cloudfront_web_acl_id CloudFrontで利用するWebACLのARN string null no
create_cloudfront_logging_bucket 新規にCloufFrontログ用のS3バケットを作成するか既存のバケットを使用するかを指定するフラグ(trueでバケットを新規に作成) bool true no
create_s3_access_log_bucket 新規にS3アクセスログ用のS3バケットを作成するか既存のバケットを使用するかを指定するフラグ(trueでバケットを新規に作成) bool true no
enable_s3_access_log S3へのアクセスをログとして記録する場合、trueを指定する bool true no
s3_access_log_bucket S3へのアクセスログ保存用S3バケット名 string null no
s3_access_log_bucket_force_destroy destroy時にbucketとともに保存されているデータを強制的に削除可能にする。create_s3_access_log_bucketをtrueにしたときにのみ有効。 bool false no
s3_access_log_object_prefix S3へのアクセスログ保存時に、オブジェクトに付与するprefix string null no
s3_frontend_bucket_force_destroy フロントの静的ファイル配置用S3バケットにファイルがあっても、destroyでバケットごと強制削除できるようにする bool false no
tags このモジュールで作成されるリソースに付与するタグ map(string) {} no
viewer_response_lambda_function_name CloudFrontからのレスポンスにSecurityHeaderを付与するLambda関数の名前 string "AddSecurityHeaderFunction" no
viewer_response_lambda_handler CloudFrontからのレスポンスにSecurityHeaderを付与するLambda関数のエントリーポイント string "index.handler" no
viewer_response_lambda_log_kms_key_id CloudFrontからのレスポンスにSecurityHeaderを付与するLambda関数のログデータを暗号化するための、KMS CMKのARNを指定する string null no
viewer_response_lambda_log_retention_in_days CloudFrontからのレスポンスにSecurityHeaderを付与するLambda関数がCloudWatch Logsへ出力するログの保存期間を設定する。

値は、次の範囲の値から選ぶこと: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653.
Resource: aws_cloudwatch_log_group / retention_in_days
number 3653 no
viewer_response_lambda_runtime CloudFrontからのレスポンスにSecurityHeaderを付与するLambda関数のランタイム string "nodejs12.x" no
viewer_response_lambda_source_dir CloudFrontからのレスポンスにSecurityHeaderを付与するLambda関数のソースコードが配置されたディレクトリのパス(デフォルト以外のコードを使用したい場合に指定) string null no
viewer_response_lambda_timeout 実行されたLambdaが停止するまでのタイムアウト設定。単位は秒で指定してください。 number 3 no

Outputs

Name Description
cloudfront_aliases CloudFrontのドメインに設定した別名(CNAME)
cloudfront_domain_name CloudFrontのドメイン名
cloudfront_id CloudFrontのID
cloudfront_logging_bucket CloudFrontのログ出力用のログ出力用のS3バケット名
cloudfront_logging_bucket_id CloudFrontのログ出力用のS3バケットのID
frontend_bucket_name ビルド済みファイルを配置するバケットの名前
frontend_bucket_origin_path ビルド済みファイルを配置するバケットのパス
s3_access_log_bucket S3へのアクセスログ出力用のS3バケット名
s3_access_log_bucket_id S3へのアクセスログ出力用のS3バケットのID

  1. リクエストされたパスに相当するファイルを返し、ファイルが存在しない場合は404を返す 


※ このドキュメントはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。

※ このドキュメントに記載されている会社名、製品名は、各社の登録商標または商標です。

© 2021 TIS Inc.