WebSocket の使用

このページでは、Cloud Run で WebSocket などのストリーミング サービスを実行し、これらのサービス用のクライアントを作成するためのガイダンスとベスト プラクティスについて説明します。

WebSocket は、追加の構成を必要とせずに Cloud Run でサポートされています。ただし、WebSocket ストリームは HTTP リクエストであり、Cloud Run サービスに対して構成されたリクエスト タイムアウトが引き続き適用されるため、クライアントへの再接続の実装などで WebSocket を使用した際にこの設定が適切に機能することを確認する必要があります。

Cloud Run でセッション アフィニティを使用している場合でも、これはベスト エフォート型のアフィニティであるため、組み込みのロード バランシングによって WebSocket リクエストが別のインスタンスに送信される可能性があります。この問題を解決するには、インスタンス間でデータを同期する必要があります。

Cloud Load Balancing を使用している場合は、Cloud Run の WebSocket もサポートされます。

サンプル WebSocket サービスのデプロイ

Cloud Shell を使用すると、Cloud Run で WebSocket を使用するサンプル ホワイトボード サービスを簡単にデプロイできます(サンプルをデプロイするを参照)。

このサンプル ホワイトボード サービスを手動でデプロイする場合:

  1. git コマンドライン ツールを使用して、Socket.IO リポジトリのクローンをローカルに作成します。

    git clone https://github.com/socketio/socket.io.git
    
  2. サンプルのディレクトリに移動します。

    cd socket.io/examples/whiteboard/
    
  3. Google Cloud CLI を使用してソースコードからサービスをビルドし、新しい Cloud Run サービスをデプロイします。

    gcloud run deploy whiteboard --allow-unauthenticated --source=.
    
  4. サービスがデプロイされた後、2 つのブラウザタブを開き、サービスの URL に移動します。クライアントが WebSocket 経由で同じインスタンスに接続されているため、1 つのタブに表示した内容が他のタブに伝播されます。

WebSocket のチャット サンプルの完全なチュートリアル

コードの完全なチュートリアルについては、Cloud Run 用の WebSocket チャット サービスの構築トピックにある追加のコードサンプルをご覧ください。

ベスト プラクティス

Cloud Run で WebSocket サービスを作成する際に最も難しい点は、複数の Cloud Run インスタンス間でデータを同期することです。インスタンスの自動スケーリングとステートレスな特性、同時実行リクエスト タイムアウトの制限のため、実装が難しくなっています。

リクエスト タイムアウトとクライアントの再接続を処理する

WebSocket リクエストは、Cloud Run で長時間実行される HTTP リクエストとして扱われます。アプリケーション サーバーがタイムアウトを適用しない場合でも、リクエスト タイムアウトの影響を受けます(最大 60 分、デフォルトは 5 分)。

クライアントとの接続時間が Cloud Run サービスに構成されたタイムアウトより長くなると、リクエストがタイムアウトしたときにクライアントとの接続が切断されます。

Cloud Run に接続する WebSocket クライアントは、リクエストのタイムアウト時またはサーバーの切断時にサーバーへの再接続を処理する必要があります。この処理をブラウザベースのクライアントで行うには、reconnecting-websocket などのライブラリを使用します。SocketIO ライブラリを使用している場合は、接続解除イベントを処理します。

WebSocket の使用で発生する料金

WebSocket との接続がオープン状態の Cloud Run インスタンスはアクティブと見なされるため、CPU が割り当てられて課金されます。

最大同時実行数の引き上げ

WebSocket サービスは通常、多くの接続を同時に処理するように設計されています。Cloud Run は同時接続数をサポートしています(コンテナあたり 1,000 まで)。サービスが特定のリソースの負荷を処理できる場合は、コンテナの最大同時実行数をデフォルト値よりも大きくすることをおすすめします。

スティッキー セッション(セッション アフィニティ)について

WebSocket 接続はステートフルであるため、クライアントは接続が終了するまで Cloud Run 上の同じコンテナに接続された状態を保持します。これにより、単一の WebSocket 接続のコンテキスト内でセッションを持続できます。

複数の後続の WebSocket 接続では、セッション アフィニティを使用するように Cloud Run サービスを構成できますが、これはベスト エフォート型のアフィニティであるため、最終的には、WebSocket リクエストが複数のインスタンスで発生する可能性があります。Cloud Run サービスに接続するクライアントは、最終的に、データを調整または共有しない別のインスタンスによって処理される可能性があります。

これを軽減するには、次のセクションで説明するように、外部データ ストレージを使用して Cloud Run インスタンス間で状態を同期する必要があります。

インスタンス間でのデータの同期

Cloud Run サービスに接続するクライアントが WebSocket 接続から同じデータを受け取るには、データを同期する必要があります。

たとえば、WebSocket を使用してチャットルーム サービスを構築し、最大同時実行の設定を 1000 に設定した場合、1000 を超えるユーザーがこのサービスに同時に接続すると、ユーザーが別のインスタンスによって処理され、チャットルーム内で同じメッセージを表示できないことがあります。

Cloud Run インスタンス間でデータを同期するには(たとえば、すべてのインスタンスでチャットルームに送信されたメッセージを受信するなど)、データベースやメッセージ キューなどの外部データ ストレージ システムが必要です。

Cloud SQL などの外部データベースを使用する場合は、データベースにメッセージを送信し、データベースから定期的にポーリングを実施できます。ただし、コンテナがリクエストを処理していない場合、Cloud Run インスタンスには CPU が割り当てられていないことに注意してください。サービスが主に WebSocket リクエストを処理している場合は、少なくとも 1 つのクライアントが接続されていることを条件としてコンテナに CPU が割り当てられます。

外部メッセージ キューは各インスタンスのアドレスを解決してデータを push できないため、Cloud Run コンテナ間のデータ同期をリアルタイムで行うとメッセージ キューの処理が向上します。サービスは、メッセージ キューとの接続を確立して、メッセージ キューから新しいメッセージを pull する必要があります。

Google では、Redis Pub/SubMemorystore)や Firestore リアルタイム更新などの外部メッセージ キュー システムを使用することをおすすめします。これらのキューシステムにより、コンテナ インスタンスによって開始された接続を介して、すべてのインスタンスに更新情報を配信できます。

Redis Pub/Sub の使用

WebSocket チャットルーム サービスのアーキテクチャ

Redis の Pub/Sub メカニズムは、Memorystore から Redis インスタンスを作成することで使用できます。WebSocket 用の Socket.IO ライブラリを使用している場合は、ライブラリの redis アダプタを使用できます。

この Redis ベースのアーキテクチャでは、各 Cloud Run インスタンスが、受信したメッセージを含む Redis チャネルとの長時間接続を確立します(SUBSCRIBE コマンドを使用します)。コンテナ インスタンスがチャネルで新しいメッセージを受信すると、WebSocket を経由してリアルタイムでクライアントに送信できます。

同様に、クライアントが WebSocket を使用してメッセージを出力すると、メッセージを受信したインスタンスが、PUBLISH コマンドを使用して Redis チャネルにメッセージをパブリッシュし、このチャネルに登録している他のインスタンスがメッセージを受信します。

コードの完全なチュートリアルについては、Cloud Run 用の WebSocket チャット サービスの構築トピックにある追加のコードサンプルをご覧ください。