1つのCloudFrontから複数のS3バケットへパスで振り分ける方法

この記事は約10分で読めます

こんにちは、 @Manabu です。

CloudFrontとS3で静的サイトを運用していると、同じドメインの中で別のWebアプリやゲームを公開したい場合があります。

すべてのコンテンツを1つのS3バケットへ配置することもできますが、更新方法や管理者が異なる場合は、S3バケットを分けた方が管理しやすくなります。

CloudFrontでは、1つのディストリビューションに複数のオリジンを登録し、リクエストされたパスによって転送先を変更できます。

今回は、ブログとWebアプリを別々のS3バケットで管理し、1つのCloudFrontから配信する方法を紹介します。

この記事は、以下のような方におすすめです。

こんな方におすすめ

  • 1つのドメインで複数の静的コンテンツを公開したい
  • ブログとWebアプリのS3バケットを分けたい
  • CloudFrontのオリジンとビヘイビアの関係を知りたい
  • パスを振り分けた後にAccessDeniedが発生している

今回作成する構成

例として、次の構成を作成します。

https://example.com/
├── ブログ                         → blog-bucket
└── /products/app/sample-app/     → products-bucket

CloudFrontには、2つのS3バケットをオリジンとして登録します。

利用者
  ↓ https://example.com/...
CloudFront
  ├── Default (*)             → ブログ用S3
  └── products/app/*          → 制作物用S3

利用者から見るドメインは同じですが、CloudFrontがパスを確認し、対応するS3バケットへリクエストを転送します。

オリジンとキャッシュビヘイビアの役割

CloudFrontでパスを振り分ける場合は、オリジンとキャッシュビヘイビアを設定します。

設定 役割
オリジン CloudFrontがファイルを取得する場所
キャッシュビヘイビア どのパスを、どのオリジンへ転送するかを決める設定

オリジンを追加しただけでは、そのS3バケットは使用されません。追加したオリジンへリクエストを送るキャッシュビヘイビアも必要です。

S3バケットを準備する

ブログ用とWebアプリ用に、2つのS3バケットを用意します。

example-blog-bucket
example-products-bucket

CloudFrontのOACを利用する場合、S3の静的Webサイトホスティング機能を有効にする必要はありません。

各バケットのBlock Public Accessを有効にし、S3へ直接アクセスできない状態にします。

今回のWebアプリ用バケットには、次のようにファイルを配置します。

example-products-bucket
└── products/
    └── app/
        └── sample-app/
            ├── index.html
            ├── assets/
            │   ├── style.css
            │   └── app.js
            └── images/
                └── main.webp

CloudFrontへ2つ目のオリジンを追加する

AWSマネジメントコンソールからCloudFrontを開き、対象のディストリビューションを選択します。

「オリジン」タブから「オリジンを作成」を選択し、Webアプリ用のS3バケットを登録します。

主な設定は次のとおりです。

項目 設定例
オリジンドメイン example-products-bucket.s3.ap-northeast-1.amazonaws.com
名前 products-s3-origin
オリジンアクセス Origin access control settings
リクエストへの署名 Sign requests

OACを使用する場合は、S3の静的Webサイトエンドポイントではなく、通常のS3バケットオリジンを選択します。

S3バケットポリシーを設定する

OACを関連付けても、S3バケット側でCloudFrontからの読み取りを許可していなければ、ファイルを取得できません。

Webアプリ用バケットへ、次のようなバケットポリシーを設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalReadOnly",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-products-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1234567890ABC"
        }
      }
    }
  ]
}

次の値は、自分のAWS環境に置き換えてください。

  • example-products-bucket:Webアプリ用S3バケット名
  • 123456789012:AWSアカウントID
  • E1234567890ABC:CloudFrontディストリビューションID

ブログ用バケットにも、同じCloudFrontディストリビューションからの読み取りを許可するポリシーが必要です。

パスを振り分けるビヘイビアを作成する

CloudFrontの「ビヘイビア」タブを開き、「ビヘイビアを作成」を選択します。

Webアプリ用のビヘイビアを、次のように設定します。

項目 設定例
パスパターン /products/app/*
オリジン products-s3-origin
Viewer protocol policy Redirect HTTP to HTTPS
Allowed HTTP methods GET, HEAD, OPTIONS
Cache policy Managed-CachingOptimized
Compress objects automatically Yes

先頭の/は省略できます。CloudFrontでは、/products/app/*とproducts/app/*は同じパターンとして扱われます。

設定後は、次のようにリクエストが振り分けられます。

リクエストパス 使用されるビヘイビア 転送先
/ Default * ブログ用S3
/aws/example/ Default * ブログ用S3
/products/ Default * ブログ用S3
/products/app/sample-app/ /products/app/* Webアプリ用S3

制作物の一覧ページをブログ側で生成し、その配下にあるWebアプリ本体だけを別のS3から配信する構成にもできます。

ビヘイビアの順番に注意する

CloudFrontは、ビヘイビアを上から順番に確認し、最初に一致した設定を使用します。

例えば、次の2つを設定する場合があります。

/products/app/*
/products/*

/products/app/*をより具体的な設定として使用したい場合は、/products/*より上に配置します。

1. /products/app/*
2. /products/*
3. Default (*)

広い範囲に一致するパターンを上にすると、目的のオリジンへリクエストが送られない場合があります。

最初に一致したビヘイビアが使用される

複数のパスパターンに一致しても、より具体的なパターンが自動的に選ばれるとは限りません。CloudFrontコンソール上の順番も確認してください。

CloudFrontはパスの先頭を自動で削除しない

パスを振り分ける時に、特に注意したい点です。

利用者が次のURLへアクセスしたとします。

https://example.com/products/app/sample-app/index.html

CloudFrontのビヘイビアが/products/app/*であっても、S3へ送られるパスから/products/app/が自動的に削除されるわけではありません。

通常は、S3にも同じオブジェクトキーを用意します。

products/app/sample-app/index.html

S3のルートへ次のように配置しただけでは、パスが一致しません。

sample-app/index.html

この状態では、S3にファイルが存在していても403や404になる可能性があります。

URLとS3のオブジェクトキーを同じ構造にしておくと、設定を理解しやすくなります。

オリジンパスを使う場合

CloudFrontのオリジンには、オリジンパスを設定できます。

例えば、オリジンパスに/publicを設定した状態で、次のURLへアクセスした場合です。

/products/app/sample-app/index.html

CloudFrontがS3から取得するオブジェクトキーは、次のようになります。

public/products/app/sample-app/index.html

オリジンパスは、リクエストパスの前へ文字列を追加する設定です。/products/app/を取り除く設定ではありません。

リクエストパスを変更したい場合は、CloudFront FunctionsやLambda@EdgeなどによるURIの書き換えを検討します。

ディレクトリ形式のURLを使用する場合

通常のS3バケットオリジンでは、次のURLから自動的にindex.htmlが返されるとは限りません。

/products/app/sample-app/

実際のS3オブジェクトは次のようになっています。

/products/app/sample-app/index.html

ディレクトリ形式のURLを使用する場合は、CloudFront Functionsなどでindex.htmlを補完します。

この処理については、以下の記事で紹介しています。

REFERENCEOACで非公開S3を使う静的サイト!CloudFront Functionでindex.htmlへ変換IT自習室

関数は、Webアプリ用のビヘイビアにも関連付ける必要があります。Defaultビヘイビアだけに設定しても、別のビヘイビアへ振り分けられたリクエストには適用されません。

公開後に動作を確認する

CloudFrontの設定反映後、ブラウザからWebアプリのURLへアクセスします。

https://example.com/products/app/sample-app/

次の項目を確認します。

  • HTMLが表示される
  • CSS、JavaScript、画像が読み込まれる
  • ブラウザの開発者ツールに403や404が出ていない
  • ブログ側のページもこれまでどおり表示される
  • S3のオブジェクトURLへ直接アクセスできない

Webアプリ側では、CSSやJavaScriptのパスにも注意します。

<link rel="stylesheet" href="./assets/style.css">
<script src="./assets/app.js"></script>

制作物ごとにパスが変わる場合は、相対パスを使用すると別のディレクトリへ配置しやすくなります。

AccessDeniedが発生した場合の確認項目

CloudFrontからXML形式のAccessDeniedが返された場合は、次の順番で確認します。

  1. リクエストが目的のビヘイビアに一致しているか
  2. ビヘイビアが正しいS3オリジンを向いているか
  3. S3にリクエストパスと同じオブジェクトキーがあるか
  4. オリジンパスが意図せず設定されていないか
  5. OACがS3オリジンへ関連付けられているか
  6. バケットポリシーのバケット名、アカウントID、ディストリビューションIDが正しいか
  7. CloudFront Functionによる書き換え後のURIが正しいか

設定を変更した直後は、CloudFrontのステータスがデプロイ完了になるまで待ってから再確認します。

コンテンツの更新方法

Webアプリの更新は、対象のS3プレフィックスだけに同期します。

aws s3 sync ./dist/ \
  s3://example-products-bucket/products/app/sample-app/ \
  --delete \
  --dryrun

実行内容に問題がなければ、--dryrunを外して同期します。

その後、WebアプリのパスだけCloudFrontのキャッシュを削除します。

aws cloudfront create-invalidation \
  --distribution-id E1234567890ABC \
  --paths "/products/app/sample-app/*"

詳しい更新手順は、以下の記事で紹介しています。

REFERENCE静的WebアプリをS3へ更新!syncとCloudFrontキャッシュ削除の手順IT自習室

まとめ

今回は、1つのCloudFrontディストリビューションに複数のS3オリジンを登録し、パスで振り分ける方法を紹介しました。

設定時のポイントは、次のとおりです。

  • 配信元となるS3バケットをオリジンとして登録する
  • パスパターンと転送先オリジンをキャッシュビヘイビアで指定する
  • 具体的なパスパターンを上へ配置する
  • 各S3バケットでOACからの読み取りを許可する
  • URLとS3のオブジェクトキーを同じ構造にする
  • ディレクトリ形式のURLを使う場合はindex.htmlを補完する

この構成を利用すると、ブログ、Webアプリ、ゲームなどを別々のS3バケットで管理しながら、利用者には同じドメインで提供できます。

CloudFrontと非公開S3を使った基本構成については、以下の記事も参考にしてください。

REFERENCECloudFrontと非公開S3で静的サイトを配信する方法|OAC構成を解説IT自習室

参考資料

REFERENCECache behavior settings - Amazon CloudFrontdocs.aws.amazon.comREFERENCEOrigin settings - Amazon CloudFrontdocs.aws.amazon.comREFERENCERestrict access to an Amazon S3 origin - Amazon CloudFrontdocs.aws.amazon.com