OpenMatch+Agonesを試す(minikube環境) [1/2]


概要

k8sのプロダクトである、マッチングシステムのOpenMatchとゲームサーバー管理のAgonesを組み合わせた小規模なアプリケーションをminikube環境で動かします。

https://open-match.dev
https://agones.dev

公式ドキュメント内のGetting StartedとTutorialをベースに変更しながら組み合わせていきます。

※ゲームサーバはAgonesのTutorialを2クライアント接続可能なように改変
※ゲームクライアントはcurlでOpenMatchから接続情報を取得し、ncでUDPパケット送信するだけとします

実行環境

  • macOS Catalina(10.15.3)
  • minikube(--vm-driver=virtualbox)
  • OpenMatch(0.9.0)
  • Agones(1.3.0)
  • Go(1.13.7)

全体構成

理解に自信が無いので、間違いがありましたらコメントにてご指摘頂けると嬉しいです。

構成図

シーケンス図

構築

minikube install

デフォルトの設定(cpus 2, memory 2048)ではOpenMatchが動作しないため注意。
https://open-match.dev/site/docs/installation/

bash

brew install minikube
minikube config set vm-driver virtualbox
minikube config set cpus 4
minikube config set memory 4096
minikube start
eval $(minikube -p minikube docker-env)

GameServer

ソースコード
https://github.com/suecideTech/try-openmatch-agones/blob/master/GameServer/mod_simple-udp/main.go

