grasys blog
grasysブログ

Cloud Run やってみた:WebSocketを使用したサービスのデプロイ

こんにちは、スプラ3の前夜祭でトリカラバトルにて中間管理職的体験をし、ツイッターの同じ陣営に強く共感している高田です。
下克上の2色は担当アイドルからの声援?応援?があったようで…終わってから知りました…マンタローのエイエイオーな応援が欲しかったな…

さて、今回のタイトルについて書くに至った経緯ですが
ゲーム案件でしばしばWebSocketを用いたサーバのデプロイとか構築とかやることが多く
あまり触れていないCloud RunがWebSocketに対応していることに興味を持ち、チュートリアルベースでやってみることにしました。

このブログで分かること

  • Cloud Run とは
  • WebSocket とは
  • チュートリアルに則ってデプロイ
  • デプロイ後の実際の動作
  • Cloud RunでWebSocketを取り扱う際の注意点
  • まとめ

ざっと箇条書きにしますとこんなところでしょうか。
では早速いってみましょう。

Cloud Run とは

Cloud Run とは?という部分に関しては「Cloud Runを軽く触った気がした」でも記載があるので、重複した内容となってしまいますが
「サーバレス(プロビジョニングの設定、スケーリング管理)が不要で、コンテナを実行することができるサービス」です。

詳しい概要については公式のドキュメントをどうぞ。

WebSocket とは

検索でよく見かけるのは「非同期通信」「双方向通信技術」とあります。
ひとつのTCPコネクションの上で、双方向通信(つまり通信元と通信相手の間で行き来の向き)に対応したチャンネルを提供する、プロトコルの一種です。

非同期通信についてもう少し噛み砕いてみる

「非同期通信」って言葉、ぶっちゃけ難しいイメージ持っていませんか(少なくとも私は難しく捉えてて中々イメージしづらかったです)。
そこで、非同期通信の具体的なイメージしやすいように噛み砕いていこうと思います。

「非同期通信」を「非同期」「通信」に分けます。

「非同期」について

同期しない、っていうのは字面からは連想できそうです。
では「同期」ってどんな意味かと思い調べてみると、以下のことを指すようです。

(通信の意味での)同期:2つ以上の信号や処理のタイミングが合うこと

つまり非同期は「複数の処理のタイミングの合わないもの」とイメージできます。

「通信」について

通信の基本的な流れは以下の通りです。

  • 通信は、自分「A」と相手「B」がいて成立します
  • AからBに要求(リクエスト)を送ります
  • BはAからのリクエストを受け取り、その応答(レスポンス)をAに返します

二つの意味を合わせて考えると

このまま処理を続けると想定した時、以下のケースが考えられます。

i) A側は、Bからの応答(処理)を「待ってから」続けてBへ要求を送ります
ii) A側は、Bからの応答(処理)を「待たない」続けてBへ要求を送ります
※AとBが逆の場合も考えられますが、ここではひとまずA側から処理を続けると想定します

i) では処理の足並み(=タイミング)が揃うので「同期通信する」ことになります。
一方で
ii) は片側から一方的に(タイミングを合わせず)に要求を投げ続ける「非同期通信する」ことになります。

「待たない」→相手の処理にタイミングを合わせない→非同期通信

って連想すると、イメージしやすいのかなと思いました。

ちょっと長くなりましたが閑話休題。
WebSocketとCloud Run を使ったデプロイを試していきます。

チュートリアルに則ってデプロイ

デプロイ自体はほぼ、公式ドキュメント通りです。
以下のコマンド3つを実行しました。

まずはコードサンプルの取得

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

続いてビルド実行ディレクトリへの移動

cd socket.io/examples/whiteboard/

そして、Cloud Runデプロイ

gcloud run deploy whiteboard --allow-unauthenticated --source=.

今回試した時にデプロイコマンドを実行した時にリージョン選択を確認されたので、今回は東京でデプロイするとして3を入力し実行すればOKです。

Please specify a region:
 [1] asia-east1
 [2] asia-east2
 [3] asia-northeast1
 [4] asia-northeast2
 [5] asia-northeast3
 [6] asia-south1
 [7] asia-south2
 ...(中略)... 
 Please enter numeric choice or text value (must exactly match list item):  3

上記入力後のビルド中のログはこんな感じ

To make this the default region, run `gcloud config set run/region asia-northeast1`.

