Google Cloud + Gaming Advent Calendar 2020の22日目を担当させて頂きます
株式会社インフィニットループのnagodonこと名古屋と申します。
TL;DR
本記事ではGCPのGoogle Cloud Game ServersとOpenMatchを使ったマルチプレイ
を実現する
ゲームサーバ
の.NET Core3.1
でのAgonesとOpenMatchの連携方法について記載します。
連携に特化した内容になっていますがご了承ください。
※触っていた段階では.NET5
がでてなかったので.NET Core3.1
となっております
はじめに
Google Cloud Game Serversに関してはKubernetesでゲームサーバを管理するAgonesのフルマネージドサービスになります。
Google Cloud Japan YuttyさんのGoogle Cloud Game Servers (beta) の紹介の記事を見ていただけるとGoogle Cloud Game Serversの概要がわかるので是非みてください。
Agonesに関しては公式サイトとGoogle Cloud + Gaming Advent Calendar 2020の1日目の
@go_vargoさんのAgonesとKubernetesの拡張機能に詳しく書かれてるので是非みてください。
OpenMatchに関してはGoogle Cloud + Gaming Advent Calendar 2020の13日目の
@castaneaiさんのOpen Matchを使ったローカル開発を考えるに詳しく書かれているので是非みてください。
前提
Google Cloud Game Servers、Agones、OpenMatchそれぞれ用語やコンポーネントが多数出てくるため1記事ではとても書ききれるようなものではないため、各前提知識があるという想定で.NET Core3.1
での連携の部分に特化して記載します。
AgonesとOpenMatchの連携方法について
連携部分はOpenMatchのDirector
でMatchFunction
を呼び出して、マッチングが成立したタイミングでゲームサーバ
を割り当てる際に連携します。
基本的にAgonesのAllocatorService
を使用するのが推奨されているため、AllocatorService
を使用して連携します。
AllocatorServiceに認証について
AllocatorServiceはmTLS認証を用いて認証を行うため事前準備として証明書の設定が必要になります。
https://agones.dev/site/docs/advanced/allocator-service/
※予約済みIPを使う場合はサーバの証明書は設定する必要はありません
今回は予約済みIPを使わない想定でサーバ証明証明書を設定するやり方を記載します。
基本的には公式のドキュメント通りになるのですが、環境構築ごとに毎回ビルドをしなくてもいいように
GCP
のSecretManagerに自身で作成した証明書を保存してソースコードで使えるようにします。
証明書の設定
証明書を設定するにはcert-manager
が必要ため、Kubernetes
に事前にインストールします。
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
次に以下のシェルスクリプトを作成して、agones-allocator
のLoadBalancer
にエフェメラルグローバルIP
が付与された後にスクリプトを実行します。
やっていることは自己証明書を作成して、Agonesへの設定と.NET Core3.1
のアプリから証明書を取得できるようにSecretManagerにデータを保存する形になります。
#!/bin/bash -u
KEY_FILE=client.key
CERT_FILE=client.crt
TLS_CA_FILE=ca.crt
EXTERNAL_IP=$(kubectl get services agones-allocator -n agones-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
REGION=asia-northeast2
# 初回のみ実行されるので適宜質問に答えてファイルを生成します
if [ ! -f "${KEY_FILE}" ]; then
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE}
fi
# Create a self-signed ClusterIssuer
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
EOF
# Create a Certificate with IP for the allocator-tls secret
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: allocator-tls
namespace: agones-system
spec:
commonName: ${EXTERNAL_IP}
ipAddresses:
- ${EXTERNAL_IP}
secretName: allocator-tls
issuerRef:
name: selfsigned
kind: ClusterIssuer
EOF
CERT_FILE_VALUE=$(cat ${CERT_FILE} | base64 -w 0)
TLS_CA_VALUE=$(kubectl get secret allocator-tls -n agones-system -ojsonpath='{.data.ca\.crt}')
KEY_FILE_VALUE=$(cat ${KEY_FILE} | base64 -w 0)
kubectl get secret allocator-tls-ca -o json -n agones-system | jq '.data["tls-ca.crt"]="'${TLS_CA_VALUE}'"' | kubectl apply -f -
kubectl get secret allocator-client-ca -o json -n agones-system | jq '.data["client_trial.crt"]="'${CERT_FILE_VALUE}'"' | kubectl apply -f -
SECRET_JSON="{\"Ip\":\"${EXTERNAL_IP}\",\"ClientKey\":\"${KEY_FILE_VALUE}\",\"ClientCert\":\"${CERT_FILE_VALUE}\",\"TlsCert\":\"${TLS_CA_VALUE}\"}"
echo ${SECRET_JSON} | gcloud secrets create agones-allocator-info --data-file=- --replication-policy user-managed --locations ${REGION}
これで準備が整ったので.NET Core3.1
で作成したDirector
のコードから呼び出せるようにします。
Directorの準備
AllocatorService
はgRPC
またはHTTP
で呼び出すことができますが、gRPC
で実装するのがお勧めなので、今回はgRPC
で進めます。
事前にnuget
で以下のパッケージをプロジェクト
にインストールします。
- Google.Api.CommonProtos
- Google.Cloud.SecretManager.V1
- Google.Protobuf
- Grpc
- Grpc.Core
- Grpc.Net.Client
- Grpc.Tools
- Newtonsoft.Json
.csproj
ファイル的には以下の形になります(実装時点)
<ItemGroup>
<PackageReference Include="Google.Api.CommonProtos" Version="2.2.0" />
<PackageReference Include="Google.Cloud.SecretManager.V1" Version="1.2.0" />
<PackageReference Include="Google.Protobuf" Version="3.14.0" />
<PackageReference Include="Grpc" Version="2.33.1" />
<PackageReference Include="Grpc.Core" Version="2.33.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.33.1" />
<PackageReference Include="Grpc.Tools" Version="2.33.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
またDirector
のコード内からGrpcサービス
を呼び出せるようにするため、AgonesとOpenMatchのリポジトリからproto
ファイルを取得しProtos
ディレクトリにコピーします。
※何か他にいい方法知ってる方いましたら教えて下さい
$ go get -u github.com/GoogleCloudPlatform/open-match
$ go get -u github.com/GoogleCloudPlatform/agones
$ mkdir -p Protos/{api,proto,google/protobuf}
$ cp $GOPATH/src/github.com/GoogleCloudPlatform/open-match/api/backend.proto Protos/api/
$ cp $GOPATH/src/github.com/GoogleCloudPlatform/open-match/api/messages.proto Protos/api/
$ cp -r $GOPATH/src/github.com/GoogleCloudPlatform/agones/proto/allocation Protos/proto/
$ cp -r $GOPATH/src/github.com/GoogleCloudPlatform/open-match/third_party/google Protos/
$ cp -r $GOPATH/src/github.com/GoogleCloudPlatform/open-match/third_party/protoc-gen-swagger Protos/
$ cp -r $GOPATH/src/github.com/protocolbuffers/protobuf/src/google/protobuf Protos/google/
$ cp $GOPATH/src/github.com/GoogleCloudPlatform/agones/vendor/github.com/golang/protobuf/ptypes/any/any.proto Protos/google/protobuf/
$ cp $GOPATH/src/github.com/GoogleCloudPlatform/agones/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.proto Protos/google/protobuf/
コピーしたら自動生成できるように.csproj
にItemGroup
タグを追加して、その中にProtobuf
タグで必要なproto
ファイルをインポート
するように設定します。
<ItemGroup>
<Protobuf Include="Protos/protoc-gen-swagger/options/annotations.proto" GrpcServices="None" AdditionalImportDirs="./Protos/" />
<Protobuf Include="Protos/protoc-gen-swagger/options/openapiv2.proto" GrpcServices="None" AdditionalImportDirs="./Protos/" />
<Protobuf Include="Protos/api/messages.proto" GrpcServices="None" AdditionalImportDirs="./Protos/" />
<Protobuf Include="Protos/api/backend.proto" GrpcServices="Client" AdditionalImportDirs="./Protos/" />
<Protobuf Include="Protos/proto/allocation/allocation.proto" GrpcServices="Client" AdditionalImportDirs="./Protos/" />
</ItemGroup>
編集が終わったらあとはdotnet build
してGrpc
のコードを生成して使用できるようにします。
DirectorからAllocatorServiceを呼び出す
まずSecretManager
から受け取るJsonデータの受け皿となるクラスを用意します。
using System;
using System.Text;
namespace MatchDirector.Domain.Models
{
public class AgonesAllocatorInfo
{
public string Ip { get; set; }
public string ClientKey { get; set; }
public string ClientCert { get; set; }
public string TlsCert { get; set; }
public string RawClientKey()
{
return FromBase64String(ClientKey);
}
public string RawClientCert()
{
return FromBase64String(ClientCert);
}
public string RawTlsCert()
{
return FromBase64String(TlsCert);
}
private string FromBase64String(string base64String)
{
return new UTF8Encoding().GetString(Convert.FromBase64String(base64String));
}
}
}
次にProgram.cs
で実際にAllocatorService
を呼び出します。
using static Allocation.AllocationService;
using static OpenMatch.BackendService;
using Allocation;
using Google.Api.Gax.ResourceNames;
using Google.Cloud.SecretManager.V1;
using Google.Protobuf;
using Grpc.Core;
using Grpc.Net.Client;
using MatchDirector.Domain.Models;
using Newtonsoft.Json;
using OpenMatch;
namespace MatchDirector
{
class Program
{
public static async Task Main(string[] args)
{
// SecretManagerから証明書情報取得してDeserializeしておく
SecretManagerServiceClient client = SecretManagerServiceClient.Create();
var json = client.AccessSecretVersion(new SecretVersionName(ProjectId, "agones-allocator-info", "1")).Payload.Data.ToStringUtf8();
var agonesAllocatorInfo = JsonConvert.DeserializeObject<AgonesAllocatorInfo>(json);
// MatchFunctionを実行してマッチング情報を取得する
.........
// 取得した証明書を使用してAllocatorServiceを呼び出す
var creds = new SslCredentials(agonesAllocatorInfo.RawTlsCert(), new KeyCertificatePair(agonesAllocatorInfo.RawClientCert(), agonesAllocatorInfo.RawClientKey()));
var channel = new Channel($"{agonesAllocatorInfo.Ip}:443", creds);
var client = new AllocationService.AllocationServiceClient(channel);
var allocationResponse = await client.AllocateAsync(new AllocationRequest {
Namespace = "default",
MultiClusterSetting = new Allocation.MultiClusterSetting { Enabled = false }
});
// あとはAssignGroupにallocationResponseにあるIPとPortを設定してチケットをアサインする
.........
}
}
}
これでAgonesとOpenMatchを連携する事ができます。
あとは実際にプロジェクトをコンテナ化して、Kuberetes
上のPod
に反映することで、連携の確認ができます。
実際に試せるコードをリポジトリにあげておきましたのでご参考までに見て頂ければと思います。
nagodon/agones-open-match-demo
あくまでサンプルなので運用で使うことはしないようにお願い致します。
また今回の記事は結構抜粋してるためリポジトリの内容と異なっていますのでご注意ください。
最後に
一部抜粋してのご紹介となりましたが、.NET Core3.1
を使ったAgonesとOpenMatchの連携についてでした。
AgonesもOpenMatchも基本go
前提と思われますが、他の言語でもMatchFunction
、Director
の実装はできるのでプロジェクトに合わせてこの辺は判断するとよいかなとお思います。
ただやはりgoで書いた方が早く書けると思います。
余談
僕自身はgo
は書いてなかったのでgo
のサンプルをC#
に置き換えるのにはちょっと苦労しました