専用ゲームサーバー: GameLift と Agones によるオーケストレーション
すべての対戦型マルチプレイヤー ゲームは、目に見えないが重要なインフラストラクチャである専用のゲーム サーバーによってサポートされています。 プレーヤーの 1 人がホストとして機能するピアツーピア アーキテクチャとは異なります (公平性の問題はすべてあります) およびその結果として生じる不正行為)、専用サーバーと、信頼できるシミュレーションを実行する中立的なマシン。 ゲームでは、すべてのプレイヤーから入力を受け取り、更新された状態を各プレイヤーに配布します。
数万台のサーバーを効率的に管理し、需要に応じて動的に拡張します コストを最小限に抑えることは、現代のゲーム バックエンドにおける最も複雑なエンジニアリングの課題の 1 つです。この中で この記事では、ゲーム サーバー オーケストレーションのための 2 つの主要なソリューションについて説明します。 AWS ゲームリフト、 Amazon のマネージド サービス、e アゴネス、Kubernetes 上で開発されたオープンソース フレームワーク Googleとユービーアイソフトから。 2 つのどちらかを選択する方法、構成方法、システムと統合する方法について説明します。 スケーラブルでコスト効率の高いゲーム インフラストラクチャを作成するためのマッチメイキングの機能。
何を学ぶか
- 専用ゲームサーバーのアーキテクチャとライフサイクル
- AWS GameLift: フリート管理、セッション、FlexMatch との統合
- Kubernetes 上の Agones: GameServer CRD、フリート、自動スケーリング
- FleetIQ アダプター: スポット インスタンスでコストを最大 90% 節約
- Global Accelerator を使用したマルチリージョン展開パターン
- 高可用性ゲームサーバーの監視とヘルスチェック
専用ゲームサーバーのアーキテクチャ
オーケストレーション ツールを使用する前に、ゲーム サーバーが正確に何を行うのかを理解することが重要です。 専用のものとそのライフサイクルを管理する方法。一般的な専用サーバーは次の状態を経ます。
| Stato | 説明 | 通常の期間 |
|---|---|---|
| 起動 | プロセスが開始され、アセットがロードされ、オーケストレーション システムに登録されます。 | 2~10秒 |
| 準備ができて | 接続を受け入れる準備ができており、セッションへの割り当てを待機しています | 変数 |
| 割り当て済み | ゲームに割り当てられ、指定されたプレーヤーを受け入れます | 試合時間 |
| 予約済み | 今後のセッションのために予約されており、他の人は利用できません | 秒-分 |
| シャットダウン | ゲームオーバー、プロセスが終了し、リソースが解放されます | 2~5秒 |
オーケストレーション システムは、各サーバーのステータスを追跡し、利用可能なサーバー全体にプレーヤーを分散する必要があります。 障害を管理し、負荷に応じてフリートを自動的にスケールします。課題は、これらの操作が それらは数秒で行われなければなりません。一致するものを見つけるために 30 秒も待ちたいプレイヤーはいません。
AWS GameLift: マネージドサービス
AWS GameLift は、サーバー オーケストレーションの複雑さを抽象化するフルマネージド サービスを提供します。 そのアーキテクチャは 3 つの主要なコンポーネントに分かれています。 艦隊 (EC2 インスタンス グループ サーバーを実行する)、 ゲームセッション (サーバー上で実行されているゲーム インスタンス) と私 プレイヤーセッション (スロットはセッション内の各プレーヤーに予約されています)。
// Integrazione GameLift SDK nel game server (Node.js)
import { GameLift } from 'aws-sdk';
import * as GameLiftServerSDK from 'gamelift-server-sdk';
class GameLiftIntegration {
private sdk = GameLiftServerSDK;
async initialize(): Promise<void> {
// Inizializza la connessione con il servizio GameLift
const initResult = await this.sdk.InitSDK();
if (!initResult.Success) {
throw new Error(`GameLift SDK init failed: ${initResult.Error}`);
}
// Registra i callback per gli eventi del lifecycle
this.sdk.ProcessReady({
onStartGameSession: this.handleStartGameSession.bind(this),
onUpdateGameSession: this.handleUpdateGameSession.bind(this),
onProcessTerminate: this.handleProcessTerminate.bind(this),
onHealthCheck: () => true, // Game server e in salute
port: 7777,
logParameters: { logPaths: ['/local/game/logs/'] }
});
console.log('GameLift: Server pronto, in attesa di sessioni');
}
private async handleStartGameSession(gameSession: GameSession): Promise<void> {
console.log(`Nuova sessione: ${gameSession.GameSessionId}`);
// Inizializza la logica di gioco
await this.initializeGameLogic(gameSession);
// Notifica GameLift che la sessione e attiva
this.sdk.ActivateGameSession();
}
private async handleProcessTerminate(): Promise<void> {
// Salva lo stato, disconnetti i giocatori
await this.saveGameState();
this.sdk.ProcessEnding();
process.exit(0);
}
// Aggiunge un giocatore alla sessione
async acceptPlayer(playerSessionId: string): Promise<void> {
const result = await this.sdk.AcceptPlayerSession(playerSessionId);
if (!result.Success) {
throw new Error(`Player non accettato: ${result.Error}`);
}
}
// Rimuove un giocatore alla sessione
async removePlayer(playerSessionId: string): Promise<void> {
await this.sdk.RemovePlayerSession(playerSessionId);
}
}
クライアントは、GameLift Client SDK または REST API を介してゲーム セッションをリクエストする必要があります。その方法は次のとおりです 新しいセッションを開始するか、既存のセッションに参加するためのクライアント側ロジックを構造化します。
// Client: creazione sessione di gioco (TypeScript)
import AWS from 'aws-sdk';
const gamelift = new AWS.GameLift({ region: 'eu-west-1' });
interface MatchResult {
serverIp: string;
serverPort: number;
playerSessionId: string;
}
async function joinGame(playerId: string, fleetId: string): Promise<MatchResult> {
// Cerca sessioni con slot disponibili
const searchResult = await gamelift.searchGameSessions({
FleetId: fleetId,
FilterExpression: 'hasAvailablePlayerSessions=true',
SortExpression: 'creationTimeMillis ASC',
Limit: 1
}).promise();
let gameSessionId: string;
if (searchResult.GameSessions?.length) {
// Unisciti a una sessione esistente
gameSessionId = searchResult.GameSessions[0].GameSessionId!;
} else {
// Crea una nuova sessione
const newSession = await gamelift.createGameSession({
FleetId: fleetId,
MaximumPlayerSessionCount: 10,
Name: `session-${Date.now()}`,
GameProperties: [
{ Key: 'map', Value: 'arena_01' },
{ Key: 'mode', Value: 'deathmatch' }
]
}).promise();
gameSessionId = newSession.GameSession!.GameSessionId!;
}
// Crea una player session per questo giocatore
const playerSession = await gamelift.createPlayerSession({
GameSessionId: gameSessionId,
PlayerId: playerId
}).promise();
return {
serverIp: playerSession.PlayerSession!.IpAddress!,
serverPort: playerSession.PlayerSession!.Port!,
playerSessionId: playerSession.PlayerSession!.PlayerSessionId!
};
}
GameLift FlexMatch: 統合されたマッチメイキング
GameLift には以下が含まれます フレックスマッチ、ルールを定義できる柔軟なマッチメイキング エンジン 試合の構成は複雑です。自動埋め戻しをサポート (試合中に空席を埋める)、 チームのバランス、地理的フィルター、スキル要件。
// FlexMatch rule set - definisce le regole di matchmaking
const flexMatchRuleSet = {
"name": "competitive-5v5",
"ruleLanguageVersion": "1.0",
"playerAttributes": [
{
"name": "skill",
"type": "number",
"default": 1000
},
{
"name": "latency",
"type": "latencyMilliseconds"
}
],
"teams": [
{
"name": "team1",
"minPlayers": 5,
"maxPlayers": 5
},
{
"name": "team2",
"minPlayers": 5,
"maxPlayers": 5
}
],
"rules": [
{
"name": "FairTeamSkill",
"description": "Differenza skill media tra team < 150",
"type": "distance",
"measurements": [
"teams[team1].players.attributes[skill]",
"teams[team2].players.attributes[skill]"
],
"referenceValue": 150,
"maxDistance": 150
},
{
"name": "FastConnection",
"description": "Latenza max 100ms",
"type": "latency",
"maxLatency": 100
}
],
"expansions": [
{
"target": "rules[FairTeamSkill].maxDistance",
"steps": [
{ "waitTimeSeconds": 30, "value": 250 },
{ "waitTimeSeconds": 60, "value": 500 }
]
}
]
};
Agones: Kubernetes 上のゲームサーバー
アゴネス オープンソース フレームワーク。当初は Google と Ubisoft によって開発され、現在は保守されています。
ゲーム専用に設計されたカスタム リソース定義 (CRD) で Kubernetes を拡張するコミュニティからの提供
サーバー。 Agones は 3 つの新しい Kubernetes オブジェクトを導入しました。 GameServer, Fleet e
GameServerAllocation.
GameLift に対する Agones の主な利点は柔軟性です。どのクラスターにもデプロイできます。 Kubernetes (GKE、EKS、AKS、オンプレミス) を使用すると、インフラストラクチャを完全に制御できます。欠点は、 GameLift が自動的に処理する多くの側面を自分で処理する必要があります。
# Agones GameServer - manifest Kubernetes
apiVersion: agones.dev/v1
kind: GameServer
metadata:
name: my-game-server
labels:
game: "shooter"
region: "eu-west"
spec:
ports:
- name: default
portPolicy: Dynamic # Kubernetes assegna la porta dinamicamente
containerPort: 7777
protocol: UDP
health:
initialDelaySeconds: 30
periodSeconds: 5
failureThreshold: 3
sdkServer:
logLevel: Info
grpcPort: 9357
httpPort: 9358
template:
spec:
containers:
- name: game-server
image: gcr.io/myproject/game-server:v1.2.0
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
env:
- name: GAME_MODE
value: "deathmatch"
- name: MAX_PLAYERS
value: "16"
# Agones Fleet - gestisce un pool di GameServer
apiVersion: agones.dev/v1
kind: Fleet
metadata:
name: shooter-fleet
spec:
replicas: 10 # Mantieni sempre 10 server pronti
scheduling: Packed # Impacchetta i server sullo stesso nodo
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
template:
metadata:
labels:
game: "shooter"
spec:
ports:
- name: default
portPolicy: Dynamic
containerPort: 7777
protocol: UDP
template:
spec:
containers:
- name: game-server
image: gcr.io/myproject/game-server:v1.2.0
resources:
requests:
memory: "512Mi"
cpu: "500m"
---
# FleetAutoscaler - scala automaticamente la Fleet
apiVersion: autoscaling.agones.dev/v1
kind: FleetAutoscaler
metadata:
name: shooter-fleet-autoscaler
spec:
fleetName: shooter-fleet
policy:
type: Buffer
buffer:
bufferSize: 5 # Mantieni almeno 5 server READY
minReplicas: 5
maxReplicas: 100
sync:
type: FixedInterval
fixedInterval:
seconds: 30
ゲームサーバーへの Agones SDK の統合
Agones は、Go、C++、Rust、Node.js、およびその他の言語用の SDK を提供します。ゲームサーバーは通信する必要があります Agones サイドカーを使用してステータスを報告し、ライフサイクル通知を受け取ります。
// Agones SDK integration - Node.js
import AgonesSDK from '@google-cloud/agones-sdk';
class AgonesGameServer {
private sdk: AgonesSDK;
private healthInterval: NodeJS.Timer | null = null;
constructor() {
this.sdk = new AgonesSDK();
}
async start(): Promise<void> {
// Connetti al sidecar Agones
await this.sdk.connect();
console.log('Connesso ad Agones');
// Ascolta le notifiche di allocazione
this.sdk.watchGameServer((gameServer) => {
console.log('Stato server aggiornato:', gameServer.status.state);
if (gameServer.status.state === 'Allocated') {
this.handleAllocation(gameServer);
}
});
// Invia health check ogni 5 secondi
this.healthInterval = setInterval(async () => {
try {
await this.sdk.health();
} catch (err) {
console.error('Health check fallito:', err);
}
}, 5000);
// Segnala che il server e pronto
await this.sdk.ready();
console.log('Server in stato READY');
}
private async handleAllocation(gameServer: any): Promise<void> {
const labels = gameServer.objectMeta.labels;
const sessionId = labels['session-id'] || 'unknown';
console.log(`Server allocato per sessione: ${sessionId}`);
// Inizializza la logica di gioco con i parametri dell'allocazione
const annotations = gameServer.objectMeta.annotations;
await this.initGame({
sessionId,
maxPlayers: parseInt(annotations['max-players'] || '10'),
mapId: annotations['map-id'] || 'default'
});
}
async shutdown(): Promise<void> {
if (this.healthInterval) {
clearInterval(this.healthInterval);
}
await this.saveGameResults();
await this.sdk.shutdown(); // Segnala ad Agones che il server sta terminando
}
// Allocazione via API
static async allocateServer(namespace: string): Promise<AllocationResult> {
const response = await fetch(`http://agones-allocator.${namespace}:8443/gameserverallocation`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apiVersion: 'allocation.agones.dev/v1',
kind: 'GameServerAllocation',
spec: {
selectors: [
{ matchLabels: { 'agones.dev/fleet': 'shooter-fleet' } }
],
metadata: {
labels: { 'session-id': `session-${Date.now()}` },
annotations: {
'max-players': '10',
'map-id': 'arena_01'
}
}
}
})
});
return response.json();
}
}
FleetIQ アダプター: スポット インスタンスによるコスト削減
ゲームサーバーホスティングにおける最大のコストの 1 つはコンピューティングです。 AWS が開発したのは、 Agones 用 GameLift FleetIQ アダプター、これにより、Agones サーバーを実行できるようになります。 EC2 スポットインスタンス、オンデマンド インスタンスと比較して最大 90% 節約されます。
スポットインスタンスのリスクと、AWS がそのキャパシティを必要とするときの突然の停止のリスク。 FleetIQ は、機械学習アルゴリズムを使用して停止を予測し、 フォールバックとしてオンデマンド インスタンスのバッファを常に維持します。
| 構成 | 時間あたりのコスト (c5.xlarge) | 貯蓄 | 中断の危険性 |
|---|---|---|---|
| オンデマンド | $0.192 | - | 0% |
| スポットインスタンス | ~0.025~0.060ドル | 70-87% | 5~15% |
| スポット + フリート IQ | ~0.030~0.070ドル | 63-85% | <1% (ML 予測) |
| グラビトンスポット | ~0.020~0.040ドル | 79-90% | <1% |
グローバル アクセラレータを使用したマルチリージョン アーキテクチャ
世界中のプレイヤー ベースを持つゲームの場合、マルチリージョン アーキテクチャは、 すべてのプレーヤーに低遅延を提供します。 AWS Global Accelerator はトラフィックをリージョンにルーティングします より近くなり、知覚される遅延が減少し、接続の安定性が向上します。
// Architettura multi-region - configurazione Terraform
resource "aws_globalaccelerator_accelerator" "game_accelerator" {
name = "game-global-accelerator"
ip_address_type = "IPV4"
enabled = true
}
resource "aws_globalaccelerator_listener" "game_listener" {
accelerator_arn = aws_globalaccelerator_accelerator.game_accelerator.id
client_affinity = "SOURCE_IP" # Affinita per sessioni di gioco
protocol = "UDP"
port_range {
from_port = 7000
to_port = 8000
}
}
# Endpoint group per EU-West-1
resource "aws_globalaccelerator_endpoint_group" "eu_west" {
listener_arn = aws_globalaccelerator_listener.game_listener.id
endpoint_group_region = "eu-west-1"
traffic_dial_percentage = 100
health_check_port = 8080
health_check_protocol = "HTTP"
health_check_path = "/health"
health_check_interval_seconds = 10
threshold_count = 3
endpoint_configuration {
endpoint_id = aws_lb.eu_game_lb.arn
weight = 100
}
}
# Endpoint group per AP-Southeast-1
resource "aws_globalaccelerator_endpoint_group" "ap_southeast" {
listener_arn = aws_globalaccelerator_listener.game_listener.id
endpoint_group_region = "ap-southeast-1"
traffic_dial_percentage = 100
endpoint_configuration {
endpoint_id = aws_lb.ap_game_lb.arn
weight = 100
}
}
比較: GameLift と Agones
| 基準 | AWS ゲームリフト | Kubernetes 上の Agones |
|---|---|---|
| 初期設定 | 高速、フルマネージド | 複雑で、K8 の専門知識が必要 |
| ベンダーロックイン | 高 (AWS のみ) | 低 (マルチクラウド) |
| 固定費 | セッション + インスタンスあたりの料金 | 計算されたコストのみ |
| マッチメイキング | 統合された FlexMatch | 別途オープンマッチが必要 |
| スケーリング | 自動、管理 | 手動 + フリートオートスケーラー |
| マルチクラウド | No | はい (GKE、EKS、AKS) |
| UDPプロトコル | Si | Si |
| スポットインスタンス | FleetIQ の統合 | K8s スポット + FleetIQ アダプター |
GameLift を選択する場合
GameLift は、すでに AWS エコシステムに深く統合されており、チームが小規模である場合に最適です。 Kubernetes の専門知識がなく、稼働時間を最小限に抑えたいと考えています。追加費用は、 マネージド サービスであり、多くの場合、本番環境で Kubernetes を管理するために必要な人件費よりも安価です。
アゴネスを選択する場合
Agones は、Kubernetes の専門知識を持つチーム、複数のクラウド プロバイダーで実行する必要があるゲーム、 またはインフラストラクチャを最大限に制御したい場合。すでに持っている場合にも正しい選択です Kubernetes インフラストラクチャが確立されており、それをゲーム サーバーに活用したいと考えています。
ヘルスチェックとモニタリング
劣化したゲームサーバーを早期に検出するには、堅牢なヘルスチェックシステムが不可欠です それはプレイヤーに影響を与えます。 GameLift と Agones は両方とも定期的なヘルスチェックをサポートしていますが、 カスタム アプリケーション メトリクスを実装することが重要です。
// Health check endpoint per game server - Express.js
import express from 'express';
import { GameMetrics } from './metrics';
const app = express();
const metrics = new GameMetrics();
app.get('/health', (req, res) => {
const health = {
status: 'ok',
uptime: process.uptime(),
memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
activePlayers: metrics.getActivePlayers(),
tickRate: metrics.getCurrentTickRate(),
avgLatencyMs: metrics.getAverageLatency()
};
// Considera degraded se tickrate scende sotto il 80% del target
if (health.tickRate < metrics.targetTickRate * 0.8) {
health.status = 'degraded';
res.status(503).json(health);
return;
}
res.json(health);
});
app.get('/metrics', async (req, res) => {
// Prometheus-compatible metrics
res.set('Content-Type', 'text/plain');
res.send(await metrics.toPrometheusFormat());
});
app.listen(8080, () => {
console.log('Health endpoint attivo su :8080');
});
ベストプラクティスとアンチパターン
ベストプラクティス
- フリートを事前にウォームアップします。 ピーク時のコールド スタートを避けるために、常に READY サーバー バッファを維持してください。
- フォールバックを使用したスポット インスタンス: スポットを使用してコストを削減しますが、フォールバックとして 10 ~ 20% のオンデマンドを維持します
- 正常なシャットダウン: シャットダウンする前に、サーバーが現在のゲームを完了するまで 30 ~ 60 秒与えます。
- 永続的なログ: シャットダウン前にログを S3 または集中システムに書き込む
- イメージのバージョン管理: 不変タグを使用します (決して使用しないでください)
latest) 再現可能な展開用 - 地域的な親和性: レイテンシーが最も低いリージョン内のサーバーにプレーヤーを割り当てます。
避けるべきアンチパターン
- サーバー上のグローバルステータス: 各ゲーム サーバーはインフラストラクチャに関してステートレスである必要があります。ゲームの状態はセッションに対してローカルです
- フリートが大きすぎる、または小さすぎる: フリートの規模が小さいと待ち時間が発生します。粗大無駄金
- あまりにも積極的なヘルスチェック: リソースを消費していることを毎秒確認します。通常は 5 ~ 10 秒で十分です
- 排水期間なし: ドレイン期間なしでサーバーを強制終了すると、進行中のゲームが突然終了します
結論
専用ゲームサーバーのオーケストレーションは、次のような微妙なバランスを必要とする分野です。 コスト、遅延、信頼性。 AWS GameLift は複雑さを軽減するマネージドソリューションを提供します Agones は、マルチクラウド アーキテクチャやチームに必要な柔軟性を提供します。 Kubernetes の強力な専門知識。 Agones と FleetIQ アダプターの組み合わせは、おそらく次のことを表します。 ほとんどの AAA ゲームにとって最良のトレードオフ: オープンソースの柔軟性と節約 スポットインスタンスのコスト。
シリーズの次の記事では、システムを構築する方法について説明します。 洗練されたマッチメイキング ELO および Glicko-2 アルゴリズムを使用し、スキルベースのマッチメイキングとオーケストレーション メカニズムを統合します。 今日私たちが調べたもの。
ゲーム バックエンド シリーズの今後の記事
- 第03条:ELOとGlicko-2によるマッチメイキングシステム
- 記事 04: リアルタイムの状態同期とネットコードのロールバック
- 第05条: サーバー側のアンチチートアーキテクチャ







