AgonesとOpenMatchとK8s
この記事はUnreal Engine (UE) Advent Calendar 2025シリーズ 1 22日目の記事です。 こんにちはみずあめです。現在2025/12/21 17:25 、公開まであと6時間です!やばい! 19:14に書き終わりました~
初めに
まずこの記事の読者の方は基本UnrealEngine界隈の方だと思い、急にK8sがぁ~とかAgonesがぁ~とか話しても、オフチョベットしたテフをマブガッドしてリット状態になると思うので頑張って解説します。ぜひ最後まで読んで下さい。
実際のプレイ映像: https://x.com/mizuameisgod/status/1990358140152676466
Dockerとは
インチキなことを言うと炎上するので、筑波大学プログラム言語処理の授業を引用します。 https://kdb.tsukuba.ac.jp/syllabi/2025/GB30504/jpn
仮想化技術は,物理的なハードウェアリソース(CPU,メモリ,ストレー ジ)を仮想マシンとして抽象化し,複数の環境を実行するための技術. 仮想マシンはホストとなる物理マシン上で動作し,独立したオペレーティ ングシステムやアプリケーションを実行するための論理環境を提供する
その中でもDockerは コンテナ型に該当します。
アプリケーションとその依存関係を一つのパッケージとしてまとめ,ホストOS上で 独立して実行する技術.コンテナはホストOSのカーネルを共有するため,仮想マシ ンよりも軽量で高速に動作する.
コンテナ型はホストOSのカーネルを共有しており,ハードウェアへ直接 アクセスするのはホストOSのみ.プロセスレベルの隔離が行われており, 一つのコンテナ上のアプリケーションとその依存関係は独立している.
今回の場合は、UnrealEngineで書き出されたLinux向けバイナリを安全に、環境の差分を吸収してもらい、安定的に実行するために使用します。
K8sとは
正式名称はKubernetesです。上記のDockerをはじめとするコンテナをオーケストレーションするオープンソースシステムです。 語弊を恐れずに言うと、コンテナをいい感じにスケジューリングしたり、負荷分散したり、など皆さんが普段使っているクラウドの根幹にある技術です。クラウドは誰かのオンプレ。
Agonesとは
今回の場合はUnrealEngineのDedicated Serverを直接管理しているもの。サーバーのライフサイクルなどを管轄します。 Googleなどが主にやってるOSSです。
OpenMatchとは
名前の通り、マッチメイキングを主に行います。クライアントとAgonesの橋渡しを実質的にやっています。(Open Match がマッチ結果を AgonesAllocator に渡し、サーバーを割り当てさせる) Googleなどが主にやってるOSSです。
オーバービュー

そして、残念なことにOpenMatchはすべての機能を提供しておらず、CustomComponentとして、director,evaluator,match function,geme frontend辺りは自分で書く必要があります。(PythonかGoになります)
-
Game Frontend ゲームクライアント向けのHTTP APIサーバー プレイヤーからのマッチングリクエストを受け付ける Open Match Frontend Serviceにチケットを作成 アサインメント(サーバー割り当て)を待機して結果を返す JWT認証トークンを発行してゲームサーバー接続に使用
-
Match Function マッチの提案を作成するカスタムロジック Query Serviceから待機中のチケットを取得 マッチングアルゴリズムを実行 マッチ提案をBackendに返す
-
Evaluator Match Functionからの提案を評価・選別する
-
Director マッチメイキングのオーケストレーター Backendからマッチ結果を取得 Agones/KubernetesでGameServerを割り当て マッチしたプレイヤーにサーバー接続情報をアサイン
そしてまともに動くサンプルが見当たらなかったので、全て簡易実装しました。 https://github.com/mizuamedesu/easy-open-match
つまりどういうことだってばよ
つまり、一回サーバー側の動きは抽象化するとして、クライアント的な視点だと以下の流れになります
- game frontendにマッチメイキングリクエストのhttpリクエストなどを送る
- マッチメイキング完了時、サーバー側はdedicated serverのIP+PortとJWTを渡す
- クライアントはOpenLevelで開く
- dedicated serverはクライアントが接続してきたとき、RPCでトークンのexchangeを要請する
- dedicated serverはもらったJWTをgame frontendのwell-known jsonを元に検証し、確認が取れたらフラグを立て、失敗orタイムアウトした場合はクライアントを削除する

