OOParts -Stateless Cloud Gaming Architecture- #CNDT2020
それではこれより、「OOParts - stateless cloud gaming architecture-」というタイトルで発表させていただきます。よろしくお願いします。
こんにちは。株式会社ブラックから来ました、中村勇介と申します。普段は「うなすけ」という名前で色々やっているので、そちらのほうがわかりやすいという方もいらっしゃると思います。
本日は、ブラックの運営するクラウドゲーミングサービス、「OOParts」のインフラについて、皆さんにご紹介できたらなと思います。
初めに軽く自己紹介をしておきますと、僕はフリーランスとして複数の会社をお手伝いしておりまして、ブラックはそのうちの1社になります。これから紹介しますが、主にバックエンドのAPIやクラウドインフラ周りの開発・運用を担当しています。
今日の発表は、先日開催されたCEDEC 2020にて発表した、「クラウドゲーミング最新開発事例」という発表を、もっと技術的に、もっとクラウドインフラに寄せた内容となっております。フロントエンドや、ゲーミングサーバー内部の話についても気になる方は公開されている資料を見ていただけると嬉しいです。
本日の発表の流れは、このようになっています。まずクラウドゲーミングとOOPartsについての説明。次にOOPartsの全体構成。さらに、そのインフラについてクローズアップし、フルスクラッチで実装したAWS EC2 Auto ScalingのようなVM管理システムの解説と、これがCloud Nativeと言えるのかどうか。そして、リリース後に起こった出来事について紹介し、最後にまとめと展望についてお話しします。
それではまず、OOPartsも含めた、クラウドゲーミングというものについて軽く説明します。
クラウドゲーミングとは、PCや、専用のハードウェアとゲームを購入し、インストールを行い、手元の所有している端末でゲームの処理をするという従来のゲーム体験とは異なり、ゲーム自体の処理はクラウド上のサーバーで行ない、ユーザーの端末ではサーバーからの映像を表示し、操作をサーバーに送信する、という処理のみを行う次世代のゲーム体験です。
クラウドゲーミングの大きな特徴として3つ上げられます。1つめが、WindowsでもmacOSでもLinuxでもiOSでも、Webブラウザさえあれば動作するという「クロスプラットフォーム」、2つめが、パブリッシャーやストアの審査が不要で制限がないという「フリーダム」、3つめが、ローカルへのインストールが不要で、ストレージを消費しないという「ネットワークさえあればプレイ可能」というものです。
現在サービス提供中のものとして、NVIDIAの「GeForce NOW」、SONYの「PlayStation Now」、Googleの「Stadia」などが挙げられます。
我々の運営しているOOPartsは、主にアドベンチャーゲームをクラウド上でプレイできるサービスとなっています。
OOPartsは、2019年4月に、社長の小川がTwitterにデモ動画を公開したことが始まりとなって、開発が開始されました。その後、8月に業界関係者向けのクローズドアルファを、11月に事前登録制のクローズドベータを、そして今年の4月に正式リリースが行われました。私がOOPartsの開発をお手伝いすることになったのは、昨年夏ごろからになっています。
ここからは、そんな時代の最先端をゆくクラウドゲーミングを、国内スタートアップによって実現させたアーキテクチャについて紹介します。
さて、インフラ側のアーキテクチャを説明するまえに、まずは全体として、どのような構成によってOOPartsが動作しているのかを説明します。大まかな登場人物は3人、プレイヤーのインターフェースとなるフロントエンド、フロントエンドからのプレイ要求を受け取るバックエンドAPI、そしてゲーム本体が実行されているゲーミングサーバーです。
ゲームプレイまでの流れとしては、まずプレイヤーからゲームをプレイするという要求がバックエンドAPIに送られます。
バックエンドAPIはゲーミングサーバーを確保し、
配信に使用する情報をブラウザ、ゲーミングサーバーのそれぞれに送信します。
その情報に従って、ブラウザとゲーミングサーバーは互いに通信が可能になり、ユーザーはゲームプレイができるようになります。
ユーザーがゲームを終了すると、ゲーミングサーバーからの要求に従って、バックエンドAPIがゲーミングサーバーを解放します。
インフラサイドの守備範囲は、主にこのバックエンドAPIの開発と、ゲーミングサーバーそのものの管理となっています。
さて、今日このイベントの名前はCloudNative Days Tokyoですよね。これからOOPartsのインフラ構成について触れていくのですが、その前に一度、"Cloud Native" の定義に立ち返ってみましょう。 https://github.com/cncf/toc によれば、"Cloud Native" とはこのように定義されます。
クラウドネイティブ技術は、パブリッククラウド、プライベートクラウド、ハイブリッドクラウドなどの近代的でダイナミックな環境において、スケーラブルなアプリケーションを構築および実行するための能力を組織にもたらします。 このアプローチの代表例に、コンテナ、サービスメッシュ、マイクロサービス、イミュータブルインフラストラクチャ、および宣言型APIがあります。
これらの手法により、回復性、管理力、および可観測性のある疎結合システムが実現します。 これらを堅牢な自動化と組み合わせることで、エンジニアはインパクトのある変更を最小限の労力で頻繁かつ予測どおりに行うことができます。
……とあります。
また、「Cloud Nativeである」と呼べるために重要な要素として、「コンテナ化」「マイクロサービス」「動的なオーケストレーション」が主に挙げられるでしょう。
さて、ではOOPartsのインフラ構成がCloud Nativeといえるかどうか、詳しくみていきましょう。
先ほど、雑に「バックエンドAPI」と呼んでいた部分について、さらにクローズアップしていきます。
これが、バックエンドAPIも含めた、インフラ側の簡単な構成図になっています。
ユーザーが「プレイ開始」をクリックすると、まず、インターネットに露出しているAPI、以降はPublic APIと呼びますが、そのPublic APIに対してプレイ要求のリクエストを投げます。
Public API側はそのリクエストを受け、WebRTC接続用の情報を生成します。そして、それらの情報を SQS に入れます。裏でSQSを監視しているコンポーネントが、キューからプレイ要求を取り出し、空いているゲーミングサーバーとの紐付けを行い、DynanoDBに格納します。
ユーザーとゲーミングサーバーの紐付け情報がDynamoDBに格納されると、内部用のAPI、これをInternal APIと呼びますが、Internal API経由で、ゲーミングサーバーが動かすゲームの情報、WebRTC接続用の情報を取得し、サーバーとWebブラウザが繋がってゲームがプレイできるようになります。
これ以降はゲームが終了するまでAPIが介入することはありません。
ゲームが終了すると、ユーザーのセーブしたデータをS3に永続化し、ゲーミングサーバーの情報をDynamoDBから削除します。
ゲームの開始から終了までは、おおよそこのような流れになっています。
ここで、さらっと「ゲーミングサーバー」となんども出てきているものですが、これは一体なんなのでしょうか。
これの実体はWindows Serverにゲーム操作、映像配信などを行うエージェントをインストールしたものとなっています。このゲーミングサーバーですが、技術的制約により、ユーザーのゲームプレイごとに1台を起動、終了させるようになっています。
まずサーバーが起動すると、内部で実行されているエージェントが、起動してきたタイミングでInternal APIにリクエストし、DynamoDBに自分の情報を登録させます。
ゲーミングサーバーは初期化処理に応じて自身の情報を更新していきます。ユーザーとインスタンスとのマッチングの時には、ゲームプレイが可能な状態のサーバーにのみ、先程のプレイ要求を割り当てるようになっています。
プレイが終了すると、ゲーミングサーバーは自身をTerminateする要求をInternal APIに投げます。Internal APIはインスタンスを終了させ、DynamoDBからそのサーバーの情報を削除します。
これがゲーミングサーバーのライフサイクルとなっています。
インスタンスの起動には、初期化処理も含めると5分程度の時間がかかるため、プレイの要求が来てからサーバーを用意していたのでは、ユーザーさんは5分も待たされることになってしまいます。では一体、何台のサーバーを用意しておけばいいのでしょう。
AWSのEC2 Auto Scalingでは、CPUの使用率などのメトリックに応じて台数調整を行うことができます。普通のWebアプリケーションでは、リクエストが増えてCPU使用率が上がってきたら台数を増やしてスケールアウトするという戦略がとれるでしょう。
OOPartsでは、ユーザーに個別のゲーミングサーバーを割り当てるというサービス特性上の都合により、スケーリングの仕組みを自分達で構築することにしました。
これは、スケーリングのロジックをAWS Lambda上で定期的に動作せさており、具体的にはデータベースから算出できる、プレイ中の人数に対して何台のサーバーを余分に起動させておくか、という制御をするようにしています。
プレイが終わるとサーバーはTerminateするため、常に一定数のフレッシュなゲーミングサーバーを確保するようになっています。
ここまでで、OOPartsのバックエンドについて、おおまかにどのような構成になっているのかを説明できたかと思います。
ご覧のとおり、サービスのメインとなる構成にはCloud Native Computing Foundation のProjectは一切使用されていません。ということは、我々のサービスアーキテクチャはCloud Nativeではない?ということになるのでしょうか?
まずここまでで、AWS Lambdaを使用したサーバーレスアーキテクチャであり、かつマイクロサービスな構成になっていることがわかります。次にゲーミングサーバーですが、プレイごとに使い捨てることで、ちょっと強引ですが、コンテナ化できているといってよいでしょう。そして、ゲーミングサーバーをどのくらい準備するかについては、Lambda上に構築したコンポーネントによって制御されています。「コンテナ化」「マイクロサービス」「動的なオーケストレーション」の3つの要素を満たしていますね。
またゲームプレイに関する、セーブデータやセッションなどの状態は、FirestoreやS3やDynamoDBなどに閉じ込めておくことで、Lambdaやフロント、ゲーミングサーバーが状態を持つ必要はなく、クラウドサービスにおけるインスタンス同時起動数の上限まで同時にプレイすることが可能です。
よって、OOPartsはステートレスであり、Cloud Nativeな構成による、クラウドゲーミングサービスを実現できている、といっても構わないのではないかな、と僕は考えています。
そして、これまでの図にはもう古くなっている部分があります。現在、OOPartsのゲーミングサーバーはそのほぼ全てをGCP上のインスタンスでまかなっています。実は、僕が参加する以前の開発初期からマルチクラウドを前提にしていた設計になっていました。これは同時に起動できるインスタンス数の上限がクラウドインフラごとに設定されているためや、将来的にコスト効率の高いオンプレミス、もしくは別のIaaSに移行、併存することも見据えたうえでの戦略です。
さて、少し横道に逸れますが、4月末の正式リリース周辺で起こった出来事について紹介させてください。
正式リリース前にやることといえば、なんでしょうか。そう、負荷試験ですよね。正式リリースの際にはアクセスが急増することが予想できていました。そこで、あらかじめ大量のゲーミングサーバーを起動しておき、Puppeteerによって、擬似的に大量のアクセスを再現させました。このとき、同時に起動していたゲーミングサーバーの数はおよそ1000台でした。ここで、大量に同時プレイの要求が来ても捌ききれそうだという検証ができたわけです。
そして正式リリースのときには、クラウドゲーミングという技術に触れてほしい、体験してほしいという想いから、「リリース後の3日間は無料でプレイができる」というキャンペーンを行いました。
その結果として、一番多い瞬間で1600台以上という、想定していた以上に大量のサーバーを同時に起動することになりました。しかし、ある時点から急にサーバーを新規に起動することができなくなってしまいました。このときの社長によるツイートがこちらです。
このときに一体何が起こっていたのか。実は、当時のEC2インスタンスの起動台数上限は、c5.large換算で2万台であり、これに到達した、という訳ではありませんでした。AWSにEC2インスタンスを起動すると、Elastic Network Interfaceが1つ、ついてきます。このENIですが、Terraformの設定を忘れており、インスタンスが終了したあとも削除せず残ったままになっておりました。結果として、約1万2千あるVPC内のIPアドレスを使い切ってしまい、EC2インスタンスを新規に起動することができなくなっていました。このとき、AWSのconsoleはTimeoutし、API経由での情報取得にも数分かかってしまうという珍しい、しかし経験したくはない状況になっていました。
原因が判明してからは、EC2インスタンス終了時にENIを自動で削除するように設定を変更し、残ったままになっているENIを削除することで、またサーバーを起動できるようになりました。
このようなトラブルはあったものの、無料キャンペーン期間には累計で5万6千台ものEC2インスタンスを起動しており、多くの方々にクラウドゲーミングを体験してもらうことができました。と同時に、それほどまでにスケールするアーキテクチャである、ということもわかっていただけると思います。
それでは最後に、この発表のまとめをさせていただきます。
クラウドゲーミングというサービス手法は、SONYさんやGoogleさんのような巨大資本ではない、我々のようなスタートアップでも実現可能な技術となってきています。
また、そのアーキテクチャは、パブリッククラウドを活用することによって大量のアクセスにも耐えうる、柔軟なものを構築することができています。
そして、設計初期からの考慮により、マルチクラウドはもちろん、プライベートクラウドも含めたハイブリッドな構成への移行も素早く行えるようになっています。
そして今後の展望ですが、まずは高集積化です。ゲーミングサーバーについて、「技術的制約により、ユーザーのゲームプレイごとに1台を起動、終了させるようになっています」とお話ししました。この「制約」についてですが、1つが、Windows ContainerではゲームのようなGUIを持つアプリケーションを起動させることが現状できないこと、2つめが、ゲーム環境のsandbox化ができず、インスタンス上にゲームの情報が残り続けてしまうことでした。これが、例えばWindows ContainerのGUIサポートや、Linux ContainerとWineの上でゲームを動作させるようにするなどの手法で高集積化できないかということを検討しています。もしそういうことが可能になるならば、それこそKubernetesとAgonesを組合せたアーキテクチャに移行する、なんてこともできるかもしれません。
次に、情報の一元化です。特に各種メトリックは、AWSのCloudWatchとGCPのCloud Monitoringに分散してしまっています。これをDatadogやMackerelなどの監視SaaSに集約するか、Prometheusを自分達で運用するかは決めていませんが、統合されたDashboardを用意したいと思っています。
OOPartsは、最先端の技術を採用し、時代を超えるべき名作をいつでも、どこでも遊ぶことのできるサービスを目指して開発を続けている真っ最中です。
以上で、「OOParts -Stateless Cloud Gaming Architecture-」の発表を終わります。ご静聴ありがとうございました!