This command is equivalent to running `gcloud builds submit --pack image=[IMAGE] .` and `gcloud run deploy takada-whiteboard --image [IMAGE]`

Building using Buildpacks and deploying container to Cloud Run service [takada-whiteboard] in project [XXXXXXXXXX] region [asia-northeast1]
X  Building and deploying new service... Done.
  OK Uploading sources...
  OK Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds/c4c96af3-08b3-4c65-8fa3-32232332c6b3?project=ZZZZZZZZZZZZZZZZ].
  OK Creating Revision...
  OK Routing traffic...
     Setting IAM Policy...
Service [takada-whiteboard] revision [takada-whiteboard-00001-jis] has been deployed and is serving 100 percent of traffic.
Service URL: https://takada-whiteboard-abcdefgh-an.a.run.app

およそ5分前後でデプロイされました。
※上記のService URLからチャットアプリへアクセスできませんので予めご了承ください

デプロイ後の実際の動作

ではデプロイで生成されたURLにアクセスしてみます

2画面出しているのは、右画面=自分と左画面=通信相手を想定しており、右画面で描画を行っています。
右画面の描画が、左画面にも即座に描画されていることを確認でます。

上記の2画面の様子から
「え!2画面でほぼ同時に描画されてるじゃん!それって非同期通信じゃなくて、同期通信じゃないの?」
と思う方もいるかもしれません。

前述した通信の基本に置き換えて右画面を「A」左画面を「B」とします。
描画を行っているのは「A」側で、「B」側は何も応答はしていません(この場合は通常、B側で「Aが書いた描画を表示する処理」を考える必要があるはずです)。
Aの要求(描画)が、Bからの応答を待たずに通信しています。
Bから描画した場合も、同様です。

AとBのどちらでも、好きなタイミングに、リアルタイムで描画できる点がWebSocketの特徴となっています。

Cloud RunでWebSocketを取り扱う際の注意点

スケールを考慮する設計が難しそう

Cloud Runは スケールが得意な性質がありますが、双方向の通信の窓口を担うWebSocketの性質を鑑みると、スケール考慮は難しそうです。
ベストプラクティスでは冒頭からこのように記載されています。(2022年8月27日現在)

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

タイムアウトの最大は60分、デフォルトは5分

Google Document上では以下のように記載されています。(2022年8月27日現在)

アプリケーション サーバーがタイムアウトを適用しない場合でも、リクエスト タイムアウトの影響を受けます(最大 60 分、デフォルトは 5 分)。
引用元:https://cloud.google.com/run/docs/triggering/websockets?hl=ja#client-reconnects

また Cloud Runの前段にLoad Balancerを置く場合、LoadBalancer側のタイムアウト設定などもあるので注意が必要かなと思います。

Socket通信しっ放しになると課金額がヤバそう…

WebSocket 接続されていると課金対象となるようです。
https://cloud.google.com/run/docs/triggering/websockets?hl=ja#billing_incurred_when_using_websockets

WebSocket との接続がオープン状態の Cloud Run インスタンスはアクティブとみなされ、課金対象となります。

コスト面を優先するととタイムアウトの時間はあまり長く無い方がいいのかもしれません。
アプリの仕様上、長時間接続し続けたいケースがある場合はコストは壁になりそうです。

同時接続数のMAXは1コンテナ1000

こちらに
https://cloud.google.com/run/docs/triggering/websockets?hl=ja#maximize-concurrency

コンテナあたり 1,000 まで

とありますが、この上限は制限緩和の出来ないのでそちらも注意が必要です。
https://cloud.google.com/run/quotas?hl=ja#cloud_run_limits

因みに1コンテナ1000を超えた1001番目の接続はCloud Run によってスケールが動作して2コンテナ目がデプロイされそちらへ接続しに行ってしまうことがあります。
その結果、データの同期で問題になる可能性があるとのことが記載されています。
https://cloud.google.com/run/docs/triggering/websockets?hl=ja#multiple-instances

まとめ


・Cloud Run へのデプロイ自体はお手軽に出来る
・WebSocketとはあまり相性が良いとは思えず
・現在はビルド対応言語にPHPが無いので、PHP言語にも対応して欲しい!

Cloud RunのWebSocketを用いたチュートリアルは上記のホワイトボードのほか、チャットアプリなども用意されていました。

これを機会に是非やってみてはいかがでしょうか。

今回は以上です。ほな解散!!!