キック処理の実装
static void KickPlayerInternal(APlayerController* PlayerController, const FText& Reason)
{
if (!PlayerController) return;
UWorld* World = PlayerController->GetWorld();
if (!World) return;
AGameModeBase* GameMode = World->GetAuthGameMode();
if (!GameMode) return;
AGameSession* GameSession = GameMode->GameSession;
if (!GameSession) return;
GameSession->KickPlayer(PlayerController, Reason);
}
何故かBlueprintにキック関数が公開されていないので、良しなに実装する必要があります。
AGameSession::KickPlayer()を呼ぶだけです。
又、PlayerControllerにもキック時のイベントを受け取るために良しなに実装が必要です。
void ABattlePlayerController::ClientWasKicked_Implementation(const FText& KickReason)
{
Super::ClientWasKicked_Implementation(KickReason);
// キック理由を保存
LastKickReason = KickReason;
bWasKicked = true;
// ログ出力
UE_LOG(LogBattlePlayerController, Warning, TEXT("Client was kicked from server. Reason: %s"), *KickReason.ToString());
// 画面にデバッグメッセージ表示
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red,
FString::Printf(TEXT("Kicked: %s"), *KickReason.ToString()));
}
// Blueprintイベントを呼び出し
OnKickedFromServer(KickReason);
ReceiveKickedFromServer(KickReason);
}
これを継承したBPのクラスを作成しました。
ゲームモードの実装

まずEvent OnPostLoginをオーバーライドし、PlayerControllerを先ほど作成したカスタムクラスにcastし、RPCを送ります。流れとしてサーバー→クライアントです。

RPCを受け取ったクライアントはサーバーへ、JWTを送るRPCをサーバーに呼びます。


RPCを受け取ったサーバーはwell-known jsonな公開鍵を取得し、検証が通れば完了フラグを立て、失敗の場合はキックをします。

