Amazon Web Services ブログ

CloudFront VPC オリジンで実現するマルチリージョンのアクティブ/アクティブ構成

はじめに

現代のデジタル社会において、組織はサイバーセキュリティの脅威に対する懸念を強めており、インフラストラクチャをより適切に保護する方法を積極的に模索しています。高度化するサイバー攻撃の増加と、より厳格になるデータ保護規制により、コンテンツ配信インフラのセキュリティ確保は企業にとって重要事項となっています。安全なコンテンツ配信ソリューションの必要性は、かつてないほど高まっています。

最近、Amazon CloudFrontCloudFront VPC オリジン のサポートを発表しました。これにより、顧客は CloudFront ディストリビューションからのみプライベートサブネット内のオリジンにルーティングできるようになりました。このアーキテクチャでは、パブリック IP アドレスやインターネットゲートウェイが不要となり、防御と監視が必要な攻撃対象領域を効果的に最小化できます。

現代のインターネットベースのアプリケーションにおいて、セキュリティは重要ですが、顧客は包括的なアプリケーションアーキテクチャとして、高可用性も必要としています。AWS は、CloudFront VPC オリジンと CloudFront Functions を使用したマルチリージョンのアクティブ/アクティブ構成によって、これらの要件に対応できます。

ユースケース概要

図1. CloudFront VPC オリジンを使用したマルチリージョンのアクティブ/アクティブ構成

マルチリージョンのアクティブ/アクティブ構成を実装することで、複数の AWS リージョン(例:us-east-1 と us-west-1)でワークロードを同時に実行し、各リージョンがアクティブにトラフィックを処理します。CloudFront VPC オリジンは、CloudFront Functions を通じて加重ルーティングを実装することが可能であり、各リージョンの重みは CloudFront KeyValueStore に保存できます。これにより、再デプロイなしでリージョン間のトラフィック分散を調整できます。例えば、リージョン間でトラフィックを 50/50 に分割し、運用ニーズに基づいてリアルタイムで調整することが可能です。この構成により、単一リージョンに障害が発生した場合でも他のリージョンでサービスを継続できるため、アプリケーションの可用性が向上します。また、トラフィックの重み付け調整により、計画的なメンテナンスやパフォーマンス最適化時にも柔軟に対応できます。

マルチリージョン構成では、データベースのレプリケーション戦略も重要な検討事項となります。リージョン間でデータベースをレプリケーションする場合、レプリケーション遅延が発生するため、ユーザーが異なるリージョンにアクセスすると、データの不整合が発生する可能性があります。このような場合、Session Affinity の実装を推奨します。CloudFront Functions で Cookie ベースのルーティングを利用することで、同一ユーザーのリクエストを一貫して同じリージョンにルーティングし、データ整合性を保証できます。

さらにプライマリとセカンダリの 2 つの CloudFront VPC オリジンを持つオリジングループに対してトラフィック分散することにより、あるリージョンで障害が発生した場合に正常なリージョンにフェールオーバーするため、回復性を高めることも可能です。

この構成は、インフラストラクチャを保護するだけでなく、リージョン障害やその他のサービス中断を伴うシナリオにおいて、アプリケーションの可用性を確保します。CloudFront VPC オリジンとマルチリージョン展開の組み合わせにより、セキュリティと可用性が両立する包括的なソリューションを実現し、現代の重要システムの要件を満たすことが可能です。

セットアップ手順

ステップ 0:前提条件

セットアップを開始する前に、以下の前提条件が満たされていることを確認してください:

CloudFront ディストリビューション用の VPC オリジンは、AWS Management Console または AWS Command Line Interface(CLI) を使用して作成できます。このブログ記事では、AWS コンソールを使用して作成する方法を説明します。

ステップ 1:VPC オリジンのセットアップ

1. Amazon CloudFront コンソールを開き、[VPC origins] を選択し、[Create VPC origin] をクリックして、新しい VPC オリジンを作成します。以下に示すように、VPC オリジン名を入力し、オリジン ARN を選択します。


図2. VPC オリジンの作成

2. 両リージョンの VPC オリジンが作成されたことを確認してください。新しい VPC オリジンのステータスは最初 “Deploying” となり、以下に示すように、使用可能な状態になると “Deployed” に変わります。


図3. VPC オリジンのデプロイ確認

3. Amazon CloudFront コンソールを開き、[Distributions] を選択してから、該当するディストリビューションを選択します。次に [Origins] タブを選択し、[Create origin] をクリックします。Origin domain として VPC オリジンを選択し、[Create origin] をクリックして、以下に示すようにディストリビューションに VPC オリジンを追加します。


図4. ディストリビューションへの VPC オリジンの追加

4. 以下に示すように、両方の VPC オリジンがディストリビューションに追加されたことを確認します。


図5. ディストリビューションへの VPC オリジン追加の確認

