← Articles

Bascule cloud à la demande : un serveur de jeu qui change de machine les soirs d’événement

· 12 min de lecture

Infrastructure · Go · Cloud

Notre serveur de jeu communautaire tourne très bien sur un petit VPS… sauf les soirs d’événement, où il rame. Ces soirs-là ne représentent qu’une vingtaine d’heures par mois. Payer un serveur dédié toute l’année pour ces quelques heures me paraissait absurde, alors j’ai fait l’inverse : pendant les événements, le serveur part sur une grosse VM Scaleway facturée à l’heure, puis il revient sur le VPS et la VM s’éteint.

Résultat : environ 6,5 € par mois au lieu de 140 € pour un dédié, à peu près une minute de coupure par bascule, et aucune modification du serveur de jeu. La bascule est pilotée par Lever, un petit orchestrateur en Go que j’ai écrit pour l’occasion. Son code est sur GitHub.

Go · Docker · WireGuard · HAProxy · Cloudflare Tunnel · Pelican · MongoDB · Scaleway

Pourquoi pas simplement une plus grosse machine

Le serveur réunit un lobby, des mini-jeux et un mode compétitif dans une seule instance. Sa boucle de jeu (la tick loop) est mono-thread et doit tenir en 50 ms par tick. Sur les vCPU partagés d’un VPS, le steal time fait grimper ce temps (le MSPT) dès qu’il y a du monde, et le jeu devient saccadé.

J’ai comparé trois options :

  • un VPS plus gros, payé 24 h/24 pour un gain limité, puisque le jeu n’utilise qu’un cœur ;
  • un serveur dédié haute fréquence, type OVH Game-1 avec un Ryzen 7 9800X3D, autour de 140 € HT par mois. C’est la meilleure machine, mais elle attendrait 97 % du temps ;
  • une instance cloud à l’heure chez Scaleway (4 cœurs EPYC Turin dédiés, 32 Go), à environ 0,23 € de l’heure. Quand elle est éteinte, seul le disque reste facturé.

Allumée en permanence, l’instance Scaleway coûterait plus cher que le dédié (environ 165 € par mois). Elle n’est intéressante que parce qu’on s’en sert peu : le point d’équilibre est vers 600 heures par mois, soit 20 heures par jour. À notre rythme, on est vingt fois en dessous, et même avec trois fois plus d’événements la facture resterait autour de 15,5 €.

Le calcul était vite fait. Le vrai travail, c’était de rendre la bascule fiable et assez simple pour que personne n’hésite à la lancer.

L’architecture

Tout ce qui doit rester en ligne en permanence vit sur le VPS. La VM n’apporte que la puissance de calcul.

Architecture de la basculeLes joueurs se connectent toujours à HAProxy sur le VPS, qui envoie le trafic à la VM quand elle tourne et sinon à la copie du VPS. La VM ouvre un tunnel WireGuard vers le VPS ; le jeu, la synchronisation des fichiers et l’accès à MongoDB passent par ce tunnel. Lever, sur le VPS, allume et éteint la VM par l’API Scaleway.VPS · toujours alluméVM Scaleway · éteinte par défautWireGuard (initié par la VM)OrganisateursCloudflare AccessJoueursmême adresseLever (Go)orchestre la basculeHAProxy :25565TCP, PROXY v2, health checksmain (Wings)la copie du quotidienMongoDBn’écoute que sur 10.8.0.1sshd · lever-syncclé limitée à rrsyncmain-event (Wings)4 cœurs dédiés, 32 GoAPI ScalewayHTTPSTCPallume, éteintrsync/SSHprioritairesecoursbase
Le chemin des joueurs (en orange) ne change pas : HAProxy choisit la copie qui répond. Tout ce qui va vers la VM passe par le tunnel.

Le tunnel part de la VM. Elle ouvre un tunnel WireGuard vers le VPS et n’expose aucun port entrant. Elle n’a donc pas besoin d’IP fixe : son adresse peut changer à chaque démarrage sans rien casser. Le jeu, l’administration, la base de données et les fichiers passent tous par ce tunnel.

Les joueurs gardent la même adresse. Sur le VPS, HAProxy écoute en mode TCP. Le backend VM est prioritaire, le VPS est déclaré backup, et un simple health check TCP choisit l’instance active. Le PROXY protocol v2 transmet l’IP réelle des joueurs. Je suis resté en couche 4 exprès : HAProxy ne termine pas le protocole du jeu, donc le serveur continue de vérifier lui-même les sessions (online-mode=true). Un proxy applicatif m’aurait obligé à désactiver cette vérification.

L’administration ne nécessite aucun port ouvert. Le panel Pelican, Wings (son daemon, installé sur la VM) et Lever sont publiés par Cloudflare Tunnel. Cloudflare Access protège le panel et Lever. Wings reste en dehors d’Access, parce que le panel appelle son API et que le navigateur y ouvre le websocket de la console. Il s’appuie donc sur sa propre authentification : le jeton du nœud pour l’API, un JWT signé par le panel pour le websocket.

Il n’y a qu’une base MongoDB, sur le VPS. Elle n’écoute que sur l’interface WireGuard (10.8.0.1), jamais sur l’IP publique, si bien que la même URI fonctionne depuis les deux machines. J’avais pensé à un replica set, mais à deux membres dont l’un est éteint 97 % du temps, il perd le quorum sans arbitre, et toutes les écritures passeraient de toute façon par le primaire. Copier la base sur la VM puis la fusionner au retour ne marchait pas non plus, puisque le site continue d’écrire pendant l’événement. Le seul point à garantir, c’est qu’il n’y ait jamais deux serveurs de jeu qui écrivent en même temps.

Enfin, des dépendances systemd (After= / Requires=) enchaînent wg-quick@wg0, puis docker, puis wings, pour que la VM se remette en route toute seule après un redémarrage.