クライアントが何らかの理由でJWTのexchangeに返答しない場合は、一定時間でキックをします。
UEの実行コンテナイメージ
https://github.com/mizuamedesu/ue-server-env
#!/bin/bash
set -e
echo "Starting UE server setup..."
if [ ! -d "/game/Engine" ] || [ ! -d "/game/${PROJECT_DIR}" ]; then
echo "ERROR: Required game files are not mounted. Please mount the game directory to /game."
exit 1
fi
if [ ! -f "/game/${PROJECT_DIR}/${SERVER_SCRIPT}" ]; then
echo "ERROR: Server start script (${SERVER_SCRIPT}) not found in /game/${PROJECT_DIR}/"
exit 1
fi
# スクリプトに実行権限を付与
if [ -w "/game/${PROJECT_DIR}/${SERVER_SCRIPT}" ]; then
chmod +x "/game/${PROJECT_DIR}/${SERVER_SCRIPT}" || true
fi
if [ ! -d "$SAVED_PATH" ]; then
echo "Creating saves directory at $SAVED_PATH"
mkdir -p "$SAVED_PATH"
fi
# 環境変数を使ってゲーム設定を修正(必要に応じて)
CONFIG_FILE="/game/${PROJECT_DIR}/Config/DefaultGame.ini"
if [ -f "$CONFIG_FILE" ]; then
if grep -q "ServerName=" "$CONFIG_FILE"; then
sed -i "s/ServerName=.*/ServerName=${SERVER_NAME}/" "$CONFIG_FILE"
fi
if grep -q "MaxPlayers=" "$CONFIG_FILE"; then
sed -i "s/MaxPlayers=.*/MaxPlayers=${MAX_PLAYERS}/" "$CONFIG_FILE"
fi
echo "Updated server configuration with environment variables"
fi
echo "Starting UE dedicated server..."
echo "Server Name: $SERVER_NAME"
echo "Max Players: $MAX_PLAYERS"
echo "Game Port: $SERVER_PORT"
echo "Query Port: $QUERY_PORT"
# 起動オプションを構築
LAUNCH_OPTIONS="-log -port=${SERVER_PORT} -queryport=${QUERY_PORT} -NetServerMaxTickRate=${NET_SERVER_MAX_TICK_RATE:-100}"
# RCONが有効な場合
if [ "$RCON_ENABLED" = "true" ]; then
LAUNCH_OPTIONS="$LAUNCH_OPTIONS -rconport=${RCON_PORT}"
echo "RCON Port: $RCON_PORT"
fi
# 追加の起動オプションがあれば追加
if [ ! -z "$ADDITIONAL_ARGS" ]; then
LAUNCH_OPTIONS="$LAUNCH_OPTIONS $ADDITIONAL_ARGS"
fi
# サーバー起動
cd "/game/${PROJECT_DIR}"
echo "Executing: ./${SERVER_SCRIPT} ${LAUNCH_OPTIONS}"
exec "./${SERVER_SCRIPT}" $LAUNCH_OPTIONS
UEで書き出した際の形に合わせ、ラップするシェルスクリプトを作成しました。バイナリのあるディレクトリをマウントすると起動するDockerコンテナイメージです。
AgonesFleet
apiVersion: "agones.dev/v1"
kind: Fleet
metadata:
name: ue5-gameserver-fleet
namespace: game
spec:
replicas: 5
template:
metadata:
labels:
app: ue5-server
spec:
ports:
- name: game
containerPort: 7777
protocol: UDP
portPolicy: Dynamic
- name: query
containerPort: 27015
protocol: UDP
portPolicy: Dynamic
health:
disabled: false
initialDelaySeconds: 30
periodSeconds: 15
eviction:
safe: Always
template:
metadata:
labels:
app: ue5-server
spec:
runtimeClassName: kata
nodeSelector:
hardware: game-runner
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: ue5-server
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: gameserver
image: ghcr.io/mizuamedesu/ue-server-env:latest
imagePullPolicy: Always
env:
- name: PROJECT_DIR
value: ""
- name: SERVER_SCRIPT
value: "DOMINATIONServer.sh"
- name: SERVER_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MAX_PLAYERS
value: "16"
- name: SERVER_PORT
value: "7777"
- name: QUERY_PORT
value: "27015"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: false
resources:
requests:
memory: "16Gi"
cpu: "2000m"
limits:
memory: "16Gi"
cpu: "20000m"
volumeMounts:
- name: game-files
mountPath: /game
subPath: LinuxServer
readOnly: false #非常によろしくないので、一旦falseにしているが、後で直すこと
volumes:
- name: game-files
persistentVolumeClaim:
claimName: ue5-game-files-s3-pvc
ここで勘のいい方なら気づくと思いますが、先ほどのUEのコンテナはPort:7777 で起動するため、これでは1インスタンスで1IPアドレスを使ってしまうのでは?と感じるかもしれません。 結論から言うとそんなことはなく、Agones側が自動でNATをしてくれるため、特に意識することなく、IP+Port(7000~8000)が返りそのまま接続できます。
また今回はruntimeClassにkataを使用していますが、普通はruncでいいと思います。
各種yamlは以下のリポジトリにあります。
https://github.com/mizuamedesu/ue-k8s
特に今回の面白ポイントはPersistentVolumeにCloudflare R2を使っています。
Agones SDKの導入
https://agones.dev/site/docs/guides/client-sdks/unreal/ https://github.com/googleforgames/agones/tree/release-1.54.0/sdks/unreal
Agonesはハートビートを行うため、最低限プラグインを有効化しておかないと死んでると思われて無限に再起動します。
ライフサイクル

Agonesはゲームインスタンスのライフサイクルを管理しているわけですが、主な状態遷移としてReady、Allocated、Shutdownがあります。
- Ready クライアントの接続を待っている状態です。
- Allocated クライアントが1つ以上割り当てられている状態です。
- Shutdown インスタンスの終了中でです。
そして、ShutdownはUEのサーバー側でAPIをコールする必要があります。


ゲームモードのEvent OnLogoutをオーバーライドし、プレイヤーコントローラー数が0になったらSDKのshutdown関数を呼びます。するとAgones側が自動でPodの回収をしてくれ、新しいインスタンスが立ち、Ready状態になります。
最後に
今回の話でもっとK8s側にフォーカスした話をCloudNative Days Winter 2025の学生スカラーシップ枠で登壇しました。(動画は残り9分位からが自分です) https://event.cloudnativedays.jp/cndw2025/talks/2777
又本内容の一部は筑波大学情報特別演習でも行っています。 https://kdb.tsukuba.ac.jp/syllabi/2025/GB13312/jpn
https://mizuame.works/slides/Unreal.k8s%20%E4%B8%AD%E9%96%93%E5%A0%B1%E5%91%8A_20251015/
当方ネットワークとUnrealEngineとセキュリティとクラウドがある程度わかります。お仕事などは https://mizuame.works/ を参照の上ご連絡ください。