5. Amazon CloudFront コンソールを開き、[Distributions] を選択してから、該当するディストリビューションを選択します。次に [Origins] タブを選択し、[Create origin group] をクリックします。以下に示すように、Origins のプライマリとセカンダリの VPC オリジンに加え、 Failover criteria を指定し、デフォルト用のオリジングループを作成します。

図6. ディストリビューションへのオリジングループ追加

6. 以下に示すように、オリジングループがディストリビューションに追加されたことを確認します。

図7. ディストリビューションへのオリジングループ追加の確認

ステップ 2:CloudFront KeyValueStore の設定

1. 以下に示すように、キーと値が含まれる JSON ファイルを S3 バケットにアップロードします。キーと値のペアは、CloudFront KeyValueStore コンソールから直接追加することもできます。このブログ記事では、JSON ファイルをアップロードし、KeyValueStore 作成時に S3 URI を指定する方法で実施します。

{
    "data": [
        {
            "key": "origin-group-us-east-1-primary",
            "value": "50"
        },
        {
            "key": "origin-group-us-west-1-primary",
            "value": "50"
        }
    ]
}

図8. VPC オリジンの重み付け設定用 JSON ファイル(キー:オリジン名、値:重み)


図9. JSON ファイルの S3 バケットへのアップロード

2. Amazon CloudFront コンソールを開き、[Functions][KeyValueStores] タブを選択します。[Create KeyValueStores] をクリックして KeyValueStore を作成し、以下に示すように、KeyValueStore 名を入力し、JSON ファイルの S3 URI を指定した後、[Create] をクリックします。

図10. KeyValueStoreの作成

3. KeyValueStore が作成されたことを確認します。新しい KeyValueStore の Last modified は、最初 “Provisioning” と表示され、以下に示すように、使用可能になるとタイムスタンプに変更されます。


図11. KeyValueStore のデプロイ確認

ステップ 3:CloudFront Functions の設定

1. Amazon CloudFront コンソールを開き、[Functions] を選択します。[Create function] から以下に示すように、関数名を入力して JavaScript バージョンを指定した後、[Create function] をクリックして関数を作成します。


図12. 重み付けルーティング用の関数作成

2. [Associate existing KeyValueStore] をクリックし、作成した KeyValueStore を選択して、関数に KeyValueStore を関連付けます。以下に示すように、選択後、[Associate KeyValueStore] をクリックします。


図13. 関数への KeyValueStore の関連付け

3. [Development] ウィンドウ内の [Build] タブで重み付けルーティングのロジックを実装し、以下に示すように [Save changes] をクリックします。

import cf from "cloudfront";

// Initialize CloudFront KeyValueStore
const kvs = cf.kvs();

/**
 * Origin Group configurations
 * OriginGroup1: us-east-1 (primary), us-west-1 (secondary)
 * OriginGroup2: us-west-1 (primary), us-east-1 (secondary)
 */
const originGroups = [
    {
        name: "origin-group-us-east-1-primary",
        originIds: [
            "vpc-origin-us-east-1-internal-webapp-alb",
            "vpc-origin-us-west-1-internal-webapp-alb"
        ]
    },
    {
        name: "origin-group-us-west-1-primary",
        originIds: [
            "vpc-origin-us-west-1-internal-webapp-alb",
            "vpc-origin-us-east-1-internal-webapp-alb"
        ]
    }
];

/**
 * Failover criteria configuration
 * Defines which HTTP status codes should trigger failover to secondary origin
 */
const failoverCriteria = {
    statusCodes: [500, 502, 503, 504]
};

/**
 * Selects an Origin Group index based on weighted probability
 * 
 * @param {Array<number>} weights - Array of weights corresponding to each Origin Group
 * @returns {number} - Index of the selected Origin Group
 */
function getOriginGroupIndex(weights) {
    // Calculate the sum of all weights
    const sum = weights.reduce((total, weight) => total + weight, 0);
    
    // Generate a random number between 1 and the sum of weights
    let choice = Math.floor(Math.random() * sum) + 1;
    
    // Start from the last Origin Group and work backwards
    let index = weights.length - 1;
    
    // Subtract each weight from our random number until we find the selected Origin Group
    while ((choice -= weights[index]) > 0) {
        index -= 1;
    }
    
    return index;
}

/**
 * Retrieves the current weight configuration for each Origin Group from KVS
 * 
 * @returns {Promise<Array<number>>} - Array of weights for each Origin Group
 */
async function getWeightsFromKvs() {
    const weights = [];
    
    // Fetch weight for each Origin Group from the KeyValueStore
    for (let i = 0; i < originGroups.length; i++) {
        const weight = await kvs.get(originGroups[i].name);
        weights.push(parseInt(weight, 10)); // Convert string to integer with base 10
    }
    
    return weights;
}

/**
 * CloudFront function handler that routes requests to Origin Groups based on weighted distribution
 * 
 * @param {Object} event - CloudFront event object
 * @returns {Object} - Modified request object
 */
