Rework article thanks to Frederic review
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
parent
12f734eef5
commit
1288d4d88f
1 changed files with 129 additions and 102 deletions
|
|
@ -2,197 +2,224 @@
|
|||
title: L'architecture pragmatique de mes projets en production
|
||||
date: !!timestamp '2026-03-05 15:00:00'
|
||||
tags:
|
||||
- hosting
|
||||
- continuous integration
|
||||
- architecture
|
||||
- web
|
||||
- infrastructure
|
||||
- security
|
||||
---
|
||||
|
||||
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 cluster Kubernetes et
|
||||
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
|
||||
parfaitement avec une architecture beaucoup plus simple.
|
||||
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 que j'utilise pour
|
||||
Voici un retour d'expérience sur l'infrastructure de
|
||||
mes projets en production, dont certains dépassent les 5000 visiteurs
|
||||
quotidiens, et que j'ai également appliqué pendant des années lors
|
||||
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 présentiel.
|
||||
participants en temps réel.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
# Contexte
|
||||
## Contexte
|
||||
|
||||
Pendant longtemps, j'ai hébergé l'ensemble de mes projets directement
|
||||
chez moi. Mes sites web, mes services, ma forge Git et même mon
|
||||
J'ai hébergé durant des années l'ensemble de mes projets directement
|
||||
[à mon domicile]({{< relref "/fr/post/self-hosting/index.md" >}}). 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).
|
||||
machines ARM : principalement des Pine64, [mais pas que]({{< relref "/en/post/kernel_configs/index.md" >}}).
|
||||
|
||||
Contrairement à ce que j'entends partout, cela fonctionnait très bien.
|
||||
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
|
||||
un [mode de vie plus nomade]({{< relref "/fr/post/china-for-digital-nomads/index.md" >}}). Une connexion résidentielle stable et
|
||||
permanente devient difficile à garantir lorsque l'on se déplace
|
||||
souvent.
|
||||
|
||||
J'ai donc déplacé la partie publique de mes services chez OVH, en
|
||||
essayant de conserver la même philosophie : simplicité et pragmatisme.
|
||||
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
|
||||
## Une observation simple
|
||||
|
||||
Dans beaucoup de projets web modernes, l'architecture ressemble à
|
||||
quelque chose comme ceci :
|
||||
Beaucoup de projets web modernes adoptent une architecture semblable à
|
||||
celle-ci :
|
||||
|
||||
```
|
||||
utilisateur → CDN → frontend → API → base de données
|
||||
navigateur → CDN → frontend → API → base de données
|
||||
```
|
||||
|
||||
Chaque requête déclenche du calcul côté serveur, même lorsque le
|
||||
contenu change très rarement.
|
||||
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.
|
||||
|
||||
Dans mon cas, plusieurs de mes sites ont une caractéristique
|
||||
importante : **le contenu principal change une fois par jour.**
|
||||
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.
|
||||
|
||||
Prenons par exemple le [jeu quotidien
|
||||
Yakazu](https://yakazu-gratuit.fr/) : pour la majorité des visiteurs,
|
||||
la seule chose qui compte est **la grille du jour**, qui ne change
|
||||
qu'une fois par jour.
|
||||
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](https://yakazu-gratuit.fr/).
|
||||
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.
|
||||
|
||||
Dans ces conditions, générer la page dynamiquement à chaque requête
|
||||
n'a pas beaucoup de sens.
|
||||
|
||||
# Architecture retenue
|
||||
|
||||
La solution est donc très simple : le site est entièrement statique.
|
||||
La solution est donc très simple : rendre tout le site principalement statique.
|
||||
|
||||
Chaque jour, un événement déclenche la CI qui :
|
||||
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.
|
||||
|
||||
1. génère la nouvelle grille ;
|
||||
2. reconstruit le site ;
|
||||
3. publie les fichiers statiques.
|
||||
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.
|
||||
|
||||
Ces fichiers sont ensuite servis directement par l'hébergement
|
||||
statique fourni 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.
|
||||
|
||||
Oui, l'offre gratuite de 100 MB.
|
||||
|
||||
Cela donne donc une architecture très simple :
|
||||
|
||||
```
|
||||
CI → génération du site → fichiers statiques → OVH
|
||||
```
|
||||
|
||||
Tout le trafic est absorbé par l'infrastructure de l'hébergeur, ce
|
||||
qui est exactement ce pour quoi elle est conçue.
|
||||
Tout le trafic est absorbé par l'infrastructure de l'hébergeur, exactement ce
|
||||
pour quoi elle est conçue.
|
||||
|
||||
|
||||
# Frontend
|
||||
## Génération des pages
|
||||
|
||||
Le site est construit avec SvelteKit.
|
||||
Le site est construit avec [SvelteKit](https://kit.svelte.dev/).
|
||||
|
||||
Ce choix peut surprendre puisque je n'utilise pas du tout les
|
||||
capacités serveur de ce framework. Mais il permet :
|
||||
Ce choix peut surprendre puisque je n'utilise pas les capacités serveur de cet
|
||||
outil. Mais il permet de :
|
||||
|
||||
- de générer facilement un site statique (`@sveltejs/adapter-static`) ;
|
||||
- d'avoir un routage propre ;
|
||||
- de structurer le code proprement ;
|
||||
- de construire des Progressive Web Apps.
|
||||
- générer facilement un site statique (`@sveltejs/adapter-static`) ;
|
||||
- structurer le code ;
|
||||
- avoir un routage propre ;
|
||||
- construire des Progressive Web Apps.
|
||||
|
||||
Autrement dit : un framework moderne, mais sans dépendre d'un backend
|
||||
permanent.
|
||||
|
||||
# Et quand il faut un backend ?
|
||||
## Quand il faut une partie dynamique ?
|
||||
|
||||
Certaines fonctionnalités nécessitent malgré tout un peu de logique
|
||||
serveur.
|
||||
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.
|
||||
|
||||
Dans ce cas, je démarre un petit serveur backend séparé, qui ne reçoit
|
||||
qu'un nombre très limité de requêtes.
|
||||
Ici, je démarre un petit serveur séparé, qui reçoit un nombre très
|
||||
limité de requêtes.
|
||||
|
||||
L'architecture devient alors :
|
||||
|
||||
```
|
||||
site statique → CORS → petit backend
|
||||
navigateur → site statique
|
||||
(CORS) → backend
|
||||
```
|
||||
|
||||
Ce backend est volontairement minimal et ne traite que les actions
|
||||
qui ne peuvent pas être réalisées côté client.
|
||||
Ce backend est volontairement minimal et traite seulement les actions
|
||||
réellement indispensables.
|
||||
|
||||
Le point important est que **la majorité du trafic ne le touche
|
||||
jamais**.
|
||||
Bénéfice principal : **la majorité du trafic ne le touche jamais**.
|
||||
|
||||
# Résultat
|
||||
|
||||
## Quid des comptes utilisateurs, des paiements ?
|
||||
|
||||
Pourquoi une "connexion" à un compte utilisateur aurait obligatoirement besoin
|
||||
d'une validation **systématique** par le serveur ?
|
||||
|
||||
{{% card color="success" title="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).
|
||||
|
||||
{{% /card%}}
|
||||
|
||||
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 ;
|
||||
- il n'y a quasiment pas de calcul côté serveur ;
|
||||
- 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 pipeline
|
||||
Les problèmes que je rencontre sont presque toujours liés au processus
|
||||
de génération :
|
||||
|
||||
- une image Docker qui n'est plus à jour ;
|
||||
- un déclenchement CI qui échoue ;
|
||||
- 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 ?
|
||||
|
||||
Avec un peu de recul, je me rends compte que cette approche suit
|
||||
quelques principes simples :
|
||||
## 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 le backend aux cas réellement nécessaires**.
|
||||
3. **réserver les calculs aux cas réellement nécessaires**.
|
||||
|
||||
Ces principes permettent souvent d'absorber une charge importante
|
||||
avec des ressources très modestes.
|
||||
Ces principes permettent d'absorber une charge importante avec des
|
||||
ressources humaines ou logistiques modestes.
|
||||
|
||||
Pendant plus de dix ans, j'ai par exemple organisé un challenge de
|
||||
sécurité informatique qui a fini par accueillir environ 300
|
||||
participants simultanés, avec des machines très simples.
|
||||
|
||||
Le secret n'était pas la puissance du matériel, mais la conception du
|
||||
système. Je vous laisse visionner ma [présentation de cette
|
||||
## 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](https://youtube.com/watch?v=naFxBW5yoUA), avec les
|
||||
tâtonnements, les erreurs et les contraintes qui l'ont façonné.
|
||||
contraintes, mes tâtonnements, mes erreurs et les succès qui l'ont façonnée.
|
||||
|
||||
# Et les comptes utilisateurs, les paiements ?
|
||||
|
||||
Une "connexion" à un compte utilisateur n'a pas nécessairement besoin
|
||||
d'une validation systématique par un backend (d'ailleurs la notion de
|
||||
compte utilisateur n'a pas toujours de sens).
|
||||
## Conclusion
|
||||
|
||||
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 en faire usage dans
|
||||
l'interface : maintenant ... ou même plus tard. Comme ça, inutile de
|
||||
refaire une requête à l'API pour une information que l'on a déjà.
|
||||
|
||||
Non seulement cela réduit la charge du serveur, mais cela accroit
|
||||
aussi la vitesse de chargement pour l'utilisateur qui voit ses données
|
||||
sans appel réseau, et donc même hors ligne. Tout le monde est gagnant.
|
||||
|
||||
# Conclusion
|
||||
|
||||
L'infrastructure moderne propose de nombreux outils très puissants.
|
||||
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 beaucoup plus simple.
|
||||
peuvent être résolus de manière simple.
|
||||
|
||||
Avant d'ajouter une nouvelle couche technologique, il vaut souvent la
|
||||
peine de se poser une question très simple :
|
||||
Avant d'ajouter une nouvelle couche technologique, je vous encourage à vous poser la question :
|
||||
|
||||
**est-ce que ce contenu doit vraiment être généré à chaque requête ?**
|
||||
**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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue