Envoy を使って Cloud Memorystore のクロスリージョン レプリカを作成する
Google Cloud Japan Team
※この投稿は米国時間 2022 年 10 月 19 日に、Google Cloud blog に投稿されたものの抄訳です。
インメモリ データベースは、オンラインのショッピング カートに商品を追加する場合や、パーソナライズされたおすすめコンテンツを表示する場合、最新のアカウント残高をチェックする場合などに、ユーザーに対するレイテンシを可能な限り低く抑えるうえで重要な役割を担うコンポーネントです。最もよく使われているインメモリ ストアは Redis ですが、Memorystore を使用すれば、この種のアプリケーションを Google Cloud 上で開発する際に、Redis のスピードと優れた機能を簡単に活用できます。Memorystore for Redis は、スタンダード ティアのインスタンスでゾーンの可用性が 99.9% という高さの SLA を提供します。ただし場合によっては、リージョン障害の障害復旧シナリオに対応すること、またはマルチリージョンでアプリケーションをデプロイするために可能な限り低いレイテンシを提供することを目的として、Memorystore のフットプリントを複数のリージョンに拡張することを検討します。ここでは、Envoy プロキシの Redis フィルタを利用して、このようなアーキテクチャをデプロイする方法について説明します。これは、Cloud Memorystore と Envoy で新たな高みにスケールアップという以前のブログで紹介した方法です。Envoy には、サポートされている構成が多数存在するため、このようなアーキテクチャをシンプルかつ拡張性の高い方法で作成できます。それでは、同様のソリューションの構築方法を説明する実践的なチュートリアルを始めましょう。
アーキテクチャの概要
まず、Google Cloud のネイティブ サービスとオープンソース ソフトウェアを組み合わせて、マルチリージョンの Memorystore アーキテクチャを実現する方法について説明します。ここでは Envoy を使用して、別々のリージョンに作成する 2 つの Memorystore インスタンスにトラフィックをミラーリングします。今回は円滑に進めるため、Redis の負荷を生成する CLI として一般的な Memtier Benchmark をサンプル アプリケーションとして使用し、エンドユーザーのトラフィックをシミュレートします。実際に試してみる際は、既存のアプリケーションを使用するか、独自に作成してもかまいません。
Envoy のトラフィック ミラーリング構成により、各種バックエンド インスタンスの存在を意識する必要はありません。プロキシに接続するだけです。以下にアーキテクチャのサンプルを示し、各主要コンポーネントの詳細を簡単に説明します。
始める前に、Envoy が現在サポートしている Redis コマンドの一覧を確認し、使用するアプリケーションとの互換性を確認しておいてください。
前提条件
このチュートリアルを進めるには、以下の操作を行う権限を持つ Google Cloud プロジェクトが必要です。
Cloud Memorystore for Redis インスタンスのデプロイ(必要な権限)
SSH アクセスが可能な GCE インスタンスのデプロイ(必要な権限)
Cloud Monitoring の閲覧権限(必要な権限)
Cloud Shell または他の gCloud 認証された環境へのアクセス
マルチリージョンの Memorystore バックエンドのデプロイ
まず、アプリケーションのすべてのトラフィックを処理するバックエンドの Memorystore for Redis キャッシュをデプロイします。リージョンのサービス停止が発生した場合に、デプロイしたインスタンスを保護するため、別々のリージョンに 2 つのインスタンスをデプロイします。ここでは us-west1 と us-central1 のリージョンを選択しましたが、自身のユースケースに最も適したリージョンを自由に選択できます。
認証された Cloud Shell 環境では、次のように実行できます。
$ gcloud redis instances create memorystore-primary --size=1 --region=us-west1 --tier=STANDARD --async
$ gcloud redis instances create memorystore-standby --size=1 --region=us-central1 --tier=STANDARD --async
プロジェクトで Memorystore for Redis API が有効になっていない場合は、コマンドを実行すると API を有効にするように求められます。Memorystore インスタンスのデプロイには通常数分かかりますが、その間に次のステップに進むことができます。
クライアントとプロキシ VM の作成
次に、Redis クライアントと Envoy プロキシをデプロイするための VM が必要になります。リージョンの障害から保護するために、リージョンごとに GCE インスタンスを作成します。各インスタンスに、Envoy と Memtier Benchmark の 2 つのアプリケーションをコンテナとしてデプロイします。このようなデプロイは「サイドカー アーキテクチャ」と呼ばれ、Envoy の一般的なデプロイモデルとなっています。この方法でデプロイすると、物理的なネットワーク ホップが追加で発生することがなくなるため、ネットワークのレイテンシが追加されることはほとんどありません。
まず、プライマリ リージョンの VM を作成します。
$ gcloud compute instances create client-primary --zone=us-west1-a --machine-type=e2-highcpu-8 --image-family cos-stable --image-project cos-cloud
次に、セカンダリ リージョンの VM を作成します。
$ gcloud compute instances create client-standby --zone=us-central1-a --machine-type=e2-highcpu-8 --image-family cos-stable --image-project cos-cloud
Envoy プロキシの構成とデプロイ
プロキシをデプロイする前に、Memorystore のエンドポイントを適切に構成するために必要な情報を収集する必要があります。これを行うには、すでに作成した Memorystore インスタンスのホスト IP アドレスが必要です。これは、次のように収集できます。
gcloud redis instances describe memorystore-primary --region us-west1 --format=json | jq -r ".host"
gcloud redis instances describe memorystore-standby --region us-central1 --format=json | jq -r ".host"
これらの IP アドレスは、Envoy の構成ですぐに使用するため、アクセスしやすい場所にコピーしておきます。また、Memorystore コンソール ページの [プライマリ エンドポイント] 列でアドレスを確認することもできます。
次に、Envoy プロキシをデプロイするために、新しく作成した各 VM インスタンスに接続する必要があります。Google Cloud コンソールの SSH で簡単に接続できます。詳細はこちらをご覧ください。インスタンスへの接続に成功したら、Envoy の構成を作成します。
まず、インスタンス上に envoy.yaml
という名前のファイルをテキスト エディタで新規作成します。以下の .yaml ファイルを使用して、作成したプライマリ インスタンスおよびセカンダリ インスタンスの IP アドレスを入力します。
各種構成インターフェースの説明は次のとおりです。
Admin: このインターフェースはオプションであり、構成や統計情報などを表示できます。また、Envoy プロキシのさまざまな要素をクエリしたり変更したりできます。
Static_resources: Envoy プロキシの起動時に構成される項目が含まれています。この中で、クラスタとリスナーのインターフェースを定義しています。
Clusters: このインターフェースでは、リージョン単位で定義するクラスタを定義できます。クラスタ構成では、利用可能なすべてのホストと、それらのホストに負荷を分散する方法を定義します。ここでは、プライマリ リージョンとセカンダリ リージョンに一つずつクラスタを定義しています。各クラスタでは、それぞれ異なる一連のホストとロードバランサ ポリシーを構成できます。今回は各クラスタにホストが一つしかなく、すべてのリクエストがそのホストに転送されるため、任意のロードバランサ ポリシーを使用できます。
Listeners: このインターフェースにより、クライアントが接続するポートを公開し、受信したトラフィックの動作を定義できます。この例では、各リージョンの Memorystore インスタンスに対応する 2 つのリスナーを定義しています。
Memorystore インスタンスの IP アドレスを追加したら、そのファイルをコンテナ OS の VM にローカルに保存し、簡単に参照できるようにします。セカンダリ インスタンスについても、このステップを繰り返してください。
Docker を使って公式 Envoy のプロキシ イメージを pull し、独自の構成で実行します。プライマリ リージョンのクライアント マシンで、次のコマンドを実行します。
$ docker run --rm -d -p 8001:8001 -p 6379:1999 -v $(pwd)/envoy.yaml:/envoy.yaml envoyproxy/envoy:v1.21.0 -c /envoy.yaml
スタンバイ リージョンのクライアント マシンで、次のコマンドを実行します。
$ docker run --rm -d -p 8001:8001 -p 6379:2000 -v $(pwd)/envoy.yaml:/envoy.yaml envoyproxy/envoy:v1.21.0 -c /envoy.yaml
今回使用するスタンバイ リージョンでは、バインディング ポートをポート 2000 に変更しています。これは、リージョン障害によってプライマリ インスタンスが利用できなくなった場合に、スタンバイ クライアントからのトラフィックが確実にスタンバイ インスタンスにルーティングされるようにするためです。
この例では、手動で Envoy プロキシをデプロイしていますが、実際の環境では Envoy プロキシをデプロイし、リージョン ベースの構成に応じてポートをバインドする CI/CD パイプラインを実装します。
これで Envoy がデプロイされたので、コンテナ VM から管理インターフェースにアクセスしてテストできます。
$ curl -v localhost:8001/stats
成功していれば、ターミナルに Envoy 管理者の各種統計情報が出力されるはずです。まだトラフィックがない状態では、これらは特に有用ではありません。しかし、コンテナが実行されていること、ネットワーク上で利用可能であることを確認できます。このコマンドが成功しない場合は、Envoy コンテナが実行されていることを確認することをおすすめします。一般的な問題としては、envoy.yaml の構文エラーがあります。この構文エラーは、Envoy コンテナをインタラクティブに実行して、ターミナルの出力を読むことで見つけることができます。
Memtier Benchmark のデプロイと実行
SSH 経由で us-west1 のプライマリ クライアント インスタンスに再接続したら、Memtier Benchmark ユーティリティをデプロイします。これは、人為的な Redis トラフィックを生成するために使用します。Memtier Benchmark を使用するため、独自のデータセットを用意する必要はありません。このユーティリティは、一連の set コマンドを使用して、キャッシュにデータを入力します。
キャッシュの内容の検証
プライマリ リージョンのクライアントからデータが生成されていますので、そのデータが両方のリージョンの Memorystore インスタンスに書き込まれていることを確認しましょう。この作業は、Cloud Monitoring の Metrics Explorer を使用して実施できます。次に、[エクスプローラ] ペイン上部で選択できる [MQL] から、グラフを構成します。スムーズに進行するため、コンソールに貼り付けるだけでグラフを作成できるクエリを用意しました。Memorystore インスタンスを異なる命名規則で作成した場合、または同じプロジェクト内に他の Memorystore インスタンスがある場合は、resource.instance_id フィルタを変更する必要があるかもしれません。終了したら、チャートが適切な時間範囲を表示していることを確認し、次のように表示されることを確認します。
このグラフでは、両方の Memorystore インスタンスで同じ数のキーを示す同じような 2 本の線が表示されるはずです。単一のインスタンスの指標を表示する場合は、特定のインスタンスを選択した後で、Memorystore コンソールから利用できるデフォルトのモニタリング グラフを使用します。
リージョン障害のシミュレート
リージョン障害は、まれな出来事です。ここでは、プライマリの Memorystore インスタンスとプライマリのクライアント VM を削除して障害をシミュレートします。
まず、次のようにプライマリの Memorystore インスタンスを削除しましょう。
$ gcloud redis instances delete memorystore-primary --region=us-west1
次に、クライアント VM を削除します。
$ gcloud compute instances delete client-primary
次に、スタンバイ アプリケーションとして使用しているセカンダリ リージョンのクライアント VM から、トラフィックを生成する必要があります。
今回は時間を節約するために、手動でフェイルオーバーを実行してトラフィックを生成します。実際の環境では、プライマリ リージョンを利用できなくなった場合に、自動的にスタンバイ リージョンにトラフィックを振り分けるフェイルオーバー戦略を策定する必要があります。これは一般的に、Cloud ロードバランサなどのサービスを使用して行います。コンソールから、もう一度セカンダリ リージョンのクライアント VM に SSH 接続し、前のセクションで説明したように Memtier Benchmark アプリケーションを実行します。コンソールのモニタリング グラフをもう一度確認することで、読み取りと書き込みがスタンバイ インスタンスに適切にルーティングされていることを確認できます。
元のプライマリ Memorystore インスタンスが再度利用可能な状態になると、Envoy の構成に基づき、そのインスタンスが新しいスタンバイ インスタンスになります。また、利用できない間は書き込みができないため、新しいプライマリ インスタンスとの同期が取れていない状態になります。この投稿記事では詳細な解決策について説明しませんが、多くのユーザーはキーに対して TTL を設定し、キャッシュが最終的に同期されるタイミングを決めています。
クリーンアップ
不要な課金を避けるために、数分かけてリソースをクリーンアップする必要があります。以下のものを消去する必要があります。
デプロイしたすべての Memorystore インスタンス
デプロイしたすべての GCE インスタンス
Memorystore インスタンスは、次のように削除できます。
$ gcloud redis instances delete <instance-name> --region=<region>
GCE コンテナ OS インスタンスは、次のように削除できます。
$ gcloud compute instances delete <instance-name>
インスタンスを追加で作成した場合は、それらをスペースで区切るだけで、単一のコマンドに結合できます。
まとめ
Cloud Memorystore はスタンダード ティアでも高可用性を提供しますが、ユースケースによってはさらに高い可用性を保証する必要があります。Envoy とその Redis フィルタを使用することで、マルチリージョン デプロイをシンプルかつ拡張性の高いものにすることができます。上記で紹介したアウトラインは、その第一歩として最適です。これらの手順は、自動化されたリージョン フェイルオーバーや、デュアル リージョンのアクティブ / アクティブ デプロイをサポートするように簡単に拡張できます。Cloud Memorystore の詳細については、ドキュメントを参照するか、ご希望の機能については、公開 Issue Tracker でリクエストしてください。- Cloud Memorystore プロダクト マネージャー Matt Geerling
- Cloud アーキテクト マネージャー Kundan Kumar