AgonesをMetalLBを用いて、ローカルのMinikubeクラスタ上で稼働させ疎通確認する
要約
- Agonesをローカル環境のMinikubeクラスタで、Hyper-Vドライバを使用して稼働させた
- 上記の稼働にはMetalLBの力を借りた
- 構成はTerraformで管理し、下準備が完了していれば
terraform apply
で環境が構築できるようにした
GitHub repo
READMEはこちら
動作方法
経緯
Agonesとは
Agonesとは,GoogleとUbisoftが共同で開発したKubernetes環境における専用ゲームサーバ管理を行う為のOSSです。
What is Agones?
Agones is an open source platform, for deploying, hosting, scaling, and orchestrating dedicated game servers for large scale multiplayer games, built on top of the industry standard, distributed system platform Kubernetes.
Agonesは、業界標準の分散システムプラットフォームであるKubernetes上に構築された、大規模なマルチプレイヤーゲーム用の専用ゲームサーバーのデプロイ、ホスティング、スケーリング、オーケストレーションのためのオープンソースプラットフォームです。
Built with both Cloud and on-premises infrastructure in mind, Agones can adjust its strategies as needed for Fleet management, autoscaling, and more to ensure the resources being used to host dedicated game servers are cost optimal for the environment that they are in.
クラウドとオンプレミスの両方のインフラを考慮して構築されたAgonesは、ゲーム専用サーバーをホストするために使用されているリソースが、その環境に最適なコストであることを保証するために、フリート管理、オートスケールなどの戦略を必要に応じて調整することができます。
上記の記事にも記載されている通り、FPSやMMO,MOBAといったジャンルのゲームサーバを開発する際は、多くの場合ステートフルなアプリケーションが必要とされます
Many of the popular fast-paced online multiplayer games such as competitive FPSs, MMOs and MOBAs require a dedicated game server
Dedicated game servers are stateful applications that retain the full game simulation in memory. But unlike other stateful applications, such as databases, they have a short lifetime. Rather than running for months or years, a dedicated game server runs for a few minutes or hours.
対戦型FPS、MMO、MOBAなど、人気の高い高速オンラインマルチプレイヤーゲームには、専用のゲームサーバーが必要なものが多くあります
ゲーム専用サーバーは、ゲーム・シミュレーションを完全にメモリ上に保持するステートフル・アプリケーションです。しかし、データベースなどの他のステートフル・アプリケーションとは異なり、その寿命は短いものです。数ヶ月、数年単位で稼働するのではなく、数分、数時間単位で稼働するのが専用ゲームサーバーです。
それらのプロセスのオーケストレーションを補助してくれる物となります。
ドキュメントを見てみるとクラスタを各種クラウドベンダーのK8sサービス(GKE,EKS,AKS)で作成する方法も記載されていますが
まずは、ローカルでの動作確認を行いたかったのでMinikubeでの環境構築について確認しました。
この時は、該当ページをよく読みもしないでさらっと流し読みだけして「結構簡単そうだなぁ~ハナホジー😗」と考えていました。
即落とし穴にはまるとも知らずに・・・
Minikubeとは
Minikubeはローカル環境でKubernetesを簡単に実行するためのツールです。
Installation
の項目にWindowsの場合のインストール方法も記載されています。
Minikubeの上でAgones動かすのなんて余裕ンゴwww → 「ツナガラナイデス・・・」
人生は甘くないし、「自分はドキュメントとか文章をちゃんと読んでいない」事を痛感させられます。
クラスタを作成し、Helmを使用した手順通りインストール作業を実施します。
Agonesのシステム系のコンポーネントの稼働を確認した後に
ゲームサーバを作成し
(この時実は初回のgameserver起動時のみゲームサーバがヘルスチェックに失敗する現象が発生しますが、そちらに関しては後述している「コンテナイメージのプルに時間かかりすぎ問題」をご参照ください)
そして、接続する為のサービスを作成してみますが
Pod群はしばらく経つと動き出しますが、サービスの状態を確認するといつまで経ってもEXTERNAL-IP
がpending状態で、IPが割り当てられません。
この状態では手元のWindowsからクラスタに対してLoadBalancerを介して、nc等でパケットを投げても内部で動いてるコンテナへパケットが到達できません。
kubectl get svc --all-namespaces
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
agones-system agones-allocator LoadBalancer 10.96.110.55 <pending> 443:31503/TCP 48m
agones-system agones-allocator-metrics-service ClusterIP 10.103.30.96 <none> 8080/TCP 48m
agones-system agones-controller-service ClusterIP 10.108.162.228 <none> 443/TCP,8080/TCP 48m
agones-system agones-ping-http-service LoadBalancer 10.96.220.206 <pending> 80:30494/TCP 48m
agones-system agones-ping-udp-service LoadBalancer 10.102.150.109 <pending> 50000:32616/UDP 48m
default agones-gameserver LoadBalancer 10.100.174.130 <pending> 7000:31168/UDP 18s
で、こういう時の為にminikube tunnel
というコマンドが用意されています。
「はいはい、これでおっけーね🤭」とminikube tunnel -p agones
とコマンドを実行します。
すると、とりあえずEXTERNAL-IP
にIPが割り当てられるわけですが、
kubectl get svc --all-namespaces
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
agones-system agones-allocator LoadBalancer 10.96.110.55 10.96.110.55 443:31503/TCP 50m
agones-system agones-allocator-metrics-service ClusterIP 10.103.30.96 <none> 8080/TCP 50m
agones-system agones-controller-service ClusterIP 10.108.162.228 <none> 443/TCP,8080/TCP 50m
agones-system agones-ping-http-service LoadBalancer 10.96.220.206 10.96.220.206 80:30494/TCP 50m
agones-system agones-ping-udp-service LoadBalancer 10.102.150.109 10.102.150.109 50000:32616/UDP 50m
default agones-gameserver LoadBalancer 10.100.174.130 10.100.174.130 7000:31168/UDP 96s
この状態で、nc -u 10.100.174.130 7000
とコマンド打って、手元からagones-gameserver:LoadBalancer
経由で接続しようとしても、接続ができません。
で、調べてみた所例えば
- 「
nc 10.96.220.206 80
としてagones-ping-http-serviceにTCPで接続にいくと接続できる」 - 「接続先をagones-ping-udp-serviceにするとこれも接続できない」
という症状が発生している事がわかるので、なんかUDP通信の場合のみ疎通ができていない事がわかります。
「え、なんで?」となり、よくよくドキュメントを読み返してみます。
Windows (amd64)
hyper-v (might need this blog post and/or this comment for WSL support)
「うん、自分の環境はWindowsでHyper-Vドライバで稼働させてるな・・・」
Depending on your operating system and virtualization platform that you are using with Minikube, it may not be possible to connect directly to a GameServer hosted on Agones as you would on a cloud hosted Kubernetes cluster.
Minikubeで使用しているOSや仮想化プラットフォームによっては、クラウドでホストされているKubernetesクラスタのように、Agones上でホストされているGameServerに直接接続できない場合があります。
「むむ?🤨」
Warning
minikube tunnel (docs) does not support UDP (Github Issue) on some combination of operating system, platforms and drivers, but is required when using the Service workaround.
警告
minikube tunnel (docs) は、OS、プラットフォーム、ドライバの組み合わせによってはUDP (Github Issue) をサポートしていませんが、Service workaroundを使用する場合は必要です。
Use a different driver
If you cannot connect through the Serviceor use other workarounds, you may want to try a different minikube driver, and if that doesn’t work, connection via UDP may not be possible with minikube, and you may want to try either a different local Kubernetes tool or use a cloud hosted Kubernetes cluster.
別のドライバを使用する
Service経由で接続できない場合は、別のminikubeドライバを試してみて、それでもうまくいかない場合は、UDP経由の接続はminikubeではできない可能性があり、別のローカルKubernetesツールを試すか、クラウドホストのKubernetesクラスタを使用した方がよいかもしれません。
「ええ、UDPでLoadbalancerを介して接続したいのに・・・😑」
なんか打開策はないかと、人類の叡智が集まるGithubのissueの海を泳いでいると、
minikube tunnel doesn't appear to support UDP LoadBalancers #12362
https://github.com/kubernetes/minikube/issues/12362
「ぐぬぬ・・・やっぱりそういうissue上がってる・・・、どないしよ・・・」と落胆している所
以下の書き込みを見つけます。
My eventual solution was to use metallb to satisfy the load balancer requirement, and then the hyperv driver with an external vswitch to get a routable/accessible IP. Published a public version of the config here for reference:
https://github.com/comerford/minikube-agones-cluster
私の最終的な解決策は、ロードバランサーの要件を満たすためにmetallbを使用し、その後、ルーティング可能/アクセス可能なIPを取得するために外部のvswitchとhypervドライバを使用することでした。参考のため、コンフィグのパブリックバージョンをここに公開します。
「MetalLBとはなんぞや?」となり、調べることでどうやら今回のようなケースでもロードバランサーに固有のIPを割り振れるかも、という事で次の項目に続きます。
MetalLBとは
既に偉大なる先人の方々が残した記録の力を借ります。
MetalLBはオンプレミスでもtype: LoadBalancerを扱えるようにする、kubernetes上で動作するOSSのアプリケーションです。
クラウドでtype: LoadBalancerを使った場合は勝手にIPアドレスが払い出されますが、MetalLBの場合はあらかじめユーザが指定したIPアドレスプール(例:192.168.0.0/24など)からの払い出しになります。
つまりは自分でLoadbalancerに割り当てるipを決めて、振り分けすることができるという事になります。
minikube ip
で手元からminikubeに到達するためのIP(今回の場合だとagones
という名前のVMに到達する為のIP)が取得できます。
と、いうことは今回の場合はこのIPをmetalLBの設定として渡しておけば、起動したLoadbalancerに自動で指定したIPを払い出す事ができます。
何とかできそうな見立てがついたので、Terraformの構成に落とし込みました。
構成について
構成は大きく
- helm_metallb.tf MetalLB関連の構成
- helm_agones.tf Agones関連の構成
- helm_gameserver gameserver関連の構成
に3分割されており,それぞれ上から順番に実行されていくような形です。
もっと詳細なフローで言うと
-
data.external.minikube_ip"
がminikube ip
コマンドの結果を受け取り、保持 - クラスタ初回起動時の対策としてあらかじめ
null_resource.preload_images
で指定されているコンテナイメージを全てminikube image load
コマンド経由でPullする- なぜ明示的にPullしているのかは、後述している「コンテナイメージのプルに時間かかりすぎ問題」をご参照ください。
-
helm_release.metallb
(helm_metallb.tf)を通じて、MetalLBのシステムコンポーネント(speakerとcontrollerやCRD定義など)がインストールされる -
helm_release.metallb_minikube
(helm_metallb.tf)を通じて、MetalLBの利用者側で設定するCRD(IPAddressPoolとL2Advertisement)がインストールされる -
helm_release.agones
(helm_agones.tf)を通じて、Agonesのシステムコンポーネント(controller,allocator etc)がインストールされる -
helm_release.gameserver
(helm_gameserver.tf)を通じて、gameserverがインストールされる
というのが流れとなります。
Input variables等については以下をご参照ください。
構成の作成中に発生した問題
CRDを含むHelmチャートを作成し、その依存するサブチャートとしてCRD定義が行われるチャートを設定しても上手く動作しない
つまり、MetalLBのケースで言うと
- 「helm_release.metallb」のインストールによってCRD関連の定義が先にHelmリリースとしてインストールされる、この場合は「IPAddressPool」「L2Advertisement」のCRD定義が作成される
- その後、「IPAddressPool」「L2Advertisement」自体の定義が作成される
という挙動を期待して、初めはシステム系のサブチャートとそれを含む親のチャートして管理しようとしました。
このまとめた親となるチャートを使用してリリースを作成しようとしても
│ Error: unable to build kubernetes objects from release manifest: [resource mapping not found for name: "local-address-pool" namespace: "metallb-system" from "": no matches for kind "IPAddressPool" in version "metallb.io/v1beta1"
│ ensure CRDs are installed first, resource mapping not found for name: "L2-advertisement-default" namespace: "metallb-system" from "": no matches for kind "L2Advertisement" in version "metallb.io/v1beta1"
│ ensure CRDs are installed first]
│
│ with helm_release.metallb_minikube,
│ on helm_metallb.tf line 89, in resource "helm_release" "metallb_minikube":
│ 89: resource "helm_release" "metallb_minikube" {
という形で「先にCRDを定義しろ!」と怒られます。
当然、自分で定義するCRD定義側のtemplateのannotationにはHelm hookpost install
を付与していました。
それでもやっぱり、同じエラーが出力され動作しませんでした。
「え?先にCRDが定義されて、その後CRD自体が作成される事を期待してるんだけど・・・依存関係はChart.yamlで定義してるし・・・」と思って調べてみると
It is not like it is not supported. It is like it can't be supported as of now, because k8s gives no way to understand when a custom resource can be marked ready. And hence the current implementation of helm can only identify limited set of k8s-resources for which Helm-Hooks are applicable.
サポートされていないわけではありません。なぜなら、k8sはカスタムリソースがいつreadyとマークされるかを理解する方法を提供しないからです。そのため、現在のhelmの実装では、Helm-Hooksが適用できるk8sリソースのセットを限定的にしか特定できません。
なるほど、つまりHelm単体ではCRDを含むチャートのHookが上手く動作しないようでした。(v3.8.2 2022/11/19現在)
という事で、「だったら依存関係はTerraform側に逃してしまえ!」という事で
2つのresourceにhelm_releaseを分割し、depends_on
で依存関係を定義することでお茶を濁しました。
コンテナイメージのプルに時間かかりすぎ問題
ある程度Terraform構成での環境構築が安定してきた & testフォルダ以下にtf構成のテストを書いた事で分かったことですが
- 「初回だけterraform applyが完了するまで5分強くらい時間がかかる、クラスタを削除せず2回目以降の適用では数十秒程度で完了する
- 「クラスタ作成後の初回のインストールだけこける、gameserverのヘルスチェックがこける」
- tfの構成自体はMacでも動作できるようにしていたのでそちらで試してみると、初回から数十秒程度で適用が完了し、gameserverも動作している
という現象が発生していました。
Windowsでクラスタ作成直後にterraform applyした後に、kubectl describe pod ~
で様子を見てみると・・・
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 110s default-scheduler Successfully
assigned metallb-system/metallb-speaker-9p8l5 to agones
Normal Pulling 109s kubelet Pulling image "quay.io/metallb/speaker:v0.13.7"
Normal Pulled 43s kubelet Successfully
pulled image "quay.io/metallb/speaker:v0.13.7" in 1m5.997699979s
Warning Failed 14s (x4 over 43s) kubelet Error: secret "metallb-memberlist" not found
Normal Pulled 14s (x3 over 43s) kubelet
コンテナのPullに非常に時間がかかっている事がわかります。
どうやらコンテナのPullに1分以上かかっていることが原因で、ヘルスチェックに失敗していることが原因だったようです。
この現象、例えばコマンドminikube ssh -p agones docker pull quay.io/metallb/speaker:v0.13.7
と言う形で
クラスタのノード上でdokcer pull経由でコンテナイメージをプルした時間を測ってみても、以下のように時間がかかっている事が分かります。
minikube ssh -p agones time docker pull quay.io/metallb/speaker:v0.13.7
v0.13.7: Pulling from metallb/speaker
213ec9aee27d: Pull complete
dc9e1a886587: Pull complete
f17e814c34e8: Pull complete
5ba64e3f5d64: Pull complete
dce6a2f1bc62: Pull complete
Digest: sha256:aeab5aeeb25c7ca31a1c468a35b1db4513cd6199bf376bdf19ff5689d2ebad28
Status: Downloaded newer image for quay.io/metallb/speaker:v0.13.7
quay.io/metallb/speaker:v0.13.7
real 1m16.251s
user 0m0.013s
sys 0m0.007s
MinikubeのGithub issueに関連するものを見つけたのですが
ここをみた上で自分の環境についての情報をまとめると
- driverはDockerを使用、バージョン情報は以下
dockerのバージョン情報
$ docker version
Client:
Version: 20.10.20
API version: 1.41
Go version: go1.18.7
Git commit: 9fdeb9c
Built: Tue Oct 18 18:14:26 2022
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.20
API version: 1.41 (minimum version 1.12)
Go version: go1.18.7
Git commit: 03df974
Built: Tue Oct 18 18:20:17 2022
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v1.6.8
GitCommit: 9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6
runc:
Version: 1.1.4
GitCommit: 5fd4c4d144137e991c4acebb2146ab1483a97925
docker-init:
Version: 0.19.0
GitCommit: de40ad0
cri-dockerd --version
cri-dockerd 0.2.1 (HEAD)
- Kubernetesのバージョンはv1.23.9
- minikubeのバージョンはv1.28.0
-
minikube image loadコマンド経由でイメージをクラスタにあらかじめ登録した場合は10秒弱程度でコンテナイメージをクラスタが稼働しているNodeに登録できる
image loadの場合
約9秒
Measure-Command {minikube image load quay.io/metallb/speaker:v0.13.7 -p agones}Seconds : 9 Milliseconds : 28 Ticks : 90281477 TotalDays : 0.000104492450231481 TotalHours : 0.00250781880555556 TotalMinutes : 0.150469128333333 TotalSeconds : 9.0281477 TotalMilliseconds : 9028.1477
- 当初はminikube cache addを使用していましたが、deprecatedのようなので最終的に
image load
にした
- 当初はminikube cache addを使用していましたが、deprecatedのようなので最終的に
という状態だったので、最終的にminikube image load
コマンドを発行するprovisinerを,あらかじめloadが必要なコンテナ数分のnull_resource.preload_images
として定義する事でお茶を濁してます。
ワークアラウンドとして異なるドライバ(containerdなど)を使用する事についても記載されていますが、そちらは確認していません。
Linuxだとdata.externalの動作時に固まって構成が適用できない問題
Macでも動作確認できたので、一応Linuxでも動作確認するかと思ってWSL2上のUbuntu 20.04で動作確認しようと試みた所
data.external.minikube_ip_unix
でminikube ipの出力をとってくる際に固まってしまう現象が発生しました。
詳細は一応issueとして書いてますが
時間があるときに調べようかなと思ってます。
所感
- test以下のtf構成のテストにはTerratestを初めて使ってみた所、便利だった事
- GithubのReadme書くのに当たってterraform-docsの力を借りた所、それも便利だった事
これらについても、初めはこちらの記事に記載しようかなと思ったのですが
同じ記事にまとめてしまうとあまりに記事が長くなりそうなので
別途記事書こうと思ってます。
構成の量自体は少ないですが、今回上記の右往左往する過程が(たどったGithubのIssueとか調査ログとか)
自分の作業バックログとして使用しているTrelloのボードにつらつら縦方向に書き並べられているのを見て
「ああ、こういうときにスクラップを利用したらいいのかぁ」と思った次第でした。
😁
「"AgonesのTerraform構成盛りプレート、MetalLBを添えて~"と言うエレガントな名前は如何でしょうか?」
😶🧑
「濁すためのお茶っ葉もないですね。」
😇
参照
Agonesドキュメント
Agone GitHub
MetalLBドキュメント
MetalLB GitHub
Minikubeドキュメント
Minikube GitHub
Helmドキュメント
Helm GitHub
Discussion