Monolithe ou microservices : choisir sans se mentir
- Accueil
- Architecture Cyber
- Fondations
- Monolithe ou microservices : choisir sans se mentir
Monolithe ou microservices : choisir sans se mentir
Section intitulée « Monolithe ou microservices : choisir sans se mentir »Le débat monolithe contre microservices se règle rarement sur des critères techniques et presque toujours sur des critères organisationnels. Cette page nomme le compromis réel (on échange de la complexité interne contre de la complexité réseau et opérationnelle) et donne les conditions dans lesquelles chaque camp tient debout.
Une cuisine de restaurant peut tenir dans une seule grande pièce où tout le monde travaille côte à côte, ou être répartie en plusieurs petits ateliers séparés qui se passent les plats. Les deux servent le même repas, mais on ne les organise pas pareil, et l’une des deux demande beaucoup plus de coordination qu’on ne le croit.
→ Lire l’explication complète de la marmotte
Ce que le découpage en microservices achète, et ce qu’il facture
Section intitulée « Ce que le découpage en microservices achète, et ce qu’il facture »Un monolithe est un seul artefact déployable : un binaire, un WAR, une image de conteneur, qui contient l’ensemble des domaines fonctionnels et parle en général à une seule base de données. Un microservice est un artefact déployable par domaine, avec son propre cycle de vie et, dans la définition stricte, son propre stockage.
Le gain n’est pas la performance. Un appel de fonction dans le même processus coûte quelques nanosecondes ; le même appel en HTTP/JSON entre deux pods coûte typiquement 0,5 à 5 ms, plus la sérialisation, plus la résolution DNS, plus le handshake TLS (Transport Layer Security) si le maillage est chiffré. Découper divise le débit d’un chemin critique par un facteur mesurable. Ce qu’on achète, c’est l’indépendance de déploiement : une équipe livre son domaine sans coordonner une fenêtre commune, et une panne mémoire dans le module de facturation ne tue plus le processus qui sert l’authentification.
Ce qu’on paie est plus discret. La transaction ACID (Atomicity, Consistency, Isolation, Durability) disparaît dès que deux services écrivent dans deux bases. Il faut la remplacer par des sagas, de la compensation, de l’idempotence sur chaque endpoint, et accepter une fenêtre de cohérence éventuelle. Un développeur qui écrivait un BEGIN ... COMMIT écrit désormais une machine à états, avec ses cas d’annulation partielle. Ce n’est pas plus difficile, c’est dix fois plus de code à maintenir pour le même effet métier.
Position assumée : par défaut, commencer par un monolithe modulaire, avec des frontières de domaine explicites dans le code (paquets isolés, interfaces publiques réduites, aucune requête SQL traversant deux domaines). Le découpage physique se justifie quand une contrainte nommée l’exige : équipes trop nombreuses pour partager une chaîne de livraison, profils de charge très hétérogènes entre domaines, ou besoin d’isoler un domaine à cause de son niveau de sensibilité (secrets, paiement).
Frontières de service, cohérence des données et couplage caché
Section intitulée « Frontières de service, cohérence des données et couplage caché »Le piège classique du découpage : le monolithe distribué. Des services séparés, mais qui partagent la même base de données. On a alors cumulé les inconvénients des deux modèles : impossible de déployer indépendamment (un changement de schéma casse trois services), et on a en prime ajouté la latence réseau. La règle est nette : un service qui ne possède pas exclusivement ses tables n’est pas un service, c’est un module déployé de travers. Sur ce point, les schémas et rôles PostgreSQL offrent une isolation intermédiaire utile : un rôle par service, limité à son propre schéma.
Deuxième couplage caché : le couplage temporel. Si le service A doit obtenir une réponse synchrone de B, C et D pour répondre, sa disponibilité est le produit des leurs. Trois dépendances à 99,9 % donnent 99,7 %, soit environ 26 heures d’indisponibilité par an au lieu de 8,7. La contre-mesure n’est pas le retry aveugle (qui transforme une lenteur en tempête de requêtes) mais le disjoncteur, les délais d’attente stricts sur chaque appel sortant, et le passage en asynchrone dès que le métier le tolère, comme décrit dans l’architecture événementielle.
Troisième point, souvent sous-estimé : le versionnement des contrats. Un microservice sans contrat versionné (OpenAPI, Protobuf, JSON Schema) et sans tests de contrat côté consommateur produit des ruptures silencieuses en production. Le champ optionnel devenu obligatoire ne casse rien au déploiement ; il casse le premier appel réel, trois heures plus tard.
Surface d’attaque : ce que le réseau ajoute au modèle
Section intitulée « Surface d’attaque : ce que le réseau ajoute au modèle »Découper un monolithe transforme des appels internes en trafic réseau, donc en surface d’attaque. Un appel de méthode n’était ni interceptable ni rejouable ; un appel HTTP l’est.
Ce qu’il faut poser avant de découper :
- Authentification service à service. mTLS (mutual TLS) avec des certificats à durée courte (24 h ou moins), ou jetons signés à courte validité. Un réseau plat où tout service peut appeler tout service reproduit exactement le modèle de confiance du monolithe, mais sur un périmètre bien plus large. Voir PKI et TLS pour la chaîne de confiance.
- Autorisation par appel. L’identité du service appelant ne dit rien de l’utilisateur final. Propager un contexte d’identité signé (jeton JWT, JSON Web Token) et le valider dans chaque service, sinon un service compromis usurpe n’importe quel utilisateur.
- Segmentation. Politiques réseau par défaut en deny, ouvertes explicitement par paire de services. Sans cela, une SSRF (Server-Side Request Forgery) dans un service exposé donne accès à toute la maille interne, y compris aux endpoints d’administration jamais prévus pour être atteints depuis l’extérieur.
- Secrets. Chaque service a ses propres identifiants de base, sa propre rotation. Un secret unique partagé par dix services annule la compartimentation qui justifiait le découpage. La distribution passe par la chaîne décrite dans Git et CI.
Ce que les microservices ne garantissent PAS : ni la résilience (un service défaillant en synchrone propage sa panne), ni la sécurité (le découpage crée des frontières, il ne les défend pas), ni la scalabilité (un point de contention en base reste un point de contention). Ces propriétés viennent des disjoncteurs, du filtrage réseau et du dimensionnement du stockage, pas de la topologie.
Côté exploitation, la trace distribuée n’est plus un confort. Sans identifiant de corrélation propagé de bout en bout, un incident sur douze services se diagnostique à la main dans douze fichiers de logs, avec des horloges qui dérivent. Les sauvegardes se compliquent aussi : douze bases signifient douze points de restauration qui ne sont pas cohérents entre eux, ce qui impose un plan explicite, sujet traité dans Sauvegardes et Observabilité.
Exemple : refuser le trafic non authentifié entre services
Section intitulée « Exemple : refuser le trafic non authentifié entre services »Extrait de politique réseau Kubernetes qui n’autorise que le service facturation à joindre paiement, sur le seul port utile. Tout le reste tombe.
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: paiement-ingress-restreint namespace: prodspec: # Cible : les pods du service paiement uniquement podSelector: matchLabels: app: paiement policyTypes: - Ingress ingress: - from: # Seul le service facturation, dans le même namespace - podSelector: matchLabels: app: facturation ports: - protocol: TCP port: 8443 # port mTLS, le 8080 en clair n'est pas ouvertVérification depuis un pod tiers : l’appel doit expirer, pas être refusé proprement (une politique réseau droppe, elle ne renvoie pas de RST).
# Depuis un pod du namespace prod qui n'a PAS le label app=facturationkubectl -n prod run test --rm -it --image=curlimages/curl -- \ curl -m 5 -k https://paiement:8443/health# Attendu : "Connection timed out after 5001 milliseconds"Attention : une NetworkPolicy n’est appliquée que si le CNI (Container Network Interface) l’implémente. Sur un cluster où le plugin ignore ces objets, le manifeste est accepté par l’API, affiché par kubectl get netpol, et ne filtre rien du tout. Tester le refus, jamais l’acceptation. Le reste du contexte conteneur est couvert dans Docker et Podman.
Migration progressive plutôt que réécriture
Section intitulée « Migration progressive plutôt que réécriture »Le remplacement intégral d’un monolithe par une flotte de services, en une seule opération, échoue avec une régularité qui devrait suffire à décourager. La voie praticable est l’étranglement progressif : placer un proxy devant le monolithe, extraire un domaine à la fois, router les URL correspondantes vers le nouveau service, garder la possibilité de revenir en arrière par simple modification de la route. Le reverse proxy devient alors la pièce centrale du plan de migration.
Premier domaine à extraire : celui qui a le moins de dépendances sortantes, pas celui qui pose problème. Un domaine périphérique valide la chaîne complète (déploiement, secrets, traces, alertes, restauration) sur un périmètre où l’échec est absorbable. Extraire d’abord le cœur métier revient à découvrir les trous de l’outillage sur le chemin critique.
Et un point de mesure honnête : si livrer une modification d’une ligne dans le monolithe prend déjà trois semaines à cause de la validation manuelle, le découpage n’y changera rien. Le problème est dans la chaîne de livraison, pas dans l’artefact.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans ces garde-fous, la ruche reste ouverte par défaut. Faute de frontières nommées et de contrats versionnés, une réponse à incident ne peut plus délimiter le périmètre compromis : rien ne permet d’affirmer quels services ont écrit une donnée, ni à quel moment, et l’analyse post-incident se réduit à des suppositions. Sans identités de service distinctes, les journaux d’accès à la base attribuent toutes les écritures au même compte, ce qui rend la traçabilité juridiquement inexploitable. Sans stratégie de retour arrière par la route, une extraction ratée impose une réécriture inverse en urgence, pendant que le trafic continue. Et sans plan de restauration coordonné, une reprise remet en ligne des états incohérents entre domaines, ce qui produit des commandes payées sans contrepartie, ou l’inverse, sans alerte pour le signaler.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Un logiciel doit exécuter plusieurs tâches : gérer les comptes, afficher un catalogue, encaisser un paiement. Première façon de faire : tout mettre dans un seul programme. Les parties se parlent directement, en mémoire, instantanément, et elles partagent la même base où sont rangées les données. Si une opération échoue au milieu, la base annule tout d’un coup et rien ne reste à moitié fait. Défaut : pour modifier une seule ligne, il faut réinstaller tout le programme, et si une partie plante, tout le programme s’arrête.
Deuxième façon : découper en plusieurs petits programmes séparés, chacun avec ses propres données, qui communiquent par le réseau. Chacun se met à jour sans toucher aux autres. Mais un message réseau peut être lent, perdu, ou lu par quelqu’un qui n’en a pas le droit. Il faut donc chiffrer les échanges, vérifier qui appelle qui, et fermer par défaut toutes les communications non prévues.
Le vrai problème apparaît quand une opération touche deux programmes : le premier enregistre, le second échoue. Plus personne n’annule automatiquement. Il faut écrire soi-même la marche arrière, prévoir qu’un même message reçu deux fois ne compte qu’une fois, et poser un numéro de suivi dans chaque échange pour retrouver plus tard ce qui a cassé et où. C’est ce travail supplémentaire, et lui seul, qui décide si le découpage en vaut la peine.
À lire aussi
- Sécuriser une API gateway comme point de contrôleArchitecture de sécurité d'une API gateway comme point de contrôle centralisé, avec ses limites, ses attaques et ses conditions d'exploitation.
- Concevoir une défense en profondeur par couchesConcevoir une architecture de sécurité en couches où réseau, système, application et données se compensent sans créer de redondance aveugle.
- Sécuriser event sourcing et CQRS à grande échelleSécurité, audit, intégrité et exploitation des architectures event-driven fondées sur l’event sourcing et CQRS.
- Comment bloquer les requêtes côté serveur et la désérialisationSSRF et désérialisation permettent de détourner une application malgré un pare-feu correctement configuré.
Pages voisines
La citent
Monolithe ou microservices : choisir sans se mentir
Elle cite
- Lien dans les deux sens :Observabilité en architecture cyber : logs, métriques, traces
- Principes et architecture de l'événementiel
- Circuit breaker et bulkhead contre la propagation des pannes
- Continuité, sauvegardes, PRA/PCA et haute disponibilité
- Conteneurs, Kubernetes, CNI et service mesh
- Déployer des NetworkPolicy Kubernetes pas à pas
- Intégration continue avec Git : automatiser vos workflows
- Sécuriser un contrat API-first dès la conception
: les deux pages se citent l’une l’autre.