Lever, l’orchestrateur

Une VM à l’heure n’est rentable que si on pense à l’éteindre. Et une bascule à la main (arrêter le serveur, copier les fichiers, le relancer ailleurs, ne rien oublier) est longue et facile à rater. Lever s’en charge. C’est un programme en Go avec une interface web d’une seule page, qui parle à l’API de Scaleway et à celle de Pelican, et synchronise les fichiers entre les deux machines.

Une bascule vers la VM se passe ainsi :

  1. Lever allume la VM et attend qu’elle soit prête : handshake WireGuard, API Wings joignable, MongoDB qui répond.
  2. Il copie une première fois les fichiers pendant que le serveur tourne encore sur le VPS.
  3. Il prévient les joueurs, arrête le serveur et vérifie qu’il est bien arrêté.
  4. Il synchronise les derniers changements, puis relance un rsync --dry-run --itemize-changes qui doit revenir vide.
  5. Il démarre le serveur sur la VM.

Le retour fait la même chose dans l’autre sens, puis éteint la VM. Si personne n’est connecté pendant 20 minutes, Lever lance le retour tout seul : un oubli coûte quelques centimes, pas une nuit entière.

Ne jamais perdre de données

C’est la partie qui m’a demandé le plus de réflexion. Lever ne garde presque rien en mémoire : à chaque démarrage, il reconstruit son état à partir de Scaleway et de Pelican. La seule information qu’il enregistre est active_copy, c’est-à-dire la machine qui a les données les plus récentes. Elle est écrite de façon atomique (fichier temporaire, fsync, rename) et ne change qu’après une synchronisation vérifiée.

Il ne croit pas non plus le panel sur parole. Pelican garde la réponse de /resources en cache pendant 20 secondes, et quand il n’arrive pas à joindre Wings, il répond quand même, avec un état missing. Pour Lever, missing ou une erreur veulent dire « état inconnu », jamais « arrêté » : avant de démarrer une copie, il lui faut deux lectures « offline » de l’autre, espacées de plus de 20 secondes.

Si Lever plante au milieu d’une bascule, il ne reprend pas tout seul. Il affiche l’étape interrompue, et on la relance à la main. Toutes les étapes peuvent être rejouées sans risque.

J’ai écrit six règles que Lever ne doit jamais enfreindre, et chacune a son test :

  1. les deux instances ne tournent jamais en même temps ;
  2. on ne démarre que l’instance qui a les données à jour ;
  3. on ne synchronise que depuis cette instance, et seulement une fois le serveur arrêté ;
  4. si la source est vide ou si rsync voudrait supprimer trop de fichiers (--max-delete), la synchronisation est refusée ;
  5. pas de retour sur le VPS sans rapatriement réussi ;
  6. pas d’extinction de la VM tant que les données ne sont pas revenues.

Une synchronisation sans root

rsync passe par SSH, dans le tunnel. Sur la VM, la clé est restreinte avec command="rrsync …",restrict dans authorized_keys : elle ne donne accès qu’au dossier du serveur. Le known_hosts est épinglé.

Une difficulté inattendue : Wings fait tourner les serveurs sous un UID différent sur chaque machine. Plutôt que de forcer les deux à s’aligner, Lever transfère avec -rlpt, sans -o ni -g. Les fichiers prennent alors l’UID de l’utilisateur distant, qui est celui de Wings. Aucun accès root n’est nécessaire.

Comment je l’ai testé

Une bascule ratée ne se voit souvent qu’au retour, quand il manque des fichiers. Je voulais donc pouvoir provoquer les pannes sans toucher au vrai serveur.

Dans les tests, chaque dépendance de Lever est remplacée par un faux : la VM, le tunnel, les deux serveurs du panel, MongoDB, rsync, le fichier d’état, Discord. Le temps est une horloge virtuelle, ce qui permet de tester les attentes de 20 secondes ou l’arrêt automatique sans attendre. Surtout, chaque faux surveille les six règles : si un test démarre la mauvaise copie ou éteint la VM trop tôt, il échoue, quel que soit ce qu’il vérifiait au départ. Les tests simulent ainsi un plantage à chaque étape des deux bascules, une annulation à chaque étape, un rapatriement qui échoue, une source vide ou un fichier d’état tronqué.

Deux séries de tests sortent du code. La synchronisation est testée avec le vrai rsync et le vrai rrsync, pour vérifier que les options utilisées passent bien la liste blanche. Et un dernier test, lancé une fois contre le vrai serveur, vérifie le comptage des joueurs : comme le serveur n’accepte que les connexions précédées d’un en-tête PROXY, Lever envoie lui-même cet en-tête avant de lui demander combien de joueurs sont connectés.

Comment je l’ai écrit

J’ai développé Lever avec Claude Code, un agent de programmation. J’ai d’abord rédigé les spécifications : l’architecture, les six règles ci-dessus, les tests attendus. Ensuite, mon travail a surtout consisté à trancher les choix, relire chaque plan avant qu’il soit codé, et tester le résultat sur le vrai serveur.

En chiffres

Coût de la puissance en plus, pour 20 h par mois~6,5 € au lieu de ~140 €
Coupure pour les joueurs à chaque bascule~1 minute
Première synchronisation1 168 fichiers, 619 Mo
Retour après un événement114 fichiers, 15,8 Mo, en 1,4 s
Modifications du serveur de jeuaucune

Sources des prix

Prix HT relevés en octobre 2026 : OVH Game-1 2026 (139,99 € / mois), Scaleway MEMORY3-X4C-32G (0,2266 € / h) plus environ 2 € / mois de disque. OVHcloud, serveurs Game · Scaleway, instances virtuelles · Scaleway, facturation des instances éteintes.