전용 게임 서버: GameLift 및 Agones를 사용한 오케스트레이션
모든 경쟁적인 멀티플레이어 게임은 눈에 보이지 않지만 중요한 인프라인 전용 게임 서버의 지원을 받습니다. 플레이어 중 한 명이 호스트 역할을 하는 P2P 아키텍처와 달리(모든 공정성 문제 포함) 그리고 그에 따른 부정 행위), 전용 서버 및 권위 있는 시뮬레이션을 실행하는 중립 머신 게임은 모든 플레이어로부터 입력을 받고 업데이트된 상태를 각 플레이어에게 배포합니다.
수만 대의 서버를 효율적으로 관리하고 수요에 따라 동적으로 확장 비용을 최소화하는 것은 현대 게임 백엔드에서 가장 복잡한 엔지니어링 과제 중 하나입니다. 이에 기사에서는 게임 서버 오케스트레이션을 위한 두 가지 주요 솔루션을 살펴보겠습니다. AWS GameLift, Amazon의 관리형 서비스, e 아고네스, Kubernetes에서 개발된 오픈 소스 프레임워크 구글과 유비소프트에서. 둘 중 하나를 선택하는 방법, 구성하는 방법, 시스템과 통합하는 방법을 살펴보겠습니다. 확장 가능하고 비용 효율적인 게임 인프라를 구축하기 위한 매치메이킹입니다.
무엇을 배울 것인가
- 전용 게임 서버의 아키텍처 및 라이프사이클
- AWS GameLift: 플릿 관리, 세션 및 FlexMatch와의 통합
- Kubernetes의 Agones: GameServer CRD, 플릿 및 자동 확장
- FleetIQ 어댑터: 스팟 인스턴스를 통해 비용을 최대 90% 절감
- Global Accelerator를 사용한 다중 지역 배포 패턴
- 고가용성 게임 서버에 대한 모니터링 및 상태 확인
전용 게임 서버의 아키텍처
오케스트레이션 도구를 시작하기 전에 게임 서버가 정확히 무엇을 하는지 이해하는 것이 중요합니다. 헌신적이며 수명주기를 관리하는 방법. 일반적인 전용 서버는 다음과 같은 상태를 거칩니다.
| 상태 | 설명 | 일반적인 기간 |
|---|---|---|
| 시작 | 프로세스가 시작되고 자산을 로드하며 오케스트레이션 시스템에 등록됩니다. | 2~10초 |
| 준비가 된 | 연결을 수락할 준비가 되어 있으며 세션에 할당되기를 기다리고 있습니다. | 변하기 쉬운 |
| 할당됨 | 게임에 할당되고 지정된 플레이어를 허용합니다. | 경기 시간 |
| 예약된 | 향후 세션을 위해 예약되었으며 다른 사람에게는 제공되지 않습니다. | 초-분 |
| 일시 휴업 | 게임이 끝나면 프로세스가 종료되고 리소스가 해제됩니다. | 2~5초 |
오케스트레이션 시스템은 각 서버의 상태를 추적하고, 사용 가능한 서버에 플레이어를 배포하고, 장애를 관리하고 부하에 따라 자동으로 차량 규모를 조정합니다. 문제는 이러한 작업이 몇 초 안에 이루어져야 합니다. 어떤 플레이어도 일치하는 항목을 찾기 위해 30초를 기다리고 싶어하지 않습니다.
AWS GameLift: 관리형 서비스
AWS GameLift는 서버 오케스트레이션의 복잡성을 추상화하는 완전관리형 서비스를 제공합니다. 아키텍처는 세 가지 주요 구성 요소로 나뉩니다. 함대 (EC2 인스턴스 그룹 서버를 운영하는 서버), 게임 세션 (서버에서 실행되는 게임 인스턴스) 및 i 플레이어 세션 (세션의 각 플레이어를 위해 예약된 슬롯)
// 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, 규칙을 정의할 수 있는 유연한 매치메이킹 엔진 경기 구성이 복잡합니다. 자동 채우기 지원(경기 중 공석 채우기), 팀 밸런싱, 지리적 필터 및 기술 요구 사항.
// 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는 세 가지 새로운 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 어댑터: 스팟 인스턴스를 통한 비용 절감
게임 서버 호스팅에서 가장 큰 비용 중 하나는 컴퓨팅입니다. AWS가 개발한 Agones용 GameLift FleetIQ 어댑터, Agones 서버를 실행할 수 있게 해줍니다. EC2 스팟 인스턴스, 온디맨드 인스턴스에 비해 최대 90% 비용을 절감합니다.
AWS에 해당 용량이 필요할 때 스팟 인스턴스 및 갑작스러운 중단이 발생할 위험이 있습니다. FleetIQ는 기계 학습 알고리즘을 사용하여 정전을 예측하고 대체 수단으로 항상 온디맨드 인스턴스의 버퍼를 유지합니다.
| 구성 | 비용/시간(c5.xlarge) | 저금 | 중단 위험 |
|---|---|---|---|
| 온디맨드 | $0.192 | - | 0% |
| 스팟 인스턴스 | ~$0.025-0.060 | 70-87% | 5-15% |
| 스팟 + FleetIQ | ~$0.030-0.070 | 63-85% | <1%(ML 예측) |
| 중력자 스팟 | ~$0.020-0.040 | 79-90% | <1% |
Global Accelerator를 사용한 다중 지역 아키텍처
글로벌 플레이어 기반을 갖춘 게임의 경우 다중 지역 아키텍처는 다음을 보장하는 데 필수적입니다. 모든 플레이어에게 낮은 대기 시간을 제공합니다. 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 GameLift | Kubernetes의 Agones |
|---|---|---|
| 초기 설정 | 빠르고 완벽하게 관리되는 | 복잡하고 K8의 전문 지식이 필요함 |
| 공급업체 종속 | 높음(AWS 전용) | 낮음(멀티 클라우드) |
| 고정 비용 | 세션당 가격 + 인스턴스 | 계산된 비용만 |
| 결혼 중매 | 통합 FlexMatch | 별도의 오픈 매치 필요 |
| 스케일링 | 자동, 관리형 | 수동 + FleetAutoscaler |
| 멀티 클라우드 | No | 예(GKE, EKS, AKS) |
| UDP 프로토콜 | Si | Si |
| 스팟 인스턴스 | FleetIQ 통합 | K8s 스팟 + FleetIQ 어댑터 |
GameLift를 선택해야 하는 경우
GameLift는 이미 AWS 생태계에 긴밀하게 통합되어 있고 소규모 팀이 있는 경우 최선의 선택입니다. Kubernetes 전문 지식 없이도 가동 시간을 최소화하고 싶습니다. 추가 비용은 관리형 서비스이며 프로덕션 환경에서 Kubernetes를 관리하는 데 필요한 인력 비용보다 낮은 경우가 많습니다.
Agones를 선택하는 경우
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: 서버측 치트 방지 아키텍처