async function handler(event) {
    // Get current weights for each Origin Group
    const weights = await getWeightsFromKvs();
    
    // Select an Origin Group based on the weighted distribution
    const selectedOriginGroupIndex = getOriginGroupIndex(weights);
    const selectedOriginGroup = originGroups[selectedOriginGroupIndex];
    
    // Create and route to the selected Origin Group with originIds array and failover criteria
    cf.createRequestOriginGroup({
        originIds: selectedOriginGroup.originIds,
        failoverCriteria: failoverCriteria
    });
    
    return event.request;
}

図14. 重み付けルーティング用の関数サンプルコード
注意: このコードはあくまでサンプルコードです。本番環境での利用を想定したものではありません。


図15. 開発ウィンドウでの重み付けルーティングロジックの実装

4. [Publish] タブを選択し、以下に示すように [Publish function] をクリックして関数を公開します。


図16. 関数の公開

5. 関数が公開されたことを確認します。以下に示すように、ステータスは “Development” から “Published” に変更されます。


図17. 関数の公開確認

6. Amazon CloudFront コンソールを開き、[Distributions] を選択してから、該当するディストリビューションを選択します。次に [Behaviors] タブを選択し、[Create Behavior] をクリックします。以下に示すように、Path pattern の入力と Origin and origin groups にデフォルト用のオリジングループを指定します。

図18. Path pattern と Origin and origin groups の入力

7. [Function associations] セクションで、Function type として CloudFront Functions を選択し、Viewer request として先ほどの関数を選択します。以下に示すように、最後に [Create behavior] をクリックします。


図19. Viewer request としての関数の関連付け

ステップ 4:関数のテスト

1. 以下に示すように、動作を編集し、キャッシュポリシーとして [CachingDisabled] を選択します。これは、重み付けに従って両方の VPC オリジンにトラフィックが分散されていることを確認するため、意図的にキャッシュを無効にするための設定です。


図20. キャッシュポリシーで [CachingDisabled] を選択

2. 以下に示すように、CloudFront ドメインに対して複数回リクエストを実行し、両方の VPC オリジンにトラフィックが分散されていることを確認します。

$ echo ""; for i in `seq 1 5`; do echo "Request [$i]"; curl <your-distribution-id>.cloudfront.net; echo ""; echo ""; done

Request [1]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

Request [2]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

Request [3]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

Request [4]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

Request [5]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

図21. CloudFrontドメインへの curl コマンド

3. Amazon CloudFront コンソールを開き、[Functions][KeyValueStores] タブを選択します。KeyValueStore を選択し、[Key value pairs] セクションで [Edit] をクリックします。以下に示すように、VPC オリジンの重み値を変更し、[Save changes] をクリックします。

図22. KeyValueStore コンソールでの重み値の変更

4. 以下に示すように、CloudFront ドメインに対して再度複数回リクエストを実行し、変更が反映されていることを確認します。

$ echo ""; for i in `seq 1 5`; do echo "Request [$i]"; curl <your-distribution-id>.cloudfront.net; echo ""; echo ""; done

Request [1]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

Request [2]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

Request [3]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

Request [4]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

Request [5]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-west-1 VPC origin</h1>
</html>

図23. us-east-1 の重みを 0、us-west-1 の重みを 100 に設定した場合の CloudFront ドメインへの curl コマンド

5. Amazon EC2 コンソールを開き、VPC オリジンとして設定されている us-west-1 の ALB リスナー Default action として [Returned fixed response] を選択し、Response code を 500 エラーに設定

図24. Amazon EC2 コンソールで ALB リスナーの Default action を変更

6. 以下に示すように、CloudFront ドメインに対して再度複数回リクエストを実行し、セカンダリーの us-east-1 にフェイルオーバしていることを確認します。

$ echo ""; for i in `seq 1 5`; do echo "Request [$i]"; curl <your-distribution-id>.cloudfront.net; echo ""; echo ""; done

Request [1]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

Request [2]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

Request [3]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

Request [4]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

Request [5]
<!DOCTYPE html>
<html>
<h1>index.html delivered from us-east-1 VPC origin</h1>
</html>

図25. us-west-1 の ALB リスナーが 500 エラーを返す設定をした状態の CloudFront ドメインへの curl コマンド

まとめ

セキュリティと可用性という重要な要件に基づき、このソリューションでは CloudFront VPC オリジンと CloudFront Functions を効果的に活用してこれらの要件を達成する方法を示しました。

このソリューションは非常に柔軟性が高く、ユーザーセッションの維持、ユーザーの位置に基づくパフォーマンスの最適化、特定のアプリケーション要件への対応など、さまざまなニーズに応じてトラフィックルーティングをカスタマイズできます。この柔軟性により、セキュリティと可用性を維持しながら、幅広いビジネスニーズに対応することが可能です。

このブログが、重要なアプリケーションのための強固なソリューションの構築に役立ち、堅牢なセキュリティ制御とサービスの継続的な可用性を確保しながら、ビジネスを成長させる一助となれば幸いです。

このブログの著者

針谷 浩紀 (Hiroki Harigai)
Technical Account Manager