概要
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/
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
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
にします。
docker build -t localimage/mod_simple-udp:0.1 .
Agones
インストール
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
kubectl apply -f Fleet.yaml
レプリカ数を2で指定しているためGameServerが2つデプロイされていることが確認出来ます。
$ 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のソースコードを確認してください。
$ 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します。
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
$ kubectl apply -f https://raw.githubusercontent.com/googleforgames/agones/release-1.3.0/examples/simple-udp/fleetautoscaler.yaml
FleetAutoscalerデプロイ後に再度GameServerをAllocateしてみます。
$ 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の詳細を確認しておきます。
$ 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)で通信するように変更もしておきます。
Dockerイメージ名はlocalimage/mod_allocator-service:0.1
にします。
docker build -t localimage/mod_allocator-service:0.1 .
デプロイ
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を実施してみます。
$ 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