Agones はなぜ、どのようにPodへの直接接続を実現しているか

Kubernetes上でゲームサーバーを扱うframeworkのAgonesゲームサーバーに直接(TCP or UDPで)接続する 機能を持つ。 ゲームサーバーの実体はPodだが、なぜPodへの直接接続が必要なのか、そしてAgonesがどのように直接接続の機能を提供しているかを読み解く。

まずはじめに、Agonesがない状態、つまり純粋なKubernetesの構成から見ていく。 Kubernetesにおいて外部からPodへのアクセスは通常 IngressService を経由する。 Ingress や Service の負荷分散機能によりPodの事情(Podの数や入れ替わりなど)を意識せずに統一的なアクセスができる。

f:id:castaneai:20220114125143j:plain
https://kubernetes.io/docs/concepts/services-networking/ingress/ より

しかし、このやり方はオンラインゲームのサーバーとは合わない部分がある。 オンラインゲームではrequest-responseで完結するHTTPとは違って接続が長時間維持され、同じ世界を共有するプレイヤー同士は高速で高頻度に通信する必要がある。

そうなると事前に同じ世界のプレイヤー同士で接続先のPodを決めておき、そのPodにプレイヤーが直接接続する方が望ましい(この処理はよくマッチメイキングと呼ばれる)。

f:id:castaneai:20220114135415j:plain

以上の理由から、AgonesはPodへの直接接続を提供する。 この機能がどうやって実現されているかはAgonesのFAQにかかれていて、 hostPort という機能を使っているらしい。

Traffic is routed to the GameServer Container utilising the hostPort field on a Pod’s Container specification.

https://agones.dev/site/docs/faq/#how-is-traffic-routed-from-the-allocated-port-to-the-gameserver-container

hostPort機能

hostPort は(Serviceを使わずに)コンテナの特定ポート番号を外部に直接公開できる機能である。 最初に書いたとおりKubernetesでは外部接続の受付には基本 Service を使うため hostPort が使われる機会は少なく、情報も少ない。

それどころか、ドキュメント には次のように書かれている。

どうしても必要な場合以外は、PodにhostPortを指定しないでください。PodをhostPortにバインドすると、Podがスケジュールできる場所の数を制限します、それぞれの<hostIP、 hostPort、protocol>の組み合わせはユニークでなければならないからです。

Agonesはこの「どうしても必要な場合」に当てはまる貴重な例なのかもしれない。

API Reference にも hostPort の項目はあるが、 "Most containers do not need this." と書かれている。 とはいっても、仕組みは割と単純である。クラスターには必ずコンテナを動かすNodeがいて、そのNodeが直接ポートを公開して該当のコンテナにつなげている。 つまり Docker コマンドでいうと docker run -p xxxx:xxxx というPort Forwarding の指定になる。

f:id:castaneai:20220327230631p:plain

たとえば次のように Port: 7053 で待機しているAgones GameServerがあるとする。

$ kubectl get gameserver
NAME                       STATE   ADDRESS        PORT   NODE     AGE
simple-game-server-56rk6   Ready   192.168.49.2   7053   agones   89s

この状態でNodeに直接SSHで入り、docker ps 1でコンテナの状態を見ると確かに Port Forwardingが指定されているのがわかる。

$ docker ps --format '{{.ID}} {{.Image}} {{.Ports}}'
...
8574c2b0cdbd k8s.gcr.io/pause:3.4.1 0.0.0.0:7053->7654/udp

Agonesによるポート番号の選択

再度同じ文を引用するが、hostPort 機能はポート番号を直接割り当てるため、同じノードで同じポートの設定が被ると動作しないという制限がある。

どうしても必要な場合以外は、PodにhostPortを指定しないでください。PodをhostPortにバインドすると、Podがスケジュールできる場所の数を制限します、それぞれの<hostIP、 hostPort、protocol>の組み合わせはユニークでなければならないからです。

しかし、Agonesの利用者はこの制限を意識しなくてもよい2。Agonesが内部で空いているポート番号を自動的に選んでくれるのである。 以下の portallocator.go が主にその実装が入ったファイルだ。

agones/portallocator.go at main · googleforgames/agones · GitHub

Agones内部では常にこのPortAllocatorが利用可能なポート番号の範囲、割当済のポート番号を管理してくれるため、hostPort 機能を使いつつもAgones利用者側は内部の仕様を理解しなくてもよいという仕組みになっている。

こういった機能を自前で実装するのは大変(Kubernetesに関する深い知識が必要)なので やはりKubernetesでゲームサーバーを管理するならAgonesはとても良い選択肢だと思える。


  1. KubernetesがDockerランタイムで動いている場合の例だが、たとえDocker以外だとしても似ている仕組みで動いているはず。

  2. portPolicy: Static にしている場合は例外。詳しくは GameServer Specification の ports の項目 を読むとよい。