Toegewijde gameservers: orkestratie met GameLift en Agones
Elke competitieve multiplayergame wordt ondersteund door een onzichtbare maar kritische infrastructuur: de speciale gameserver. In tegenstelling tot een peer-to-peer-architectuur waarbij een van de spelers optreedt als gastheer (met alle eerlijkheidsproblemen van dien). en daaruit voortvloeiend bedrog), een speciale server en een neutrale machine die de gezaghebbende simulatie van de spel, ontvangt input van alle spelers en distribueert de bijgewerkte status naar elk van hen.
Beheer tienduizenden van deze servers efficiënt en schaal dynamisch als reactie op de vraag en het minimaliseren van de kosten is een van de meest complexe technische uitdagingen in de moderne gaming-backend. Hierin artikel zullen we twee toonaangevende oplossingen voor gameserverorkestratie verkennen: AWS GameLift, De beheerde service van Amazon, bijv Agones, het open-sourceframework op Kubernetes ontwikkeld van Google en Ubisoft. We zullen zien hoe u tussen de twee kunt kiezen, hoe u ze kunt configureren en hoe u ze met systemen kunt integreren van matchmaking om een schaalbare en kosteneffectieve gaming-infrastructuur te creëren.
Wat je gaat leren
- Architectuur en levenscyclus van een speciale gameserver
- AWS GameLift: wagenparkbeheer, sessies en integratie met FlexMatch
- Agones op Kubernetes: GameServer CRD, Fleet en Autoscaling
- FleetIQ Adapter: Spot Instances om tot 90% op de kosten te besparen
- Implementatiepatroon voor meerdere regio's met Global Accelerator
- Monitoring en gezondheidscontrole voor gameservers met hoge beschikbaarheid
Architectuur van een speciale gameserver
Voordat je ingaat op de orkestratietools, is het essentieel om te begrijpen wat een gameserver precies doet toegewijd is en hoe het zijn levenscyclus beheert. Een typische dedicated server doorloopt deze toestanden:
| Staat | Beschrijving | Typische duur |
|---|---|---|
| BEGINNEN | Het proces start, laadt assets en registreert zich bij het orkestratiesysteem | 2-10 seconden |
| KLAAR | Klaar om verbindingen te accepteren, in afwachting van toewijzing aan een sessie | variabel |
| TOEGEWEZEN | Toegewezen aan een spel, accepteert de opgegeven spelers | duur van de wedstrijd |
| GERESERVEERD | Geboekt voor een toekomstige sessie, niet beschikbaar voor anderen | seconden-minuten |
| UITSCHAKELEN | Als het spel voorbij is, wordt het proces afgesloten en wordt de bron vrijgemaakt | 2-5 seconden |
Het orkestratiesysteem moet de status van elke server volgen, spelers over beschikbare servers verdelen, beheer storingen en schaal de vloot automatisch op als reactie op de belasting. De uitdaging is dat deze operaties ze moeten binnen enkele seconden gebeuren: geen enkele speler wil 30 seconden wachten om een match te vinden.
AWS GameLift: de beheerde service
AWS GameLift biedt een volledig beheerde service die de complexiteit van serverorkestratie abstraheert. De architectuur is verdeeld in drie hoofdcomponenten: de Vloot (EC2-instantiegroepen waarop de servers draaien), de Spelsessie (spelinstanties die op een server draaien) en i Speler sessie (slots gereserveerd voor elke speler in een sessie).
// 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);
}
}
De klant moet een gamesessie aanvragen via de GameLift Client SDK of REST API. Hier is hoe structureer de logica aan de clientzijde om een nieuwe sessie te starten of deel te nemen aan een bestaande sessie:
// 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: geïntegreerde matchmaking
GameLift omvat FlexMatch, een flexibele matchmaking-engine waarmee u regels kunt definiëren complex voor de samenstelling van de wedstrijden. Ondersteunt automatische aanvulling (vult vacatures in tijdens de wedstrijd), teambalancering, geografische filters en vaardigheidsvereisten.
// 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: Gameserver op Kubernetes
Agones en een open-sourceframework, aanvankelijk ontwikkeld door Google en Ubisoft en nu onderhouden
van de community, die Kubernetes uitbreidt met Custom Resource Definitions (CRD), speciaal ontworpen voor games
server. Agones introduceert drie nieuwe Kubernetes-objecten: GameServer, Fleet e
GameServerAllocation.
Het belangrijkste voordeel van Agones ten opzichte van GameLift is flexibiliteit: je kunt het op elk cluster inzetten Kubernetes (GKE, EKS, AKS, on-premise) en jij hebt volledige controle over de infrastructuur. Het nadeel is dat je moet veel aspecten zelf afhandelen die GameLift automatisch afhandelt.
# 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-integratie in de gameserver
Agones biedt SDK's voor Go, C++, Rust, Node.js en andere talen. De spelserver moet communiceren met het Agones-zijspan om uw status te rapporteren en levenscyclusmeldingen te ontvangen.
// 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 Adapter: kostenbesparingen met Spot Instances
Een van de grootste kosten bij het hosten van gameservers is computergebruik. AWS heeft de GameLift FleetIQ-adapter voor Agones, waarmee u Agones-servers kunt gebruiken EC2 Spot-instanties, een besparing tot 90% vergeleken met On-Demand-instanties.
Het risico van Spot Instances en plotselinge uitval wanneer AWS die capaciteit nodig heeft. FleetIQ beperkt dit risico door machine learning-algoritmen te gebruiken om uitval en storingen te voorspellen altijd een buffer van On-Demand-exemplaren aanhouden als reserve.
| Configuratie | Kosten/uur (c5.xlarge) | Besparingen | Risico op onderbreking |
|---|---|---|---|
| Op aanvraag | $ 0,192 | - | 0% |
| Spot-instantie | ~$0,025-0,060 | 70-87% | 5-15% |
| Spot + FleetIQ | ~$0,030-0,070 | 63-85% | <1% (ML-voorspelling) |
| Graviton-plek | ~$0,020-0,040 | 79-90% | <1% |
Architectuur voor meerdere regio's met Global Accelerator
Voor een game met een mondiale spelersbasis is een architectuur met meerdere regio's essentieel om te garanderen lage latentie voor alle spelers. AWS Global Accelerator routeert verkeer naar de regio dichterbij, waardoor de waargenomen latentie wordt verminderd en de verbindingsstabiliteit wordt verbeterd.
// 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
}
}
Vergelijking: GameLift versus Agones
| Criterium | AWS GameLift | Agones op Kubernetes |
|---|---|---|
| Initiële installatie | Snel, volledig beheerd | Complex, vereist K8s-expertise |
| Leverancierslock-in | Hoog (alleen AWS) | Laag (multi-cloud) |
| Vaste kosten | Prijzen per sessie + exemplaar | Alleen berekende kosten |
| Matchmaking | Geïntegreerde FlexMatch | Vereist een aparte Open Match |
| Schalen | Automatisch, beheerd | Handmatig + FleetAutoscaler |
| Multi-cloud | No | Ja (GKE, EKS, AKS) |
| UDP-protocol | Si | Si |
| Spot-instanties | FleetIQ geïntegreerd | K8s Spot + FleetIQ-adapter |
Wanneer kies je voor GameLift?
GameLift is de beste keuze als je al sterk geïntegreerd bent in het AWS-ecosysteem en een klein team hebt zonder Kubernetes-expertise en u de uptime wilt minimaliseren. De meerkosten van managed service en vaak lager dan de kosten van het personeel dat nodig is om Kubernetes in productie te beheren.
Wanneer kies je voor Agones?
Agones is ideaal voor teams met Kubernetes-expertise, voor games die op meerdere cloudproviders moeten draaien, of wanneer u de controle over uw infrastructuur wilt maximaliseren. En ook de juiste keuze als je die al hebt een gevestigde Kubernetes-infrastructuur hebben en deze willen inzetten voor gameservers.
Gezondheidscontrole en monitoring
Een robuust systeem voor gezondheidscontrole is essentieel om defecte gameservers eerder te detecteren die impact hebben op de spelers. Zowel GameLift als Agones ondersteunen periodieke gezondheidscontroles, maar Het is belangrijk om aangepaste applicatiestatistieken te implementeren.
// 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');
});
Beste praktijken en antipatronen
Beste praktijken
- Verwarm de vloot voor: Zorg altijd voor een READY-serverbuffer om koude starts tijdens pieken te voorkomen
- Instanties met fallback spotten: Gebruik Spot om de kosten te verlagen, maar behoud 10-20% On-Demand als reserve
- Sierlijke afsluiting: Geef servers 30-60 seconden de tijd om de huidige games te voltooien voordat ze worden afgesloten
- Persistente logboeken: Schrijf logboeken naar S3 of een gecentraliseerd systeem voordat u het afsluit
- Versiebeheer van afbeeldingen: Gebruik onveranderlijke tags (nooit
latest) voor reproduceerbare implementaties - Regionale affiniteit: Wijs spelers toe aan servers in de regio met de laagste latentie
Antipatronen die u moet vermijden
- Globale status op de server: Elke spelserver moet staatloos zijn wat betreft de infrastructuur; de spelstatus is lokaal voor de sessie
- Vloot te groot of te klein: Ondermaatse vloten veroorzaken wachten; te groot afvalgeld
- Te agressieve gezondheidscontroles: Controleer elke seconde het verbruik van hulpbronnen; Meestal is 5-10 seconden voldoende
- Geen afvoerperiode: Als u een server zonder afvoerperiode doodt, worden lopende games abrupt beëindigd
Conclusies
De orkestratie van speciale gameservers is een discipline die een delicaat evenwicht vereist kosten, latentie en betrouwbaarheid. AWS GameLift biedt een beheerde oplossing die de complexiteit vermindert operationeel, terwijl Agones de flexibiliteit biedt die nodig is voor multi-cloud-architecturen en teams sterke Kubernetes-expertise. De combinatie van Agones met de FleetIQ Adapter vertegenwoordigt waarschijnlijk beste afweging voor de meeste AAA-games: open-sourceflexibiliteit met besparingen kosten van Spot-instanties.
In het volgende artikel van de serie zullen we zien hoe je een systeem kunt bouwen verfijnde matchmaking met ELO- en Glicko-2-algoritmen, waarbij op vaardigheden gebaseerde matchmaking wordt geïntegreerd met orkestratiemechanismen die we vandaag hebben verkend.
Aankomende artikelen in de Game Backend-serie
- Artikel 03: Matchmaking Systeem met ELO en Glicko-2
- Artikel 04: Realtime statussynchronisatie en netcode-rollback
- Artikel 05: Anti-Cheat-architectuur aan de serverzijde







