AgonesとKubernetesの拡張機能

この記事はGoogle Cloud + Gaming Advent Calendar 2020の1日目のものです。
この記事では、KubernetesのGaming Libraryである[Agones](https://agones.dev/site/)がKubernetesの拡張機能をどのように使って実現されているかを技術的に解説する記事です。

※ Kubernetesの基本知識を前提としています

# Agonesとは？

AgonesはKubernetes上で動くGame Serverを制御するライブラリです。
Kubernetesの拡張機能であるAdmission Webhook, Custom Resource Definition, API Aggregationを使ってその機能を実現しています。

> 公式サイト: https://agones.dev/site/

### Agonesのユースケース

AgonesはGame Serverを起動したりスケールさせたりするために使いますが、Gameの中でも特にリアルタイムマルチプレイゲーム(e.g. 協力プレイ, 対戦)用に使用します。
コンテナはベストプラクティスとして状態を持たない(ステートレス)ことが望ましいですが、リアルタイムマルチプレイではその名の通りリアルタイムで情報を処理するため、どうしても状態を持つ必要が出てきます。
Kubernetes上ではコンテナのスケジューリングを基本Kubernetesにお任せするので、コンテナがいつ起動するのか/いつ終了するのかを完全に制御することはできません。
普通のコンテナとしてリアルタイムマルチプレイの機能を持ったGame Serverを動かしていると、ユーザー同士が対戦・協力プレイなどのリアルタイム性の高いゲームをしている最中にコンテナが終了され、ユーザー体験が台無しにされてしまう可能性があります。
そこで出てくるのがAgonesです。 Agonesはコンテナ(正確にはPod)が勝手に終了しないように保護したり、グローバルな通信をServiceやIngressといったKubernetesの機能を介さずに通信を受け付けられるようにするなど、リアルタイムマルチプレイに必要な機能を提供してくれます。

本記事ではそのAgonesを支えるKubernetesの拡張機能部分を解説していきます。
Agonesのより詳しい説明については、素晴らしい記事があるのでこちらを紹介します。

参考記事: [Agones 超入門](https://medium.com/google-cloud-jp/agones-beginner-jp-5a6553e7e9a4)

# Agonesを支えるKubernetesの拡張機能

ここからはKubernetesの拡張機能についての話になってきます。
KubernetesはPlatform for Platformと呼ばれることが多くなってきたプロダクトですが、素晴らしいことにその機能をユーザー自身が拡張する仕組みを提供しています。
そのKubernetesが提供している拡張機能を使うことで、Agonesが実現しているような特殊な機能を実装することができます。

そのKubernetesが提供している拡張機能には3つの種類があります。

 1. Admission Webhook
 2. Custom Resource Definition(CRD)
 3. API Aggregation

それぞれ簡単に説明していきます。

### 1. Admission Webhook

Admission Webhookは、Resourceを作成・更新・削除などをした時に任意のWebhookを挟むことができる、という拡張機能です。

![admission-controller-phases.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/d98bf7e4-c104-d6a8-279b-8251c03d6949.png)
出典: https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/

ユースケースとして、Podに`resources.requests`や`resources.limits`が付いていない時はデフォルト値を設定するようにしたいとします。

Admission Webhookには、`Mutating Webhook`(デフォルト値を埋め込んだり変更したりできるHook)と`Validating Webhook`(決めたルールに沿っているか検証するHook)の2種類があります。
上のユースケースだと、Mutating Webhookで`resources.requests`や`resources.limits`が空の場合はデフォルト値を埋めて、Validating Webhookで本当に`resources.requests`や`resources.limits`が埋まっているかを確認する処理を挟むことで実現できます。

あるResource(Pod, Deploymentなど)の操作に対して任意の処理(Webhook)を挟むことになるため、CRDやAPI Aggregationに比べると自由度は高くありません

### 2. Custom Resource Definition

CRDは、PodやDeployment, Serviceなど組み込みAPI Objectの他に、独自のAPI Objectを作成できる機能です。
下図のようにCustom Resource Definitionを定義し、Custom Resouceを監督するCustom Controllerを実装することで、独自のAPI Objectを管理できます。

![crd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/56997384-00c4-eac3-0a1e-e36d9f095ca4.png)

ユースケースとしては`〇〇 Operator`として使われることが多く、たとえば[Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator)や[MySQL Operator](https://github.com/oracle/mysql-operator)のように、ステートフルなソフトウェアをCustom Resourceとして構築・運用を自動化する目的で使われています。

Kubernetesの拡張機能としては主流の機能ですが、次のAPI Aggregationよりも柔軟性・拡張性には劣ります。

### 3. API Aggregation

API Aggregationは、拡張API Serverとして動くWebhook Serverを用意することでCRDよりもより柔軟なAPI Objectを作成できます。
Kubernetesで実際に使われる例としては、[Metrics Server](https://github.com/kubernetes-sigs/metrics-server)が有名です。

![aggregation_crd.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/32412c86-04a2-c460-e5a6-d6e781a60df3.jpeg)

ユースケースとしては、CRDよりもより複雑かつ柔軟性が求められるAPI Objectを作りたい時にAPI Aggregationを利用します。
たとえばCRDを作ると、Create/Update/Delete/Patchなどの基本的なAPIの操作方法が提供されますが、そこに独自のAPI Path(e.g. logs, execのような特殊なAPI)を加えたい時にはAPI Aggregationを選択します。

実際にAPI Aggregationを使って実装されているプロダクトは実は多くありません。というのもCRDを使えば大体のプロダクトは目的を達成できるからです。

# Kubernetesの拡張機能とAgones

これまでKubernetsの3つの拡張機能の説明をしてきました。
Agonesはこれらの拡張機能を利用して実装されています。Agonesが利用している機能はなんと、この3つ全てです。
3つの機能のそれぞれの特性を活かして実装されており、Kubernetesの拡張機能を使って何かを実装する場合はとても綺麗なお手本になります。

では、これからAgonesが3つの拡張機能をどのように使っているのか確認していきたいと思います。

### AgonesとAdmission Webhook

Admission Webhookを確認するにはkubectlを使います。Mutating WebhookとValidating Webhookの2種類に分かれているので注意してください。

まずはkubectlを使って、Mutatingから見ていきます。

```bash
$ kubectl get mutatingwebhookconfigurations
NAME                      CREATED AT
agones-mutation-webhook   2020-11-30T13:50:39Z
```

`agones-mutation-webhook`がMutatingの設定名です。中身を見ると次のようになっています(一部省略)。

```yaml
$ kubectl get mutatingwebhookconfigurations agones-mutation-webhook -o yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: agones-mutation-webhook
  ...
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    service:
      name: agones-controller-service
      namespace: agones-system
      path: /mutate
      port: 443
  rules:
  - apiGroups:
    - agones.dev
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - gameservers
    - fleets
    scope: '*'
```

注目するのは`clientConfig.service`と`clientConfig.rules`です。
`rules`でなんのAPI Resourceがどの操作の時にWebhookが動くのかを設定しており、`service`でWebhookの接続先を決めます。
この場合は、`agones-system` namespaceの`agones-controller-service`というServiceの後ろにいるController(Podで動いている)に問い合わせています。

続いてkubectlで、Validatingを確認します。

```bash
$ kubectl get validatingwebhookconfigurations
NAME                        CREATED AT
agones-validation-webhook   2020-11-30T13:50:39Z
```

こちらもMutatingと似た設定で、検証用のWebhookの設定を行なっていることが分かりました(YAMLの中身は省きます)。

Agonesの場合は、`GameServer`か`Fleet` Resourceが作成(CREATE)されると、Mutatingとしてデフォルト値が埋め込まれ、Validatingでその検証が行われます。

### AgonesとCRD

AgonesのメインのAPI Resourceには基本単位となる`GameServer`と、複数の`GameServer`を管理する`Fleet`、`Fleet`のオートスケールを管理する`FleetAutoscaler`があります。

これらはCRDで実現されています。これもkubectlから確認します。

```bash
$ kubectl get crd
NAME                                                   CREATED AT
fleetautoscalers.autoscaling.agones.dev                2020-11-30T13:50:38Z
fleets.agones.dev                                      2020-11-30T13:50:38Z
gameserverallocationpolicies.multicluster.agones.dev   2020-11-30T13:50:38Z
gameservers.agones.dev                                 2020-11-30T13:50:38Z
gameserversets.agones.dev                              2020-11-30T13:50:38Z
```

ここで確認したCRDはあくまでもAPI Resourceの**定義**です。
これらのResouceをどのように管理し、どのように振る舞うのかを実現しているのがCustom Controllerです。
AgonesのCustom Controllerはデフォルトでは`agones-system` namespaceに存在しています。

```
$ kubectl get pods -n agones-system
NAME                                READY   STATUS    RESTARTS   AGE
agones-controller-f8965d66d-2vhfm   1/1     Running   0          25m
```

このController Podは1つしかないため、もしなんらかの原因で一度落ちてしまうとその間は`GameServer`や`Fleet`などが機能しなくなるため注意が必要です。

実装については細かく説明していく余裕はないので割愛しますが、ソースコードの場所だけここに残しておきます。
興味のある方はソースコードリーディングに挑戦してみてください。

※ Agonesのバージョンは1.10のコード

GameServer: https://github.com/googleforgames/agones/blob/v1.10.0/pkg/gameservers/controller.go
GameServerSet: https://github.com/googleforgames/agones/blob/v1.10.0/pkg/gameserversets/controller.go
Fleet: https://github.com/googleforgames/agones/blob/v1.10.0/pkg/fleets/controller.go
FleetAutoscaler: https://github.com/googleforgames/agones/blob/v1.10.0/pkg/fleetautoscalers/controller.go

### AgonesとAPI Aggregation

Agonesには`GameServerAllocation`機能があります。
これはユーザーがGameServerに入室して使用している(Allocated)か使用していない(Ready)か、ステータスを識別するための機能です。

Agonesの`GameServerAllocation `機能はAPI Aggregationによって実現されています。

kubectlでAPI Aggregationの設定を確認します。

```bash
$ kubectl get apiservice v1.allocation.agones.dev
NAME                       SERVICE                                   AVAILABLE   AGE
v1.allocation.agones.dev   agones-system/agones-controller-service   True        38m
```

`v1.allocation.agones.dev`の設定を確認します。

```yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1.allocation.agones.dev
  ...
spec:
  group: allocation.agones.dev
  groupPriorityMinimum: 1000
  service:
    name: agones-controller-service
    namespace: agones-system
    port: 443
  version: v1
  versionPriority: 15
```

ここでも`spec.service`を確認すると、`agones-system`の`agones-controller-service`を向いています。 CRDで定義したCustom Resourceを管理していたのは`agones-controller` Podでしたが、`GameServerAllocation`も同じように`agones-controller`が管理していることが分かります。

参考: https://github.com/googleforgames/agones/blob/v1.10.0/pkg/gameserverallocations/controller.go

`GameServerAllocation`は、CRDのようにAPI Objectを作ってそれを管理する...といったものではなく、存在しているGameServerのステータスを`Ready`から`Allocated`に変更するためのResourceです。
そのためCRDではなく、柔軟な動作ができるAPI Aggregationが使われているのでないかと思われます。

# 最後に

以上がKubernetesの拡張機能とそれをAgonesがどう活用しているかの確認でした。

私もこの記事を書いて、あらためて`agones-controller`が重要な役割を占めていることを再認識しました。

AgonesはKubernetesの拡張としてとても綺麗な作りをしているので、日本でもたくさん使われると知見が貯まってもっと面白くなりそうだなー、と思っています。

以上