main.go
var addrs = map[uint]net.Addr{}
()
func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) {
    b := make([]byte, 1024)
    for {
        sender, txt := readPacket(conn, b)

        switch addr := sender.(type) {
        case *net.UDPAddr:
            addrs[uint(addr.Port)] = addr
        }
()
// respond responds to a given sender.
func respond(conn net.PacketConn, sender net.Addr, txt string) {
    for _, sendaddr := range addrs {
        if _, err := conn.WriteTo([]byte(txt), sendaddr); err != nil {
            log.Fatalf("Could not write to udp stream: %v", err)
        }
    }
}

とりあえずローカル環境内で複数クライアントから接続出来るよう、接続元Portのみ記憶するように変更しました。(いろいろと問題はありますが...)

Dockerイメージ名はlocalimage/mod_simple-udp:0.1にします。

bash
docker build -t localimage/mod_simple-udp:0.1 .

Agones

インストール

bash
kubectl create namespace agones-system
kubectl apply -f https://raw.githubusercontent.com/googleforgames/agones/release-1.3.0/install/yaml/install.yaml

Fleet

GameServerのイメージlocalimage/mod_simple-udp:0.1を、2つ確保するFleetを作成します。

Fleet.yaml
https://github.com/suecideTech/try-openmatch-agones/blob/master/Deployment/Fleet.yaml

Deploy Fleet

bash
kubectl apply -f Fleet.yaml

レプリカ数を2で指定しているためGameServerが2つデプロイされていることが確認出来ます。

bash
$ kubectrl get gameserver
NAME                     STATE   ADDRESS          PORT   NODE       AGE
simple-udp-7pzwj-5hpnz   Ready   192.168.99.100   7052   minikube   5s
simple-udp-7pzwj-tp8dq   Ready   192.168.99.100   7074   minikube   5s

試しにncコマンドで接続し、電文「ALLOCATE」を送信します。
電文を受けた際の挙動はGameServerのソースコードを確認してください。

bash
$ nc -u 192.168.99.100 7052
hello
ACK: hello
ALLOCATE
ACK: ALLOCATE

---

$ kubectl get gameserver
NAME                     STATE       ADDRESS          PORT   NODE       AGE
simple-udp-7pzwj-5hpnz   Allocated   192.168.99.100   7052   minikube   3m27s
simple-udp-7pzwj-tp8dq   Ready       192.168.99.100   7074   minikube   3m27s

STATEがAllocatedになりました。
全体構成のシーケンス図ではAllocateServiceがAlocateするよう記載していますが、GameServer内部から自身のStateを変更するSDKも用意されています。

次に電文「EXIT」を送信しGameServerをShutdownします。

bash
EXIT
ACK: EXIT

---

$ kubectl get gameserver
NAME                     STATE   ADDRESS          PORT   NODE       AGE
simple-udp-7pzwj-l2xtt   Ready   192.168.99.100   7310   minikube   5s
simple-udp-7pzwj-tp8dq   Ready   192.168.99.100   7074   minikube   12m

GameServersimple-udp-7pzwj-5hpnzが消え、新たにsimple-udp-7pzwj-l2xttが生成されています。
GameServerは「EXIT」を受けるとAgones.SDKのShutdown()を実行します。
これによりSTATEがUnhealthyに遷移しますが、FleetはUnhealthyのGameServerを積極的に削除し、新たなGameServerをデプロイするように動作します。

次にFleetAutoscalerをデプロイします。
AgonesのGetting Startedをそのまま使います。

FleetAutoscaler

bash
$ kubectl apply -f https://raw.githubusercontent.com/googleforgames/agones/release-1.3.0/examples/simple-udp/fleetautoscaler.yaml

FleetAutoscalerデプロイ後に再度GameServerをAllocateしてみます。

bash
$ nc -u 192.168.99.100 7310
ALLOCATE
ACK: ALLOCATE

---

kubectl get gameserver
NAME                     STATE       ADDRESS          PORT   NODE       AGE
simple-udp-7pzwj-l2xtt   Allocated   192.168.99.100   7310   minikube   11m
simple-udp-7pzwj-rm4p7   Ready       192.168.99.100   7157   minikube   3s
simple-udp-7pzwj-tp8dq   Ready       192.168.99.100   7074   minikube   23m

ReadyのGameServerが2つ保たれているのがわかります。
先程デプロイしたyamlファイルにはbufferSize: 2と定義しているため、StateがReadyのGameServerが2つになるようにFleetのreplica数を調整しています。

bufferSize is the size of a buffer of “ready” and “reserved” game server instances.

Fleetの詳細を確認しておきます。

bash
$ kubectl describe fleet simple-udp
    :
Spec:
  Replicas:    3
    :
Status:
  Allocated Replicas:  1
  Ready Replicas:      2
  Replicas:            3
  Reserved Replicas:   0
    :
Events:
  Type    Reason                 Age    From              Message
  ----    ------                 ----   ----              -------
  Normal  CreatingGameServerSet  30m    fleet-controller  Created GameServerSet simple-udp-7pzwj
  Normal  ScalingGameServerSet   6m50s  fleet-controller  Scaling active GameServerSet simple-udp-7pzwj from 2 to 3

AllocateService

以下のTutorialをベースに、GameServerNameとIP,Portを応答するように改造します。
https://agones.dev/site/docs/tutorials/allocator-service-go/

オレオレ証明書を作成するなどの手順がありますが、minikubeのローカルな環境にデプロイするためhttp(80)で通信するように変更もしておきます。

ソースコード
https://github.com/suecideTech/try-openmatch-agones/blob/master/AllocateService/mod_allocator-service/main.go

Dockerイメージ名はlocalimage/mod_allocator-service:0.1にします。

bash
docker build -t localimage/mod_allocator-service:0.1 .

デプロイ

bash
kubectl create -f service-account.yaml
kubectl apply -f allocator-service.yaml

これでfleet-allocator-backend.svc.cluster.local:80/addressにhttpRequestを出せばサーバを確保し、接続用のIP/Portが返ってきます。

デバッグ用にNodePortで公開しているのでminikube serviceでIP/Portを確認し、minikube外からアクセスしてAllocateを実施してみます。

bash
$ minikube service list
|---------------|---------------------------|-----------------------------|-----|
|   NAMESPACE   |           NAME            |         TARGET PORT         | URL |
|---------------|---------------------------|-----------------------------|-----|
    :
| default       | fleet-allocator-backend   | http://192.168.99.100:32288 |
    :
|---------------|---------------------------|-----------------------------|-----|

$ kubectl get gs
NAME                     STATE   ADDRESS          PORT   NODE       AGE
simple-udp-7pzwj-rm4p7   Ready   192.168.99.100   7157   minikube   107m
simple-udp-7pzwj-wnt28   Ready   192.168.99.100   7150   minikube   113s

$ curl -k -u v1GameClientKey:EAEC945C371B2EC361DE399C2F11E http://192.168.99.100:32288/address
{"status":{"state":"Allocated","gameServerName":"simple-udp-7pzwj-rm4p7","ports":[{"name":"default","port":7157}],"address":"192.168.99.100","nodeName":"minikube"}}

$ kubectl get gs
NAME                     STATE       ADDRESS          PORT   NODE       AGE
simple-udp-7pzwj-rm4p7   Allocated   192.168.99.100   7157   minikube   107m
simple-udp-7pzwj-wnt28   Ready       192.168.99.100   7150   minikube   2m32s
simple-udp-7pzwj-xfzx9   Ready       192.168.99.100   7795   minikube   9s

curlの応答として以下が取得出来ました。

  • GameServerName: "simple-udp-7pzwj-rm4p7"
  • Ports: 7157
  • Address: 192.168.99.100

このIPとPortはクラスタ外からアクセス可能なものです。

また、GameServerの1つがSTATE: Allocatedに変化していることがわかります。

Tipes

Allocate直後に情報の受け渡しを実施する際は以下の方法が考えられます

  • GameServerのLabelに情報をセット
    • GameServer内からはAgonesのSDKにmetadataを取得/監視するAPIが用意されている
  • RESTやgRPCで通知
    • GameServerNameとPod名は同一のためPodの詳細からクラスタ内のIPアドレスがわかる
  • GameServerから別のサーバーリソースへ問い合わせ
    • 前途のmetadata監視APIでAllocatedになったタイミングでコールバック実行

今日はここまで

ここまででゲームサーバーを確保、再起動、スケーリングする方法がわかりました。

長くなりましたので記事を分けます。
後半ではOpenMatchの変更箇所と動作確認をした後にAgones、OpenMatchの自分の理解を書き連ねます。

【後半】
OpenMatch+Agonesを試す(minikube環境) [1/2]
https://qiita.com/suecideTech/items/4a04febaaf4f21e7e1a6