L'architecture pragmatique de mes projets en production

Lorsqu’on lit certaines discussions entre développeurs ou architectes logiciels, on pourrait croire que la moindre application web nécessite aujourd’hui une infrastructure distribuée, un environnement Kubernetes et plusieurs services cloud spécialisés.

Pourtant, de nombreux services web, y compris ceux qui accueillent plusieurs milliers de visiteurs par jour, peuvent fonctionner aussi bien avec une architecture beaucoup plus simple.

Voici un retour d’expérience sur l’infrastructure de mes projets en production, dont certains dépassent les 5000 visiteurs quotidiens. Je l’ai également appliquée pendant des années au profit d’une compétition de sécurité informatique avec plus de 250 participants en temps réel.

Contexte

J’ai hébergé durant des années l’ensemble de mes projets directement à mon domicile. Mes sites web, mes services, ma forge Git et même mon infrastructure d’intégration continue tournaient sur de petites machines ARM : principalement des Pine64, mais pas que.

Contrairement à ce que je lis souvent, l’ensemble fonctionnait très bien.

La principale raison pour laquelle j’ai arrêté n’est pas un problème de puissance de calcul ou de fiabilité, mais simplement mon passage à un mode de vie plus nomade. Une connexion résidentielle stable et permanente devient difficile à garantir lorsque l’on se déplace souvent.

Ma solution a consisté à déplacer la partie publique de mes services chez OVH, en conservant la même philosophie : simplicité et pragmatisme.

Une observation simple

Beaucoup de projets web modernes adoptent une architecture semblable à celle-ci :

navigateur → CDN → frontend → API → base de données

Chaque requête déclenche du calcul côté serveur, alors même que le contenu principal change très rarement. Cela réclamme de nombreuses machines, implique parfois des prestataires, avec pour effet que si une brique tombe en panne, le contenu ne peut plus être servi à l’utilisateur.

De nombreux blogs, dont celui-ci, sont servis directement par un serveur web, sans phase de génération ou mécanisme complexe de cache. La génération des pages a lieu avant la publication : 1 fois pour toute.

La très grande majorité des site web qui présentent principalement du contenu pourraient être simplifié de la même manière : journaux en ligne, vitrines d’entreprise, boutiques en ligne, …

J’édite par exemple un jeu quotidien de Yakazu. Pour la majorité des visiteurs, la seule chose qui compte est la grille du jour. Or, ce contenu ne change qu’une fois par jour. Ainsi, générer la page dynamiquement à chaque requête n’a pas beaucoup de sens.

Architecture retenue

La solution est donc très simple : rendre tout le site principalement statique.

Chaque jour, un événement déclenche la reconstruction du site puis pousse les nouveaux fichiers sur l’espace mis à disposition par l’hébergeur.

Ces fichiers sont donc servis aux utilisateur directement, avec pour seul intermédiaire l’hébergement statique inclus avec le nom de domaine chez OVH.

Oui cette offre gratuite de 100 MB qui vous semble microscopique et inutilisable peut en réalité servir à héberger une boutique où les utilisateurs sont heureux de revenir chaque jour.

Tout le trafic est absorbé par l’infrastructure de l’hébergeur, exactement ce pour quoi elle est conçue.

Génération des pages

Le site est construit avec SvelteKit.

Ce choix peut surprendre puisque je n’utilise pas les capacités serveur de cet outil. Mais il permet de :

  • générer facilement un site statique (@sveltejs/adapter-static) ;
  • structurer le code ;
  • avoir un routage propre ;
  • construire des Progressive Web Apps.

Quand il faut une partie dynamique ?

Certaines fonctionnalités nécessitent malgré tout un peu de logique côté serveur.

Par exemple, les championnats organisés régulièrement doivent vérifier les participations et enregistrer les résultats et les chronos.

Ici, je démarre un petit serveur séparé, qui reçoit un nombre très limité de requêtes.

L’architecture devient alors :

navigateur → site statique
    (CORS) → backend

Ce backend est volontairement minimal et traite seulement les actions réellement indispensables.

Bénéfice principal : la majorité du trafic ne le touche jamais.

Quid des comptes utilisateurs, des paiements ?

Pourquoi une “connexion” à un compte utilisateur aurait obligatoirement besoin d’une validation systématique par le serveur ?

Arrêtons de mettre des bâtons dans les roues

L’étape de création d’un compte utilisateur n’a bien souvent aucune justification : lorsqu’un joueur veut acheter plus de grilles de Yakazu, il veut jouer, il ne veut pas imaginer un nouveau mot de passe.

Il n’y a aucune barrière technique qui empêche de donner accès à du contenu contre un simple paiement.

Si on profite du paiement pour receuillir une donnée personnelle identifiante lors du paiement (email, numéro de téléphone, compte Apple ou Google pour Apple Pay ou Google Pay, …), cette même donnée personnelle pourra servir pour donner à nouveau accès au contenu, cette fois après authentification (lien magique par exemple).

Une seule requête d’API peut faire l’authentification : que ce soit la validation d’un paiement ou d’une adresse email. Puis on stocke cette réponse dans le navigateur pour l’utiliser dans l’interface aussitôt … ou plus tard ! Inutile d’interroger à nouveau l’API pour une information déjà connue.

D’une part, nous réduisons la charge du serveur. D’autre part nous accélérons le chargement pour l’utilisateur qui voit ses données sans appel réseau, même hors ligne. Tout le monde y gagne !

Résultat

Avec cette architecture :

  • la quasi-totalité du trafic est servie sous forme de fichiers ;
  • la puissance de calcul nécessaire côté serveur est ridicule ;
  • la surface de panne est très réduite.

Les problèmes que je rencontre sont presque toujours liés au processus de génération :

  • un déclenchement qui échoue ;
  • une image Docker obsolète ;
  • un oubli dans la génération des données (l’absence de grille de jeu par exemple).

Mais ces problèmes sont faciles à détecter et se corrigent généralement en quelques minutes.

Pourquoi faire simple ?

Après plusieurs années d’expérience, je constate que cette approche suit plusieurs règles apparemment simples :

  1. pré-calculer ce qui peut l’être ;
  2. servir des fichiers plutôt que du calcul ;
  3. réserver les calculs aux cas réellement nécessaires.

Ces principes permettent d’absorber une charge importante avec des ressources humaines ou logistiques modestes.

Application à un service principalement dynamique

Certains services reposent obligatoirement sur une phase de calculs ou de génération côté serveur. Pas pour de mauvaises raisons, mais parce que le contenu présenté dépend de facteurs qui dépassent le seul utilisateur.

J’ai organisé pendant plus de dix ans un challenge de sécurité informatique avec des machines très simples. Avec près de 300 participants simultanés les dernières années, la conception d’une architecture qui tient la charge et résiste aux attaques logicielles probables est un défi qui dépasse de loin ce que la majorité des sites web pourront observer.

Le secret n’était pas la puissance du matériel, mais la conception du système. Regardez ma présentation de cette infrastructure, avec les contraintes, mes tâtonnements, mes erreurs et les succès qui l’ont façonnée.

Conclusion

L’infrastructure moderne propose des outils puissants. Mais il est parfois utile de se rappeler que beaucoup de problèmes peuvent être résolus de manière simple.

Avant d’ajouter une nouvelle couche technologique, je vous encourage à vous poser la question :

Ce contenu doit vraiment être généré à chaque requête ?

Dans bien des cas, la réponse est non.

Prêtons attention à ne pas être quelqu’un qui construit une cuisine de restaurant 3 étoiles pour faire des pâtes. La complexité est souvent la solution paresseuse.