やりたいこと
同一ドメインで複数のSPAアプリケーション(Angular)を配信する要件があった為、今回下記の内容で実装をしました。
- 単一のCloudFrontから複数のOrigin(S3 static website hosting)にアクセスを振り分ける。
- Originへの振り分けルールはパスパターンで指定する。
- Angularのルーティングが正しく動作するようにする。
- やりたいこと
- 前提条件
- Angularのホスティング設定
- CloudFrontの設定
- Lambda@edgeでOriginへのリクエストパスを除外させる
- CloudFrontに設定が反映されるまで待つ
- CloudFrontのキャッシュを消す
- あとがき
- 参考
前提条件
- CloudFrontディストリビューションは作成済み。https配信できるよう代替ドメイン名(CNAME)を指定しておきます。
- 今回は例として
hogefuga.com
というドメインでアプリを配信し、以下のルールで配信するアプリを振り分けることとします。
Angularのホスティング設定
Angularのビルド設定
- ビルド時のコマンド
ng build
のオプションに--deploy-url /{パス名}/ --base-href /{パス名}/
を付与します。
AngularをS3から配信する
各Angularアプリ毎にS3を作成し、static website hostingの機能を使ってホスティングさせます。
- S3バケットを作成し、ビルドしたAngularのソースをアップロードする。必ずS3のルートにソースを配置する。
- 「静的ウェブサイトホスティング」を有効にする。
- 「ホスティングタイプ」は「静的ウェブサイトをホストする」を指定。
- 「インデックスドキュメント」は「index.html」を指定。
- 「エラードキュメント」は「index.html」を指定。
- S3はCloudFront経由でのみアクセスするよう制限したいので、「ブロックパブリックアクセス」をオフにしてから、「バケットポリシー」にRefererヘッダでのアクセス制限を追記します。値はランダム文字列等の推測不可能なものを指定しておきましょう。この値は後で使います。
バケットポリシーの例
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowCloudFrontReadonlyAccess", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::{S3バケット名}/*", "Condition": { "StringLike": { "aws:Referer": "{外部に推測されないような文字列}" } } } ] }
CloudFrontの設定
Originを追加する
OriginはCloudFrontが配信する具体的なリソースを指します。S3だけでなく、EC2やALBなどのAWSのサービスも指定ができます。
- AWSコンソールでCloudFrontのOriginを新たに作成。
- 「オリジンドメイン」にはS3のstatic website hosting有効化時に生成されるバケットウェブサイトエンドポイントのドメインを指定します。
- S3そのものドメインではないので注意。(かつてこの部分を勘違いしてドハマりしました)
- S3で指定したバケットポリシーを満たす為、カスタムヘッダーとして「Referer」ヘッダを追加し、バケットポリシーで指定した値を指定します。これでCloudFrontからOrigin(S3)へリクエストする際にRefererヘッダが付与され、S3にアクセスできるようになります。
- 同じ要領でS3の数だけOriginを追加していきます。
Behaviorを追加する
Behaviorではクライアントからのリクエストパスのパターンを判定してOriginへリクエストを振り分ける為の設定ができます。CloudFrontディストリビューションを作成した段階で「デフォルト(*)」というパスパターンがデフォルトで設定されているかと思います。
- Behaviorを新たに作成。
- パスパターンを指定します。「*」はワイルドカードです。例えば
/spa-app1/*
と指定するとリクエストパスが前方一致すると、次で指定するオリジンとオリジングループにリクエストが振り分けられます。 - オリジンとオリジングループに先ほど追加したOriginを指定します。
- ビューワープロトコルポリシーは「Redirect HTTP to HTTPS」を指定。
- 許可されたHTTPメソッドは「GET, HEAD」を指定。
- 同じ要領でOriginの数だけBehaviorを追加していきます。
エラーページは未指定にする
今回、S3側でAngularのルーティングを動作させる為のエラードキュメントの設定をしている為、CloudFrontでは何も指定しません。
Lambda@edgeでOriginへのリクエストパスを除外させる
この段階ではCloudFrontからOrigin(S3)へのアクセスが失敗する
ここまでの設定だと、クライアントからCloudFrontまでのリクエストは通るものの、CloudFrontからOrigin(S3)へのリクエストはパス付きで実施されてしまいます。
具体的には/angular-app1/******
といった形のパスが付いた状態でOriginへリクエストされます。
すると、S3側はルートではなく/angular-app1
という存在しないS3のフォルダを参照しようとしてしまい、エラーになってしまい、アプリが動作しないという問題が出てきます。
ここはかなり頭を悩ませましたが、Lambda@Edgeを使いCloudFrontからOriginへのリクエスト直前にパスを除去することで解決ができました。
Lambda@Edgeを作成する
Lambda@EdgeはCloudFrontで動作するLambdaで、CloudFrontへのリクエスト、レスポンスをトリガーに実行し、リクエストを加工したりリクエストに応じた処理をさせたりといったことができます。
- まずはnode.jsのLambdaを新規作成します。Lambda@Edgeはバージニア北部リージョンで作成する必要があります。
チュートリアル: シンプルな Lambda@Edge 関数の作成 - Amazon CloudFront
exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; // パスの内、一番最初のスラッシュとスラッシュで囲われた文字列だけを除外する。 // "/angular-app1/admin/config-page/"といったパスならば"/admin/config-page/"に変換される。 request.uri = request.uri.replace(/^\/[^\/]+\//,'/'); return callback(null, request); };
- Lambdaに適用しているIAMロールを編集し、Lambda@Edge利用に必要なIAM許可、およびサービスプリンシパルを付与しておきます。詳細は下記URL参照。
Lambda@Edge 用の IAM アクセス権限とロールの設定 - Amazon CloudFront
- 新しいLambdaのバージョンを発行します。
- 発行したバージョンのトリガーにCloudFrontを指定します。
- トリガーの設定では、対象のCloudFrontディストリビューションを指定し、「キャッシュ動作」にはBehaviorで指定したパスパターンがリストアップされるのでまず1つを指定します。
- 「Lambda@Edgeへのデプロイを確認」のチェックボックスにチェックを入れてから、トリガーを追加します。
- 他のBehaviorのパスパターン分のトリガーも同様の手順で追加していきます。
- このトリガー設定はCloudFrontのBehaviorの「関数の関連付け」から設定も可能です。
CloudFrontに設定が反映されるまで待つ
各種設定を変更したのでCloudFrontはデプロイ状態になっていると思います。最終変更日が「デプロイ」から日付に変わるまで数分かかるので待ちます。
CloudFrontのキャッシュを消す
CloudFrontはキャッシュがあればそれを使いまわしてクライアントへレスポンスするので、設定変更を即時反映したい場合はキャッシュ削除が必要になります。
- 「キャッシュ削除を作成」し、オブジェクトパスには「/*」を指定してキャッシュ削除を実施します。
- これで
https://hogefuga.com/angular-app1/
,https://hogefuga.com/angular-app2/
,https://hogefuga.com/angular-app3/
のそれぞれにアクセスすると、異なるAngularアプリへアクセスができるようになりました!
あとがき
CloudFrontは設定項目が多く恥ずかしながら漠然と使っていたところがあったので、仕様を理解する良い機会になりました。
今回Lambda@Edgeはパスを加工する為に利用しましたが、リクエスト、レスポンスをキャッチできるので外部からのアクセス分析や制御にも応用できそうですね。
参考
amazon web services - Multiple Cloudfront Origins with Behavior Path Redirection - Stack Overflow