はじめに
Sreake 事業部でインターンをしている小川です。主にパブリッククラウド周辺に触れながら、 Kubernetes 関連の OSS の技術検証・調査をしています。
本調査では、Agones と Open Match を使用したリアルタイム対戦ゲームのデモを提供する space-agon という OSS を用いて、Agones と Open Match の動作を追ってみました。
本記事では調査のうち、space-agon を通してリアルタイム対戦ゲームが提供しているゲームインフラの入門的な内容を紹介しており、Kubernetesの基礎的な知識を持つ人向けの記事となっています。また、Agones や Open Match についてのオプションや詳細処理については記載しておりません。
Agonesとは
Agones は Kubernetes 上でマルチプレイヤーを実現するゲームサーバを提供・管理するためのOSSです。
Agones をインストールすることで、ゲームサーバを管理するためのコントローラとカスタムリソース定義が追加されます。Agones コントローラは、ゲームサーバの起動や更新、必要な台数の確保、正常性の確認などを行います。また、Agones SDK サーバをゲームサーバのサイドカーとして起動することで、ヘルスチェックや安全な停止などのライフサイクルをユーザーが管理できます。これらの仕組みにより、プレイヤー数の変動に応じてゲームサーバを効率的に管理し、コストを削減しながら動的にスケーリングすることができます。
また、以下の言語の Client SDK がサポートされています。
- Unreal Engine
- Unity
- C++
- C#
- Node.js
- Go
- Rust
- REST
space-agon では Go で Agones SDK を利用しています。
Agones のリソース
Agonesをインストールした際に作成されるものとしては下記のようなものがあります。
- custom resources
- gamesever(ゲームサーバ): プレイヤー同士が接続し、実際にゲームが行われるサーバ。
- fleet: ゲームサーバの元となるコンテナの image やレプリカ数を定義するリソース。
- fleet autoscaler: fleet を自動的にスケーリングするリソース。
- deployment
- agones-allocator: ゲームサーバの割り当てを行う。
- agones-controller: ゲームサーバのライフサイクルを管理し、状態の更新を行う。
- agones-extensions: agonesの機能拡張を管理する(space-agon では使用していない)。
- agones-ping: クライアントからゲームサーバまでのレイテンシを測定する。
OpenMatchとは
Open Match は、マルチプレイヤーゲームのゲームマッチ部分の機能を担うマッチメイキングフレームワークです。ゲーム開発者がプレイヤーを効率的にマッチングさせるための基盤を提供し、特定のゲームやアプリケーションのニーズに合わせてマッチメイキングロジックをカスタマイズできるように設計されています。
マッチメイキングロジックのカスタマイズは、 space-agon の mmf(後述) のようなマッチ関数を独自実装することで実現できます。プレイヤーのスキルレベル、地理的位置、ゲームプレイスタイルなど、任意の基準を使用してマッチメイキングロジックを定義できます。
Open Match のリソース
Open Matchをインストールした際に作成されるものとしては下記のようなものがあります。
- deployment
- open-match-backend:マッチメイキングのリクエストを処理し、プレイヤーをゲームセッションに割り当てをする。
- open-match-frontend:ゲームクライアントからのマッチメイキングリクエストを受け取り、Ticket(プレイヤー)の作成や削除、Assignment を提供する。
- open-match-evaluator: 異なるマッチで同じ Ticket(プレイヤー) を含む可能性があるため、重複しているマッチを評価して重複を解消する。
- open-match-query: Ticketの情報等を取得するためのクエリを発行する。
- open-match-synchronizer: 同時に行われるマッチメイキングリクエストを同期させる。evaluator が重複するマッチを評価している間に、既に選択されたチケットがマッチング対象となってしまう可能性があるため、同期的に評価できるようにする。
- statefulset
- open-match-redis:チケット情報の一時保存等に使用される。
また、Open Match は Kubernetes 上のリソースの他に、固有のデータ構造があります。今回は記事に関係するデータ・フィールドのみ紹介します。
詳細のデータ構造については公式ドキュメントをご参照ください。
- Ticket: OpenMatch 内でマッチングを行うプレイヤーを表す。
フィールド名 | 型 | 説明 |
---|---|---|
id | string | OpenMatch が作成したチケットID |
assignment | Assignment | Ticket に紐ついているゲームサーバ |
- Pool(Ticket Pool): 特定のマッチングを指定するためのフィルタ条件のセット。
フィールド名 | 型 | 説明 |
---|---|---|
name | string | Pool の名前 |
double_range_filters | DoubleRangeFilter | フィルタリングに使われるフィルターのセット(数値) |
string_equals_filters | StringEqualsFilter | フィルタリングに使われるフィルターのセット(文字列) |
tag_present_filters | TagPresentFilter | フィルタリングに使われるフィルターのセット(タグ) |
- Match: Ticket(プレイヤー) 同士のマッチングを含むデータ。
フィールド名 | 型 | 説明 |
---|---|---|
match_id | string | マッチID |
match_profile | string | このマッチを作成に用いた MatchProfile(後述) |
match_function | string | このマッチを作成した マッチメイキング関数(MatchFunction) |
tickets | Ticket | このマッチに紐つく Ticket |
- MatchProfile: Poolなど、MatchFunctionでマッチを作成するために必要な情報が含まれるデータ。
フィールド名 | 型 | 説明 |
---|---|---|
name | string | MatchProfile の名前 |
pool | Pool | この MatchProfile の Match を生成するときに使われる Pool(フィルタ) のセット |
- Assignment: Ticket(プレイヤー)に紐つくゲームサーバの割り当て情報(IPアドレス、ポート番号等)。
フィールド名 | 型 | 説明 |
---|---|---|
connection | string | 割り当てられたゲームサーバの接続情報 |
space-agonとは
space-agon はAgonesとOpenMatchを統合デモを行うOSSになります。GoogleForGamersが提供しているOSSです。提供しているゲームとしては、シューティング形式のリアルタイム対戦ゲームとなっています。
Makefile を使って space-agon をインストールすると、クラスタ上には下図のようにリソースが作成されます。
![](https://sreake.com/wp-content/uploads/2024/06/space-agon.jpg)
space-agon で独自実装しているリソース
Agones と Open Match はマッチングやゲームサーバを用意するためのOSSとなっています。そのため、ゲームを提供する場合には、ゲームそのもの以外にもどのようにマッチングするか?であったり、Agones,OpenMatchとの連携部分については独自実装が必要になります。 対戦ゲームを提供する際に最低限実装する必要のあるリソースについては、下記のものがあります。
- deployment
- director: マッチメイキングのロジックを管理し、適切なマッチを作成する。
- frontend: ゲームクライアントと OpenMatch を繋ぐ役割を持つ。OpenMatch の frontend とやりとりして Ticket の作成要求や Assignment をプレイヤーに送信する。
- mmf: マッチング機能のコア部分となるマッチメイキング関数(MatchFunction)を持つリソース。
- CRD
- fleet(dedicated): fleet で扱うゲームサーバ自体の実装。space-agon ではマッチ完了後、dedicated という pod 内でゲームが行われる。
なお、space-agon では実装していませんが、Evaluator についても独自のマッチング評価方法を実装することができます。(Custom Evaluator について)
インストール方法
基本的には公式に沿ってインストールすることで space-agon をデプロイできます。
space-agon は GKE か minikube 上のクラスタ上にインストール可能で、リポジトリ上の Makefile を使うと Agones と Open Match のインストールまでやってくれます。
今回はあらかじめ用意した GKE クラスタ上にインストールをしました。なお、限定公開クラスタで作成すると Node に Public IP が付与されずゲームクライアントから接続できなくなるため、限定公開クラスタでは作成しておりません。
その際の注意点として、ゲームクライアントが Node の7000 ~ 8000 番ポートに接続するために UDP トラフィックを許可するファイアウォールを作成する必要があります(Agones ドキュメント参照)。
gcloud compute firewall-rules create game-server-firewall \
--allow udp:7000-8000 \
--target-tags game-server \
--description "Firewall to allow game server udp traffic"
space-agonに触れる
インストールが完了したら、実際に space-agon をプレイしてみましょう。
space-agon はブラウザで遊べるゲームなので、アクセス先を取得する必要があります。
アクセス先のIPアドレスはインストール時に作成された frontend という service から取得できます。
$ kubectl get service frontend
アクセスすると画像のようなトップページが表示されます。
![](https://sreake.com/wp-content/uploads/2024/06/top-page-1024x677.png)
ゲームマッチング形式としては
- Find Game:マッチングを待っているプレイヤー同士でランダムにマッチング
- Connect To Server:指定した部屋(ゲームサーバ)に入って対戦
の2通りがあります。
Connect To ServerはゲームサーバのIPアドレス・ポート番号を直接入力してゲーム開始となりますが、今回は「Find Game」の方で進めます。
Find Game を選択し、マッチングが完了するとリアルタイムのシューティングゲームが始まる形になっています。(自機が青色の三角で、敵機がもう一方の青色の物体となっているようです)
![](https://sreake.com/wp-content/uploads/2024/06/game-page-1024x675.png)
両プレイヤーがタブを閉じるか別ページに移動すると、コネクションが切断されてマッチ終了になります。
このようにアプリケーションの動作としてはとても単純ですが、裏では space-agon, Agones, Open Match 間でさまざまな処理が行われています。今回はマッチングまでの大まかな流れについて、space-agon を中心に紐解きながら紹介いたします。
マッチメイキングのプロセス
Find Gameが押されてからマッチングが完了するまで、どういった流れで処理が行われているのかを見ていきます。
ゲームクライアントからマッチメイクのリクエスト
- 「Find Game」を選択する。
- space-agon の frontend からopen-match-frontend に Ticket を作成するようリクエストを送信する。
- open-match-frontend がチケットを作成し,open-match-redis に保存してマッチメイクの対象とする。
- マッチメイクできたら Assignment を返却する。
frontend は open-match-frontend と gRPC ストリームを張って通信しており、作成した Ticket の Assignment の更新を待ちます。Assignment が更新されるとマッチング完了となり、ゲームが開始されます。
![](https://sreake.com/wp-content/uploads/2024/06/space-agon-1024x706.png)
Assignmentの要求から返却まで
プレイヤーはマッチ時に接続するゲームサーバの情報が必要になります。OpenMatch 内では Ticket(プレイヤー) とゲームサーバの接続先の情報のセットを Assignment と呼び、ここではそのAssignment が space-agon の frontend に返却されるまでの流れを紹介します。 director が OpenMatch および Agones とやりとりすることによって、マッチングから Assignment の作成要求まで行います。
- director が open-match-backend に mmf にアクセスするための service 情報と MatchProfile を渡し、条件に当てはまるマッチ一覧をリクエスト
- open-match-backend がmmfを呼び出してマッチ一覧を取得
- 作成したマッチ一覧を director に返却
- director から、ゲームサーバを割り当てるよう Agones SDK を用いて Agones に対してリクエストを送信
- Agones がゲームに使用するゲームサーバを確保し、予約したゲームサーバの情報(IPアドレスやポート番号)をdirector に返す
- Director は open-match-backend に、4.で取得したゲームサーバの情報と2.で取得したマッチを送信し、Assignmentを作成するよう要求
- open-match-backend が Assignment を作成し、open-match-frontend に渡す
- open-match-frontend 経由で space-agon の frontend に渡しプレイヤー同士の対戦が開始する
space-agonでは、director が上記 1. 〜 6. の処理を定期実行(毎秒)することで、順次Assignmentを作成し、プレイヤー同士のマッチを提供しています。
![](https://sreake.com/wp-content/uploads/2024/06/space-agon-2.png)
マッチの作成
「Assignmentの要求から返却まで」の2.でマッチ一覧を取得していますが、このマッチ一覧はどのように作られるのかを紹介します。 space-agon の mmf にはマッチング関数が定義されており、OpenMatch がマッチング関数を実行することでマッチが作成されます。
また、OpenMatch とのやりとりは mmf 内に OpenMatch と通信するための gRPC サーバを作成し、そのサーバを経由してマッチング関数の実行等を行なう形になります。
- director が open-match-backend に mmf の service の情報と MatchProfile を渡し、条件に当てはまるマッチ一覧をリクエスト
- open-match-backend が MMF に対してマッチングをするようリクエスト
- open-match-query service 経由で open-match-redis にマッチングの対象となる Tickets を要求
- Tickets を返却
- 取得した Ticket 同士で マッチングを作成
- MMF Podによって作成されたマッチをbackend Podにレスポンスとして返却
- open-match-synchronizer にマッチ一覧を渡す
- マッチ一覧を open-match-evaluator に渡し、同期的に処理をリクエスト
- open-match-evaluator の最終評価が完了したマッチ一覧を open-match-backend に返却
- マッチ一覧を director に返却
![](https://sreake.com/wp-content/uploads/2024/06/space-agon-3.jpg)
ゲームサーバ内での処理
プレイヤーは ゲームサーバ への接続を行い、リアルタイムの対戦を行っています。
実際にはゲームサーバの中でリアルタイム通信はどのように実現しているのかを紹介します。
ゲームの同期
space-agon は敵プレイヤーの機体を追撃するシューティングゲームとなっていますが、同期は1秒に60回、データをゲームサーバ 側で処理し、更新した画面をレンダリングすることでサーバからそれぞれのプレイヤーに同期しています。
イベント生成
プレイヤーが何らかのアクション(移動、射撃)を行うと、クライアント側でアクションを検出しイベントを生成します。
生成されたイベントはプロトコルバッファオブジェクト(コード内では memoというオブジェクト)に変換され、websocket 経由でゲームサーバに送信します。
ゲームサーバの実装
ゲームサーバ内でプレイヤー同士の対戦を提供するために、ゲームを同期する方法、プレイヤーの接続のハンドリングやゲームを終了した時のサーバの制御等、自前実装を行う必要があります。
ゲームサーバの構成は、Agones SDK を用いて実装することができ、space-agon では主に下記のような内容を実装しています。
- Agones SDK を使用して Agones 上に組み込めるようにする
- ゲームサーバのヘルスチェックや割り当て状況の監視、プレイヤーがいなくなった後のシャットダウンも実装する
- ゲームサーバにアクセスするためのエンドポイント用HTTPサーバ
- ゲーム中は websocket でリアルタイム通信が行われる
- ゲームロジックの実行
- 同期は1秒に60回(60FPS)、ゲームサーバからそれぞれのプレイヤーに同期される
- プレイヤーの接続と切断のハンドリング
- クライアントが接続または切断するたび、プレイヤーの数を追跡し、必要に応じてAgonesを通じてスケーリングやシャットダウンの操作を行う
space-agon では上記の仕様を実装し、ゲームサーバとして実装することで Fleet というリソース、つまりは Agones の管理下に置かれることになります。
Agones はあくまでゲームサーバの管理をkubernetes上でしてくれるフレームワークを提供しているだけであって、ゲームサーバの内部動作は自前で実装する必要があるということになります。
ゲームサーバのライフサイクル
Agones が提供しているゲームサーバは “Ready”と”Allocated”の2つの状態を持ち、ライフサイクルに応じて変化しており、下記の流れでゲームサーバの状態は変遷していきます。
- director が agones にゲームサーバの割り当てを要求
- 割り当てられたゲームサーバを返却された段階で、
State: Ready
なゲームサーバからState: Allocated
になり、Ticket(プレイヤー) が接続する dedicated Pod が作成される - プレイヤー両者がブラウザを閉じると、割り当てられたゲームサーバ(dedicated Pod)が削除される
なお、space-agon ではマッチ終了時にゲームサーバを削除され、新たに State: ready
なゲームサーバを再作成する形で実装していますが、マッチ終了時に使われていたゲームサーバを再利用する形で実装することもできます。
また、ゲームサーバのレプリカ数は Fleet で定義してますが、FleetAutoscaler は需要に応じて Fleet のオートスケーリングを行うリソースで、マッチングに使用できるゲームサーバに過不足があれば下記のように自動的に調節してくれます。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingGameServerSet 4m10s fleet-controller Scaling active GameServerSet dedicated-swrgz from 2 to 3
Normal ScalingGameServerSet 3m40s fleet-controller Scaling active GameServerSet dedicated-swrgz from 3 to 4
Normal ScalingGameServerSet 2m40s fleet-controller Scaling active GameServerSet dedicated-swrgz from 4 to 5
Normal ScalingGameServerSet 2m10s fleet-controller Scaling active GameServerSet dedicated-swrgz from 5 to 2
まとめ
space-agon、OpenMatch、Agonesの3つのOSSが登場しましたが、それぞれの役割としては次のようにまとめることができます。
- space-agon:ゲームそのものを提供し、AgonesとOpenMatchの機能を使用するためのエントリポイントとなる
- OpenMatch:マッチング時に必要なフレームワークを提供
- Agones:対戦時に使用するゲームサーバを管理する機能の提供
Agones と OpenMatch のそれぞれのコンポーネントを利用し、space-agon というゲームを組み合わせて、簡易的なデモを提供するOSSであるspace-agonを紹介しました。
おわりに
本記事では、ゲームインフラに用いられるOSSの調査として space-agon を通じて、Agones や OpenMatch の動作を調査しました。
space-agon で用いられたOSSや構成を踏まえて、今後は space-agon そのものに監視基盤の導入やSREの適用という視点で調査を行なっていく予定です。