Ingénierie de plateforme

Ceci n'est pas un schéma de ce que je ferais.
C'est la flotte qui tourne en ce moment.

J'ai conçu, construit et j'exploite seul un écosystème SaaS multi-tenant en production, avec de vrais clients payants. Cette page montre les chiffres mesurés, comment une mise en production se déroule et — ce qui compte vraiment — les incidents qui ont mis le système à terre et ce qui a changé après chacun.

Tous les chiffres ci-dessous ont été mesurés sur la flotte, non estimés
Lecture de 30 secondes

Ce que je conçois, réalise, exploite et mesure

Ce tableau existe pour vous éviter de lire toute la page. La colonne qui compte le plus est la dernière : mesurer est ce qui sépare exploiter de supposer. Et là où je n'ai que la formation, pas la pratique, c'est dit — cocher tout n'informe personne.

Domaine ConçoisRéalise ExploiteMesure
Linux, réseau et diagnostic pare-feu avec SSH seul · réseau segmenté en deux sous-réseaux · diagnostic de contention de verrou dans la base
Conteneurs et composition 224 conteneurs · limites de CPU et de concurrence déclarées · une image par dépôt
Livraison continue et stratégie de publication canari avec validation et retour par digest · verrou de flotte · registre de publication
Observabilité et réponse aux incidents 9 cibles · 12 règles · trois post-mortems écrits, chacun avec le garde-fou qu'il a laissé
Bases de données et continuité primaire + réplique chaude · WAL continu · RTO et RPO chronométrés
Sécurité, identité et multi-tenant zéro port exposé · isolation fail-closed dans la base · OAuth2 et JWKS maison · coffre à secrets
Architecture distribuée et messagerie outbox transactionnel · 12 groupes de consommation · déduplication active · file d'échecs surveillée
Infrastructure as code modules Terraform de ma main pour cloud géré (réseau, base, sécurité, observabilité) — ce n'est pas ce que j'exploite aujourd'hui : la flotte est déclarative par composition
Kubernetes formation achevée et manifestes écrits ; décision assumée de ne pas l'exploiter — le pourquoi est à l'Acte III
Cloud géré de tiers formation achevée ; l'exploitation réelle tourne en cloud public avec des services maison — je le dis exprès
en production, aujourd'hui partiel ou hors production non — et je le dis
I

Ce qui est en jeu

Ce n'est pas un laboratoire. Il y a des clients qui paient, et quand cela tombe quelqu'un cesse de vendre. Tout le reste de cette page existe à cause de cette phrase.

La flotte

La taille de l'exploitation

31
microservices avec déploiement en production
224
conteneurs en service (112 par nœud)
87
mises en production par jour (moyenne sur 13 jours)
11.047
tests automatisés dans 3 des 31 services

Deux nœuds actif-actif

ARM64 en cloud public, 8 vCPU et 30 Go chacun, derrière un tunnel Cloudflare. Les deux servent du trafic : aucun nœud ne reste inactif à attendre la catastrophe.

Multi-tenant avec isolation dans la base

Row-Level Security dans PostgreSQL, fail-closed : sans la portée du client dans la session, la requête renvoie zéro ligne. Le rôle d'exécution n'a pas BYPASSRLS.

Une barrière qualité qui refuse vraiment

Une couverture minimale qui fait échouer le build : 95 % sur deux services, 90,3 % sur un autre. Ce n'est pas un objectif affiché au mur — c'est une condition pour publier.

D'où vient chacun des chiffres ci-dessus
la question que je poserais à votre place — et la réponse, avec la limite de chaque mesure
ouvrir

Un chiffre sans provenance est une opinion déguisée en donnée. Chaque ligne ci-dessous dit ce qui a été compté, comment, quand — et ce que la mesure ne prouve PAS. La dernière colonne est celle qui m'intéresse : une mesure honnête déclare sa propre limite.

ChiffreComment il a été mesuréCe que cela ne prouve PAS
31 comptage des fichiers de composition de production dans les dépôts que tous soient actifs en même temps — il compte ce qui a un déploiement défini
224 docker ps sur les deux nœuds : 112 chacun. Sur ces 112, 59 sont des processus applicatifs (web, travailleur, publieur, consommateur, ordonnanceur) ; les 53 autres sont bus, données, observabilité, bordure et coffre ce n'est pas une mesure de charge ni d'utilisation — c'est un comptage de processus
87 registre de publication d'un nœud, 1 212 lignes sur 14 jours. Chaque ligne = une recréation de service avec date, digest et commit. Médiane 79 ; varie de 2 à 172 par jour ce n'est pas « 87 nouvelles versions » : une publication logique recrée le service sur les deux nœuds, et la moyenne masque la variation
11 047 tests réellement exécutés lors d'une exécution de 3 services. En parallèle : 31 438 fonctions de test définies dans 14 services (comptage statique) les deux chiffres mesurent des choses différentes, et aucun n'est la couverture — la couverture est le plancher qui fait échouer le build
RPO mesuré à l'instant : retard de réplication de 1,01 s et zéro octet de WAL en attente d'envoi au standby c'est un échantillon, pas un test de perte du primaire. Le vrai RPO en cas de panne ne se connaît qu'en provoquant la panne — c'est au plan, pas à l'historique
47 min exercice de provisionnement d'un nœud de zéro et de démarrage de la flotte dessus, en septembre 2026, le lendemain de la migration vers le cloud actuel (instances créées les 12 et 16, vérifiées dans les métadonnées du fournisseur). Critère de « prêt » : s'authentifier avec identifiant et mot de passe et entrer dans le système — ce qui éprouve bordure, tunnel, routage, identité, base et session de bout en bout cela ne prouve pas que les 31 services étaient sains ni que le travail de fond tournait déjà — cela prouve que le chemin critique de l'utilisateur était debout. C'est un critère de bout en bout, plus fort que « le conteneur a démarré » et plus étroit que « toute la flotte a convergé »
12 min temps pour que la flotte entière démarre et converge, chronométré lors du même exercice de septembre 2026, avec le même critère de bout en bout même réserve : un exercice, à une date, chez un fournisseur. Le répéter chez un autre fournisseur est ce qui transformerait le chiffre en preuve de portabilité, et non seulement de reprise
12 règles fichier d'alertes lu directement depuis le service de métriques ; 9 cibles actives, toutes répondant au moment de la lecture qu'une règle existe n'est pas une alerte testée : toutes n'ont pas été déclenchées exprès pour vérifier le chemin jusqu'à moi

« Montrez-moi le schéma et je me méfie. Montrez-moi ce qui se passe quand il tombe et je vous crois. »

ce que cette page tente de répondre
II

Comment elle tient

Le dessin complet : par où le trafic entre, où chaque pièce tourne, comment on atteint la donnée — et ce qui se passe quand chaque partie tombe.

Avant l'infrastructure : le système vu de l'extérieur, et de l'intérieur

Deux vues qui précèdent le schéma de déploiement. La première répond à « qui utilise ceci et avec quel monde extérieur il parle ». La seconde répond à « quelles applications existent et comment elles se parlent » — sans aucune infrastructure, à dessein : mélanger les deux dans un seul schéma est l'erreur qui rend la plupart illisibles.

Schéma de contexte : qui utilise le système et avec quel monde externe il parle NIVEAU 1 · CONTEXTE qui l'utilise, et avec quel monde extérieur il dialogue Opérateur du client vend, achète, facture Client final portail, reçu, signature Exploitant de la plateforme une personne — moi Zenithus SaaS multi-tenant 31 microservices une origine unique pour tous les écrans Acquéreur · TPE carte et réconciliation Bureaux · Open banking crédit et relevés API gouvernementales fiscal et travail Signature · Marketplace contrats et vente externe Messagerie WhatsApp et courriel tout ce qui sort passe par UN service d'intégration Frontière de confiance aucun acteur n'atteint un service directement : toute entrée passe par la bordure gérée et l'identité maison ● personne ■ le système ▣ système externe
Schéma de conteneurs : quelles applications existent et comment elles se parlent NIVEAU 2 · CONTENEURS les applications et leurs échanges — sans infrastructure, à dessein Navigateur de l'utilisateur UNE ORIGINE — tout ce qui suit répond sous la même adresse Identité Java · Spring Boot émet le jeton publie les clés points d'accès standard OAuth2/OIDC Coque applicative Python · Django menu, contexte du client et session — il embarque les verticales 12 verticales métier Python · Java · Go ventes · stock · fiscal finance · RH · flotte… montées par chemin Plan de contrôle qui est le client, quel plan il a, de qui il dépend appel SYNCHRONE, typé Bus d'événements un fait → 12 groupes dédup · file d'échecs chemin PAR DÉFAUT : asynchrone Intégration 22 connecteurs · le SEUL qui parle aux tiers renvoie un FAIT normalisé PostgreSQL — isolation client dans la base elle-même une base par verticale · la règle du qui-voit-quoi ne dépend pas du code applicatif L'infrastructure (nœuds, réseau, équilibrage) est dans le schéma de déploiement juste en dessous — ici, uniquement l'applicatif.
Architecture de la flotte Zenithus — schéma logique et physique De l'utilisateur à la base : bordure Cloudflare avec WAF et tunnel sortant, aucun port d'entrée sur le pare-feu, identité maison servant OAuth2 et JWKS sur la même origine unique où les verticales sont montées par chemin, réseau segmenté en sous-réseau d'application et sous-réseau de données, deux nœuds actif-actif, PostgreSQL primaire avec réplique chaude et archivage continu du WAL, sauvegarde hors du fournisseur, et une couche transversale d'autorisation, d'observabilité et de livraison continue. Les points uniques de défaillance sont marqués. Architecture Zenithus — logique et physique Mesurée sur la flotte, non estimée · identifiants et adresses omis à dessein 12 minRTO — flotte entière 47 minRTO — nœud neuf de zéro secondesRPO — WAL continu 1 Utilisateurs, partenaires et API HTTPS BORDURE GÉRÉE — CLOUDFLARE l'attaque meurt avant d'atteindre le nœud DNS + TLS certificat géré WAF filtre avant l'origine 2 Tunnel — un connecteur par nœud connexion SORTANTE ; drainer = publier sans risque R2 — sauvegarde hors fournisseur dump toutes les 3h · base hebdo · médias · rétention Pas d'egress facturé · pas de verrouillage l'origine peut changer de fournisseur sans réécriture PARE-FEU SYSTÈME — services autorisés : SSH uniquement. Les ports 80 et 443 n'existent pas dans la règle : le trafic entre parce que le nœud SORT. OCI · région São Paulo · réseau virtuel privé SPOF accepté : les 2 nœuds dans le même domaine de disponibilité SOUS-RÉSEAU D'APPLICATION · /24 sans IP publique · sortie par NAT NŒUD A — canari (publie en premier) ACTIF ARM64 · 8 vCPU · 30 Go · 112 conteneurs cloudflared Traefik — route hôte + chemin 3 IDENTITÉ MAISON (Spring Boot) — OAuth2 + JWKS authentification et clés sur la MÊME origine que les écrans session sur un seul domaine : sans CORS, sans cookie tiers ORIGINE UNIQUE — 12 verticales montées par CHEMIN /app/<verticale> — un préfixe par domaine métier une coque héberge les autres ; aucune verticale n'a de route publique propre Bus local — 14 Redis (un par verticale) outbox transactionnel → relais → Streams → inbox avec dédup → DLQ 5 HAProxy — porte unique de base (un backend ; promotion manuelle) NŒUD B — stable ACTIF identique · aucun des deux n'est une réserve inactive cloudflared Traefik 4 SDK UNIQUE DE LA PLATEFORME une seule implémentation d'autorisation pour toutes les verticales — pas 12 copies divergentes portée par requête, fail-closed : sans portée, l'appel est REFUSÉ machine à machine : une clé par service la vérité du plan et du tenant vient du Control Plane PILES POLYGLOTTES, UN SEUL CONTRAT Java/Spring Boot · Go · Rust · Python le contrat est l'événement et la portée, pas le langage Bus local — 15 Redis · file sur disque PostgreSQL 16 RÉPLIQUE CHAUDE streaming actif instance séparée SSH fermé + pg_receivewal archive le WAL en continu récupération à un instant donné lecture, pas écriture SOUS-RÉSEAU DE DONNÉES · /24 — segmenté de l'application joignable uniquement depuis le réseau privé pgbouncer entre dans le chemin pool en transaction — des dizaines de services sans ouvrir de connexion à volonté PostgreSQL 16 — PRIMAIRE hôte dédié, hors des nœuds applicatifs · 24 bases isolation client par Row-Level Security, fail-closed rôle d'exécution sans BYPASSRLS : oublier la portée renvoie zéro ligne SPOF CONNU le primaire est le point unique. Pas de bascule automatique — à dessein : évite le split-brain. réplication continue → ⑥ sauvegarde chiffrée hors fournisseur SORTIE CONTRÔLÉE — UNE SEULE PORTE VERS LE MONDE mesuré : zéro SDK tiers dans les verticales métier 7 Intégration — 22 connecteurs acquéreur · bureau · gouvernement · open banking signature · marketplace · messagerie · stockage Disjoncteur · réessai avec recul · idempotence un tiers lent ne devient pas une file bloquée ici les identifiants viennent du COFFRE, jamais de l'environnement Retour du tiers : origine SÉPARÉE pour les webhooks signature HMAC vérifiée — charge non signée rejetée le tiers ne touche jamais l'origine des écrans le résultat revient à l'écosystème comme FAIT normalisé sur le bus — le consommateur ne parle jamais au tiers COUCHE TRANSVERSALE — vaut pour tout ce qui précède SECRETS Coffre OpenBao · un jeton par consommateur moindre privilège · MFA sur opération critique alerte dédiée pour jeton qui expire OBSERVABILITÉ Prometheus · Grafana · Alertmanager cAdvisor · exporters · sondes externes 9 cibles · 12 règles — 3 surveillent l'ABSENCE LIVRAISON CONTINUE branche → PR → barrière locale → canari valide sur le nœud drainé · retour par digest la production ne compile jamais · verrou global de flotte CONTINUITÉ dump toutes les 3h · base hebdo · WAL continu destination hors fournisseur · restauration vérifiée registre : date, digest et commit de chaque service CE QUI SE PASSE EN CAS DE PANNE — la partie qu'un beau schéma omet Un nœud applicatif tombe L'autre sert déjà 100 % du trafic. La file du nœud arrêté est sur disque et reprend à son retour. Impact utilisateur : aucun. Sans action humaine. La base primaire tombe Panne réelle. La réplique chaude existe et est à jour ; la promotion est manuelle, avec un runbook écrit. Choix assumé : automatiser sans fencing donne un split-brain. La région entière est perdue La sauvegarde chiffrée vit hors fournisseur. Un nœud neuf provisionné et la flotte debout : 47 minutes, mesurées. Perte de données : des secondes, grâce au WAL continu.
bordure gérée calcul et réseau privé données et continuité limite de sécurité · point unique de défaillance assumé
  Pourquoi les points uniques de défaillance sont marqués. Un schéma qui cache le SPOF n'est pas un schéma, c'est une brochure. Les trois qui existent ici — les deux nœuds dans le même domaine de disponibilité, la base primaire sans promotion automatique et la porte unique de base — sont des choix, et chacun a sa raison consignée plus bas.
La plateforme

Couche par couche

Les 224 conteneurs ne sont pas 224 copies de la même chose. Comptés par rôle, ils dessinent la plateforme — et chaque couche existe parce qu'une douleur l'a réclamée.

Bordure — aucun port ouvert sur internet

Aucun des deux nœuds n'expose de port public. Le trafic entre par un tunnel Cloudflare (connecteur sortant) et arrive sur Traefik, qui assure le routage par hôte, le TLS automatique et sert de point d'équilibrage. Drainer un nœud du tunnel est ce qui rend la mise en production canari possible : c'est ainsi qu'on retire un nœud de l'équilibrage sans rien interrompre.

Données — pool, proxy et isolation dans la base

PostgreSQL sur un hôte dédié, partagé par les verticales, avec HAProxy comme point d'accès unique et pgbouncer provisionné mais encore hors du chemin (mesuré : zéro client actif ; les applications vont directement à HAProxy) — il entrera quand le nombre de connexions l'exigera. Il y a deux instances : primaire et réplique chaude. L'isolation client repose sur Row-Level Security, pas sur des WHERE tenant_id éparpillés dans le code : le rôle d'exécution n'a pas BYPASSRLS, donc oublier la portée renvoie zéro ligne au lieu de fuiter.

Honnêteté sur la cible : il n'y a pas de bascule automatique de la base. La promotion est manuelle et documentée — et je préfère le dire plutôt que de dessiner un Patroni qui n'existe pas.

Messagerie — 12 relais, 12 consommateurs, 10 ordonnanceurs

Les services ne s'appellent pas en chaîne : ils publient des faits. Le chemin est un outbox transactionnel (le fait est validé avec la donnée, ou aucun des deux) → relais → Redis Streams → inbox avec déduplication par groupe de consommation → DLQ pour ce qui a échoué. Il y a 14 instances Redis : le bus est par verticale et par nœud, et c'est précisément pourquoi la convergence se règle dans Postgres, pas dans un verrou distribué qui ne traverserait pas les nœuds.

Secrets — un coffre, pas un fichier .env

Les identifiants vivent dans un coffre (OpenBao) avec un jeton de service par consommateur. Une alerte dédiée surveille le jeton du coffre qui approche de l'expiration — car un secret qui expire en silence fait tout tomber d'un coup, à la pire heure.

Comment les services se parlent — et pourquoi c'est presque jamais un appel direct

La règle est de publier un fait, pas d'appeler un service. Le chemin par défaut est asynchrone : outbox transactionnel (le fait n'existe que si la donnée existe), relais, file, inbox avec déduplication par groupe de consommation et DLQ pour ce qui a échoué. Un service en panne retarde donc l'autre — il ne l'entraîne pas dans sa chute.

Quand la réponse est nécessaire maintenant — « ce client a-t-il droit à cette fonctionnalité ? », « cette entreprise est-elle fille de celle-là ? » — l'appel est synchrone, mais ce n'est jamais du HTTP épars dans le code : il passe par un client typé du SDK unique, avec des opérations nommées et une authentification machine à machine par clé propre à chaque service. L'autorité sur le plan, la hiérarchie et le droit d'usage a un seul propriétaire ; les autres demandent, ils ne déduisent pas.

Le même SDK est chargé dans les 9 verticales Python — une implémentation d'autorisation, pas neuf divergentes

Le bus d'événements — un fait, douze lecteurs, aucun couplage

Quand une vente a lieu, le service de ventes n'avertit personne. Il publie le fait — et douze groupes de consommation indépendants le lisent à leur rythme. Le stock décrémente la pièce, la finance crée la créance, le fiscal prépare le document, la comptabilité enregistre. Aucun ne sait que les autres existent, et l'un d'eux en panne retarde sa propre file, pas la vente.

l'écriture et le fait dans la MÊME transaction le relais publie un flux par domaine 12 groupes de consommation l'inbox écarte les doublons DLQ pour ce qui a échoué
10.003faits sur le flux de la plateforme
12groupes de consommation indépendants
242consommateurs enregistrés
0en attente — le bus est à jour
5.362marqueurs de déduplication actifs
20dans la file d'échecs, sous surveillance

  Travailleurs avec une limite écrite

Par nœud : 13 travailleurs, 11 publieurs, 10 consommateurs et 10 ordonnanceurs. Chacun tourne avec une concurrence de 1, déclarée dans le fichier de composition. La valeur par défaut de l'outil est 8 — et c'est ainsi que la flotte est tombée une fois. Une limite qui n'est pas écrite n'existe pas.

  Files séparées par nature

Le travail d'IA, d'analyse et de routine ont leurs propres files. Un appel de modèle qui prend trente secondes ne peut pas passer devant la file qui confirme une vente. Séparer est ce qui empêche le lent de retarder l'urgent.

  File sur disque : retard, pas perte

Le bus écrit sur disque. Si un nœud tombe avec du travail en file, ce travail ne s'évapore pas — il attend le retour du nœud et reprend. La différence entre « le client a attendu » et « la commande a disparu » tient à cette ligne de configuration.

  Le détail distribué qui m'a le plus coûté à comprendre. Le bus est par nœud — chacun a le sien. Un verrou distribué via le bus ne fonctionne donc pas entre les deux : chaque nœud verrait son propre verrou et les deux exécuteraient. Comme la base est la seule ressource réellement partagée, la convergence se règle là, par une revendication atomique de la ligne — le premier qui la prend exécute, l'autre poursuit sans bloquer. Ce n'est pas une élection de leader, et c'est voulu : l'élection concentre le travail sur un nœud et réduit le parallélisme que les deux nœuds existent pour offrir.

Observabilité — et le soin d'alerter sur l'absence

Prometheus collectant 9 cibles (les deux nœuds, cAdvisor, l'exportateur PostgreSQL, Traefik, sondes HTTP), Grafana, Alertmanager, cAdvisor, node et postgres exporter, blackbox sondant les routes de l'extérieur. 12 règles d'alerte en service.

Ce que je considère comme la partie mature, ce n'est pas la quantité — c'est ce qu'elles surveillent. Trois d'entre elles ne demandent pas « est-ce que quelque chose a cassé ? », mais « est-ce que quelque chose a cessé de se produire ? » : sauvegarde hors site vide, sauvegarde en retard, sauvegarde sans la base à l'intérieur. Un coffre de sauvegarde qui existe mais reste vide passe pour sain dans n'importe quel contrôle de présence — et on ne le découvre que le jour de la restauration. Les autres couvrent la chute de PostgreSQL, le disque qui se remplit sur chaque nœud, un nœud hors service, un slot WAL qui s'accumule et un conteneur en boucle de redémarrage.

9 cibles sur 9 répondant au moment de cette mesure

« Le meilleur outil est la planification. »

pourquoi chaque pièce est là où elle est
III

Pourquoi c'est ainsi

Chaque choix ici a un gain, un coût que je paie et une conséquence commerciale. Qui ne liste que le gain vend, il ne conçoit pas.

Docker Compose — et non Kubernetes ou k3s
la décision qu'on me demande le plus, et celle au compromis le plus clair
voir le compromis

Tout le budget de calcul tient en deux nœuds de 8 vCPU et 30 Go, et ils font déjà tourner 224 conteneurs. Un plan de contrôle Kubernetes — serveur d'API, etcd, ordonnanceur, contrôleurs, réseau superposé, contrôleur d'entrée — consomme du CPU et de la mémoire sur ce même budget, de façon soutenue. Et la charge CPU soutenue est exactement ce qui m'a déjà coûté deux suspensions d'infrastructure.

ce que j'y gagne Un modèle mental qu'une seule personne peut exploiter à 3 heures du matin. L'unité de déploiement est la même chose que ce qui tourne sur ma machine. Pas de réseau superposé, pas de contrôleur d'entrée, pas de cycle de mise à jour de cluster, pas de ressources personnalisées à déboguer pendant un incident.
ce que je paie pour cela Je n'ai pas de replanification automatique entre nœuds ni d'autoscaling horizontal. La capacité est une décision humaine. Si les deux nœuds saturent en même temps, rien ne grandit tout seul — il faudrait provisionner, et cela prend des minutes, pas des secondes.

En décision d'affaires : Kubernetes résout un problème d'échelle que je n'ai pas encore, en facturant aujourd'hui en complexité opérationnelle et en consommation de la ressource la plus rare dont je dispose. Tant que la charge tient sur deux nœuds, l'adopter reviendrait à payer d'avance une assurance contre un risque inexistant — et à en créer un nouveau, bien réel : une surface que je ne peux pas tenir seul en astreinte. Ce n'est pas rejeter l'outil ; c'est ne pas changer d'outil par mode. Le jour où le calcul changera — plus d'un opérateur, ou une charge qui ne tient plus sur deux nœuds — la migration est déjà dessinée, et elle est bon marché précisément parce que tout est déjà image de conteneur et configuration déclarative.

Tunnel sortant — et non un équilibreur avec IP publique
la surface d'attaque qui cesse d'exister
voir le compromis
ce que j'y gagne Il n'y a aucune porte d'entrée à scanner. Le pare-feu n'autorise que SSH ; 80 et 443 ne figurent pas dans la règle. Le filtrage applicatif, le certificat et l'atténuation volumétrique restent sur la bordure gérée, avant qu'un seul octet ne touche mon nœud. Et drainer un nœud de l'équilibrage, c'est arrêter un processus — c'est ce qui rend la mise en production canari possible.
ce que je paie pour cela Une dépendance à un tiers sur le chemin critique d'entrée : si la bordure tombe, le système est injoignable même s'il tourne. C'est un fournisseur de plus dans la chaîne, et je ne contrôle pas sa fenêtre de maintenance.

En décision d'affaires : un équilibreur géré coûte à l'heure et au volume, et me rend en prime une IP publique à défendre. Le tunnel ne coûte rien sur le forfait utilisé, supprime la surface exposée et apporte le filtrage applicatif avec. J'ai échangé un risque que je devrais exploiter (l'exposition) contre un risque que je surveille (la disponibilité d'un tiers) — et ce second-là a une sonde externe et une alerte.

Promotion manuelle de la base — et non bascule automatique
la décision la plus inconfortable à défendre, et la plus juste
voir le compromis
ce que j'y gagne Le split-brain devient impossible. Deux instances acceptant des écritures en même temps, dans un système multi-tenant qui déplace de l'argent, c'est un dégât qu'aucune sauvegarde ne défait correctement — parce que les deux côtés sont « justes ».
ce que je paie pour cela La base primaire est un point unique de défaillance déclaré. Si elle tombe, il y a une panne réelle jusqu'à la promotion, et le délai dépend d'un humain qui répond. Je ne le cache pas dans le schéma : c'est marqué.

En décision d'affaires : une bascule automatique sans couche de consensus ni isolement du nœud défaillant échange une panne rare et récupérable contre une corruption silencieuse et permanente. Pour qui conserve les données financières de tiers, cet échange est mauvais dans tous les cas. L'atténuation n'est pas d'automatiser la promotion : c'est que la réplique soit chaude et à jour, que la procédure soit écrite, et que la récupération à un instant donné existe — le tout mesuré.

Deux nœuds actif-actif — et non un nœud plus gros
une résilience utilisée tous les jours, pas endormie à attendre la catastrophe
voir le compromis
ce que j'y gagne Les deux servent du trafic en permanence, donc le chemin de résilience est éprouvé à chaque requête — pas le jour de la catastrophe. C'est aussi ce qui permet de publier sur un nœud qu'aucun utilisateur n'utilise.
ce que je paie pour cela Tout doit être sûr en double : une tâche planifiée tourne sur les deux, donc la protection contre la duplication vit dans la base, pas dans un verrou distribué — qui ne fonctionnerait même pas, car le bus est local à chaque nœud.

En décision d'affaires : une réserve inactive est un coût qui ne sert que le pire jour — et qui échoue souvent précisément ce jour-là, parce que personne ne l'éprouve. Avec les deux actifs, le même argent achète de la capacité et de la résilience, et la preuve que cela marche, c'est le trafic d'hier. Honnêteté sur la limite : aujourd'hui les deux sont dans le même domaine de disponibilité, donc une défaillance corrélée du domaine me met encore à terre. C'est marqué dans le schéma parce que c'est la prochaine étape, pas parce que je ne le vois pas.

Chaîne et barrière qualité maison
sans exécuteur hébergé — avec une conséquence que je dis tout haut
voir le compromis
ce que j'y gagne La suite tourne contre PostgreSQL avec l'isolation client armée, pas contre une base jouet — un défaut de sécurité multi-tenant apparaît à la barrière, pas en production. Pas de file d'exécuteur, pas de minute facturée, pas de secret de production chez un tiers.
ce que je paie pour cela Il n'existe aucun filet de sécurité distant. Si je ne lance pas la barrière, personne ne le fait — et j'ai déjà découvert qu'elle pouvait finir au vert sans avoir exécuté un seul test.

En décision d'affaires : la valeur de la barrière n'est pas où elle s'exécute, c'est ce qu'elle refuse. Un exécuteur hébergé lançant la suite contre une base sans isolation client donne une impression de couverture et laisse passer la classe de défaut la plus douloureuse dans un SaaS multi-tenant : la fuite entre clients. Pour compenser le coût que j'ai assumé : un test qui surveille la barrière elle-même et échoue bruyamment.

Identité maison et origine unique — et non identité externalisée
la décision qui élimine toute une classe d'attaque au lieu de la filtrer
voir le compromis

Les douze verticales n'ont pas d'adresse publique propre. Elles sont toutes montées par chemin sous une origine unique, et le serveur d'identité — OAuth2 et publication de clés — vit sur cette même origine. L'autorisation ne se limite pas à une porte en façade : elle voyage avec le service, via un SDK unique que toutes les verticales embarquent et qui refuse l'appel quand la portée est absente.

ce que j'y gagne Une origine unique signifie une session sur un seul domaine : pas de partage inter-origines, pas de cookie tiers — que les navigateurs suppriment — et aucune des surfaces qu'un portail par sous-domaine créerait. Et comme l'autorisation vit dans le service, pas seulement en bordure, celui qui arrive depuis l'intérieur du réseau est bloqué aussi : il n'existe pas de « j'ai déjà passé la porte, donc je suis de confiance ».
ce que je paie pour cela L'identité est le composant le plus coûteux à exploiter avec sérieux : rotation des clés, expiration, fédération. Elle m'a déjà coûté cinq heures et demie de connexion hors service — j'avais fait tourner le jeu de clés de signature en croyant que c'était celui de publication. L'externaliser aurait évité cet incident précis.

En décision d'affaires : le produit est multi-tenant et se vend à des entreprises qui doivent hériter d'une hiérarchie de clients, d'un plan souscrit et d'une séparation des tâches. Une identité externalisée facture par utilisateur actif — le coût croît avec la base au moment précis où la marge devrait s'améliorer — et pousse la règle métier hors du système qui en est propriétaire. En la gardant en interne, le coût est d'ingénierie (une fois) plutôt que d'abonnement (toujours), et la règle du qui-voit-quoi reste là où elle est vraie. Le coût est atténué côté exploitation : une alerte dédiée surveille clé et jeton qui expirent, et la barrière de publication vérifie que l'émetteur de clés répond avant de rendre le nœud au trafic.

Quatre langages — et un seul contrat
polyglotte par décision, pas par accident historique
voir le compromis

Java avec Spring Boot pour l'identité, la comptabilité et le moteur de flux. Go pour les nouveaux services — un binaire unique, sans runtime, démarre vite et occupe peu de mémoire, la ressource rare ici. Rust à deux endroits précis : l'agent qui observe le cœur du système d'exploitation et le moteur de calcul fiscal. Python avec Django dans les verticales métier, où la vitesse de changement vaut plus que les microsecondes.

ce que j'y gagne Chaque problème dans l'outil qui le résout le mieux, sans réécrire ce qui marche déjà. Comme le contrat entre services est l'événement et la portée — pas le langage — changer l'implémentation d'un service n'oblige personne d'autre à changer. C'est ce qui rend la migration progressive possible, au lieu d'un saut risqué.
ce que je paie pour cela Quatre chaînes de construction, quatre jeux de dépendances à maintenir à jour et quatre façons de déboguer sous pression. Pour une petite équipe c'est réel — d'où le confinement de chaque langage là où il paie, et non son éparpillement par goût.

En décision d'affaires : réécrire ce qui fonctionne est la façon la plus coûteuse de ne rien livrer. L'existant productif continue de rapporter en Python pendant que ce qui naît naît en Go, et le contrat stable entre eux permet de changer pièce par pièce sans arrêter l'exploitation — sans le risque de la réécriture totale, qui historiquement fait tomber des entreprises.

Une seule porte vers le monde — et non un SDK tiers dans chaque service
22 intégrations externes, un seul endroit où elles existent
voir le compromis

Acquéreur de carte, bureau de crédit, API de gouvernement, open banking, signature électronique, marketplace, messagerie — tout entre et sort par un seul service. Il traduit chaque fournisseur en une capacité normalisée et renvoie le résultat à l'écosystème sous forme de fait sur le bus. Aucune verticale métier ne connaît le nom du fournisseur ; la règle métier reste chez le consommateur, pas dans l'adaptateur.

ce que j'y gagne Changer de fournisseur, c'est changer un adaptateur, pas traquer des appels dans douze services. L'identifiant du tiers existe à un seul endroit et vient du coffre. Et le rayon d'impact d'une panne externe reste contenu : disjoncteur, réessai avec recul et idempotence vivent là — un tiers lent ne devient pas une file bloquée dans la maison.
ce que je paie pour cela Un saut réseau de plus sur le chemin, et un service qui, s'il tombe, emporte toutes les intégrations d'un coup. C'est une concentration de risque — assumée, et c'est pourquoi il est parmi les plus surveillés.

En décision d'affaires : l'intégration avec des tiers est l'endroit où vit le risque contractuel — identifiant fuité, facturation indue, donnée client qui sort. Concentrer signifie qu'auditer « qui parle au monde extérieur, et avec quel identifiant ? » revient à regarder un service, pas douze. Et c'est vérifiable, pas une promesse : j'ai mesuré les six verticales métier et aucune n'a de SDK tiers installé — ils n'existent que dans le service d'intégration. Une règle qui dépend de la discipline humaine échoue en silence ; celle-ci se mesure.

Le coût comme exigence d'architecture — et non comme facture en fin de mois
le calcul qui décide ce qui entre et ce qui reste dehors
voir le compromis

Exploiter seul avec de vrais clients impose une contrainte qu'une grande entreprise n'a pas : chaque service géré que je souscris est de l'argent tous les mois et une pièce que je ne contrôle pas. C'est pourquoi la conception évite les trois sources classiques d'une facture qui grossit sans prévenir : le trafic sortant facturé, un équilibreur facturé à l'heure et une identité facturée par utilisateur actif.

ce que j'y gagne Les données sortent sans facturation au volume, car la sauvegarde va vers un stockage sans frais d'egress et le trafic entre par tunnel plutôt que par un équilibreur facturé. L'identité maison signifie un coût d'ingénierie fixe, pas un abonnement qui croît avec la base d'utilisateurs — précisément quand la marge devrait s'améliorer. Et la marge est mesurée : 47 % de la mémoire utilisée et une charge moyenne inférieure à 1 sur les deux nœuds.
ce que je paie pour cela Je paie en travail. Un service géré est cher en argent et bon marché en temps ; mon arrangement est l'inverse. Chaque pièce que je n'ai pas achetée est une pièce que j'exploite, mets à jour et répare à 3 heures du matin. Ce calcul ne tient que tant que l'échelle tient sur deux machines et l'exploitation sur une personne.

En décision d'affaires : dans un SaaS, le coût d'infrastructure est ce qui sépare la marge de la perte quand la base grandit. La question que je pose avant d'adopter un service est « ce coût croît-il avec le nombre de clients ? » — s'il croît linéairement avec le revenu, il mange l'économie d'échelle que le modèle devrait apporter. Le dimensionnement, les limites de CPU et de concurrence déclarées et le registre de publication existent autant pour le coût que pour la fiabilité : une ressource que personne ne mesure est une ressource que personne ne coupe.

Et le test que presque personne ne fait : combien de temps pour revenir ?

Un plan de reprise jamais chronométré est une intention, pas un plan. Ces trois chiffres ont été réellement mesurés, non estimés.

12 min
pour que la flotte entière démarre et converge
47 min
pour provisionner un nœud de zéro et y faire tourner la flotte
secondes
de perte maximale de données — le WAL est archivé en continu
  Les 47 minutes sont aussi la preuve la plus honnête de portabilité du calcul et de l'applicatif : c'est le temps de reconstruire la couche de calcul et d'application chez n'importe quel fournisseur livrant une machine Linux — ce qui tourne, ce sont des images de conteneur, PostgreSQL et Redis, sans service propriétaire. Ce n'est pas une indépendance totale — la bordure, le registre d'images et le fournisseur lui-même restent des dépendances externes, et remplacer chacune a son propre coût.

« Rien n'est publié à la main — et rien ne compile sur le serveur de production. »

une règle née de deux suspensions
IV

Comment elle change sans tomber

Publier est le moment où un système stable devient dangereux. Le chemin ci-dessous existe pour que changer cesse d'être un acte de courage.

Livraison continue

Comment une mise en production se déroule

Rien n'est publié à la main et rien ne compile sur le serveur de production. La contrainte est née d'une vraie douleur : l'infrastructure précédente a été suspendue deux fois pour charge CPU soutenue — compiler sur l'hôte était l'un des déclencheurs. L'architecture a changé à cause de cela.

1

Construire hors production

L'image est construite sur le poste/CI et publiée dans le registre avec le tag du commit. Le serveur de production ne fait que pull — il ne compile jamais.

2

Drainer le nœud canari de l'équilibrage

Le connecteur du tunnel est arrêté sur le nœud A. 100 % du trafic va vers le nœud B. La mise en production se fait sur un nœud qu'aucun utilisateur n'utilise.

3

Valider avec des données réelles — et avec la bonne question

Pas de crashloop, l'émetteur de clés répond, et zéro migration en attente. Ce dernier contrôle est né d'un incident : un processus debout ne signifie pas que la base a suivi.

4

Échec ? Retour arrière automatique par digest

Il revient au digest précédent sur le nœud même et s'interrompt — le nœud stable n'a jamais été touché. Et le connecteur du tunnel est restauré avec vérification : en cas d'échec, il crie sur stderr avec la commande de réparation manuelle.

5

Succès ? Retour à l'équilibrage, puis miroir

Le nœud validé revient dans le tunnel et seulement ensuite le second nœud reçoit la même image, un service à la fois. Un verrou global sérialise toute la flotte : deux déploiements simultanés ont déjà mis l'infrastructure à terre.

Et c'est consigné

Chaque service recréé est inscrit dans un registre sur le nœud avec la date, le digest de l'image et le commit. C'est ainsi qu'on répond à « qu'est-ce qui tourne exactement ? » sans deviner.

La chaîne

La barrière est locale — et c'est pour cela qu'elle a des dents

La chaîne est maison : il n'y a pas de runner hébergé qui vérifie la pull request. GitHub stocke le code et sert de registre d'images ; la barrière qualité tourne avant, sur le poste. Cela a une conséquence inconfortable que je préfère dire tout haut : il n'existe aucun filet de sécurité distant — si je ne lance pas la barrière, personne ne la lance.

  Ce que la barrière exige

Formatage et lint avec cliquet (le plancher ne fait que monter), contrôle d'intégrité du projet et la suite tournant sur PostgreSQL avec RLS armé — pas sur une base jouet. Une couverture sous le plancher fait échouer. Sur le service en Java, la même barrière ajoute tests d'architecture, intégration avec de vrais conteneurs et analyse statique.

  Rien n'entre directement dans la ligne principale

Le travail se fait sur une branche, la publication passe par une pull request, et la ligne principale est protégée. push --force, reset --hard et la suppression de la principale sont bloqués par politique. Avant un gros remaniement, un repère de retour.

  Une barrière verte qui n'a jamais démarré

La cible de la barrière appelait l'interpréteur du système et mourait juste après que le lint ait affiché « tout va bien ». Qui lisait l'écran voyait du vert sur une barrière qui n'avait exécuté aucun test. Aujourd'hui un test surveille la barrière elle-même — et il échoue bruyamment.

« Un système qui n'a jamais cassé est un système que personne n'utilise. »

trois incidents réels, et la cicatrice de chacun
V

Quand ça a cassé — et ce qui est resté

La partie que l'on ne publie normalement pas. Cause, ce qui a échoué dans la détection, ce que j'ai aggravé en essayant de réparer, et le garde-fou qui empêche la récidive.

Fiabilité

Incidents, et ce qui a changé ensuite

Un système qui n'a jamais cassé est un système que personne n'utilise. Ce qui fait la différence n'est pas l'absence d'incident — c'est ce qui reste après. Ces trois-là sont réels, du même jour, et chacun a laissé un garde-fou dans le code.

La production est tombée

La migration qui s'est verrouillée contre elle-même

Symptôme
Chaque écran qui lisait les ventes répondait par une erreur : colonne inexistante. La mise en production avait été signalée comme terminée.
Cause
Le service utilise deux rôles de base — un propriétaire, qui exécute le DDL, et un d'exécution, avec RLS. Une migration a créé la colonne dans la transaction du propriétaire et, à la ligne suivante, a mis à jour les données via le mauvais rôle : la seconde connexion ne voyait pas la colonne et attendait le verrou que la migration elle-même détenait. Auto-interblocage — et PostgreSQL ne le signale même pas, car la session propriétaire n'attend pas un verrou, elle attend le client.
Pourquoi c'est passé
La suite tourne avec les deux rôles fusionnés en un seul. Le défaut n'existe qu'en production. Et la validation du déploiement mesurait « le processus est monté », pas « la base a suivi ».
J'ai aggravé
Ma première tentative d'appliquer la migration à la main est morte par timeout et a laissé la session idle in transaction toujours en possession du verrou. J'ai empilé deux tentatives de plus avant de comprendre.
Résolution
Terminer les sessions bloquées, revenir en arrière en récupérant l'image précédente du registre et recréer un nœud à la fois. Ensuite : reproduire le défaut sur une base jetable — la version cassée se bloque, la corrigée s'applique — avant de republier.
Ce qui est resté
Un garde-fou structurel par AST dans la suite (une migration de données doit utiliser la connexion de la migration) et un comptage des migrations en attente dans le déploiement, qui interrompt et annule.

Le garde-fou est né avec le vice même qu'il allait chasser : il ne comptait que les migrations en attente, et grep -c sur une sortie vide affiche zéro — identique à « aucune en attente ». Aujourd'hui il compte aussi le total : une liste vide n'est pas un schéma à jour, c'est un manque de preuve.

Défaillance silencieuse · argent

L'écart de carte qui était avalé

Symptôme
Aucun. C'est bien le problème.
Cause
La réconciliation quotidienne compare les transactions de l'acquéreur aux paiements enregistrés. Chaque écart publie un événement — mais le type de l'événement n'avait jamais été déclaré au catalogue, et le publieur refuse un type non déclaré. L'exception tombait dans un except large englobant le client entier : chaque écart était détecté puis jeté. Une transaction fantôme chez l'acquéreur, ou un paiement sans confirmation — de l'argent — devenait une ligne de journal.
Comment c'est apparu
Ce n'est pas apparu : c'est un nouveau garde-fou qui balaie le code à la recherche d'événements publiés hors catalogue. Il en a trouvé deux — celui-ci et un autre.
Le second défaut
Déclarer l'événement, à lui seul, aurait déclenché une fausse alerte par vente par carte : l'adaptateur en production ne sait pas lister les transactions de l'acquéreur, et le code traitait « je n'ai pas pu lire » comme « l'acquéreur n'a rien ».
Ce qui est resté
Sans lecture, la routine ne signale rien et indique explicitement qu'il n'y a pas eu de preuve. Une liste vide issue d'une lecture réussie compte toujours comme un écart réel.

L'absence de mesure n'est pas l'absence du fait. Et une alerte qui crie toujours aveugle le gardien — ç'aurait été pire que le défaut d'origine.

Intégration morte au milieu

L'événement que personne n'écoutait

Symptôme
La marchandise retournée au fournisseur sortait correctement du stock — et la dette envers lui restait entière. Le magasin payait pour des pièces qu'il avait renvoyées.
Cause
Le service de stock publiait l'événement de retour, la documentation disant littéralement que le document financier « vit dans l'autre service ». Aucun service ne le consommait. Suite verte aux deux bouts, fonctionnalité morte au milieu.
Et il était inconsommable
L'événement identifiait le fournisseur par l'identifiant local de celui qui publiait. De l'autre côté, cet identifiant ne signifie rien — et le résolveur crée un fournisseur générique quand il ne reconnaît pas la référence. Le consommer tel quel aurait créé un crédit suspendu à un fournisseur fantôme.
Ce qui est resté
L'événement porte désormais l'identité qui franchit la frontière (référentiel central ou document fiscal). Sans elle, rien n'est publié — et l'écran avertit l'opérateur, par ce même prédicat, que le crédit n'a pas été généré et quoi faire.

En mesurant une intégration, ne vous arrêtez pas à « l'événement est publié ». Demandez qui écoute — et si ce qu'il transporte signifie quelque chose de l'autre côté.

Qualité par construction

Garde-fous as code

Chaque incident ci-dessus est devenu un test qui fait échouer le build si la classe de défaut revient. Une convention écrite dans un document vieillit ; un garde-fou exécutable, non. Et chaque garde-fou est testé avec un mutant : si je réintroduis le défaut et qu'il reste vert, il ne sert à rien.

  Une question, un propriétaire

« Cet entrepôt compte-t-il comme stock vendable ? » était écrit, littéralement, en cinq endroits. Avec un second type d'entrepôt, chaque copie oubliée devient un trou silencieux. La règle a désormais un propriétaire unique — et un test refuse quiconque la réécrit à la main.

  Un événement publié est au catalogue

Un balayage par AST : tout type d'événement publié dans le code doit être déclaré. Il a trouvé deux cas vivants — l'un avalait les écarts de carte.

  Une migration écrit par la bonne connexion

Un garde-fou structurel, pas comportemental : en test les deux rôles de base fusionnent, donc le défaut n'existe qu'en production. Un test comportemental resterait vert avec le système à terre.

  Un lien symbolique ne s'échappe pas de l'arbre

Deux liens absolus pointant vers la machine de celui qui les a créés se sont retrouvés dans l'image de production — et ils étaient autoréférents : récursion infinie pour tout balayage qui les suit.

Culture SRE

Ce n'est pas le titre — c'est ce que la pratique impose

Le SRE, ici, n'est pas un titre : c'est un ensemble de règles qui m'ont déjà coûté cher à apprendre et qui sont aujourd'hui écrites dans des scripts, des tests et des politiques. En voici six :

  Post-mortem sans coupable — y compris sur moi

Le récit du premier incident de cette page comporte un champ intitulé « J'ai aggravé » : ma tentative de réparation manuelle a empilé trois sessions bloquées avant que je comprenne le problème. Un post-mortem qui omet l'erreur de son auteur n'aide personne à apprendre — il protège l'auteur.

  Restaurer d'abord, comprendre ensuite

Face à une production à terre, la marche à suivre est de revenir au digest précédent et seulement ensuite d'enquêter. Le retour arrière est automatique et par digest — pas par « reconstruire l'image d'avant », ce qui n'est pas la même chose et m'a déjà trompé.

  Une alerte qui crie toujours aveugle le gardien

Une alerte déclenchée sur un total cumulé reste allumée pour toujours et finit ignorée — la nouvelle panne arrive donc sans que personne ne la voie. Une alerte doit mesurer le changement, et se taire quand le changement s'arrête.

  La contrainte vient de la douleur, pas du manuel

L'infrastructure précédente a été suspendue deux fois pour charge CPU soutenue, et compiler sur le serveur était l'un des déclencheurs ; une troisième suspension aurait été définitive. Aujourd'hui « la production ne compile jamais » n'est pas une préférence de style — c'est une règle avec une revue qui bloque la pull request.

  Le changement est sérialisé, jamais parallèle

Deux déploiements simultanés ont déjà mis la flotte à terre. Il existe un verrou global : un service à la fois, un nœud à la fois, et un verrou orphelin est récupéré au lieu de bloquer la flotte pour toujours.

  Tout changement laisse une trace consultable

Chaque recréation de service est inscrite dans un registre sur le nœud : date, digest et commit. La question « qu'est-ce qui tourne exactement ? » se répond par une requête, pas par la mémoire de celui qui a déployé. C'est de là que vient la moyenne mesurée de 92 mises en production par jour.

Comment je travaille

Trois principes nés de cicatrices

Mesurer avant d'affirmer

J'ai déjà signalé « c'est une question de configuration de l'opérateur » à propos d'un champ impossible à configurer — le type ne pouvait pas contenir l'identifiant de l'autre service. Zéro enregistrement rempli sur douze dans toute la flotte, ce n'était pas de la négligence : c'était un défaut. Aujourd'hui je mesure avant de conclure.

Un manque de preuve n'est pas l'absence de défaut

Une validation qui n'a pas pu mesurer ne peut pas annoncer « tout va bien ». Cette confusion est apparue trois fois : dans le déploiement, dans la réconciliation des cartes et dans un garde-fou de mon cru — toujours avec l'air d'être au vert.

Une barrière verte n'est pas une fonctionnalité qui marche

Une suite avec des milliers de tests verts cohabitait avec l'écran principal cassé. Un test avec une doublure ne mesure pas la règle qui n'existe qu'à la vraie frontière — et la vérification visuelle attrape ce qu'aucun test n'attrape.

  À propos de cette page. Les incidents sont réels et les chiffres ont été mesurés sur la flotte. Les noms de clients, adresses réseau et identifiants internes ont été omis à dessein — ce sont des informations de tiers et de la surface d'attaque. À qui veut aller plus loin, je fais la démonstration en direct.

« Ce que je n'ai pas encore testé vaut autant que ce que j'ai testé — à condition que je dise lequel est lequel. »

l'état honnête de ce qui manque
VI

Capacité, continuité et gouvernance

Ce qui est fait, ce qui est partiel et ce qui n'existe pas encore. Séparer les trois est ce qui rend les deux premières colonnes crédibles.

Ingénierie de capacité

faitLimites de CPU, mémoire et concurrence déclarées par service dans le fichier de composition — pas la valeur par défaut de l'outil
faitMarge mesurée : 47 % de la mémoire utilisée et charge moyenne sous 1 sur les deux nœuds
faitFiles séparées par nature, pour que le travail lent ne retarde pas l'urgent
partielPercentiles de latence : mesurés. p50 50 ms · p95 96 ms · p99 100 ms, depuis l'histogramme du routeur de bordure, avec zéro réponse 5xx sur la période. La réserve qui compte : c'était à 0,43 req/s — cela prouve un chemin sain, pas de la résistance. Le test de stress, pousser jusqu'à saturation pour trouver le coude de la courbe, reste à faire
partielN-1 éprouvé en pratique, des dizaines de fois par jour. La mise en production canari draine un nœud de l'équilibrage pendant ~200 s (médiane) par service : 82 fenêtres en 72 h. Pendant celles-ci, le nœud restant a porté tout le trafic avec une charge entre 0,13 et 2,5 sur 8 vCPU et zéro réponse 5xx. La réserve : c'est du N-1 sous trafic normal, pas sous pic — le prouver sous charge maximale est le prochain exercice

Ingénierie de sécurité

faitSurface d'entrée supprimée par conception : pare-feu avec SSH seul, aucun port applicatif ouvert
faitIdentité maison, autorisation par portée dans chaque service et secrets au coffre avec un jeton par consommateur
faitIsolation client appliquée dans la base, fail-closed — elle ne dépend pas du code applicatif qui doit viser juste
partielRotation des identifiants : il existe une alerte d'expiration et une procédure, mais la rotation n'est pas automatique — et elle m'a déjà coûté une panne de connexion
pas encoreModélisation des menaces formelle, inventaire signé des dépendances et gestion continue des vulnérabilités. Il y a mise à jour des dépendances et revue, mais pas un processus avec artefact

Continuité et reprise

faitSauvegarde chiffrée hors fournisseur, à trois cadences : dump toutes les 3 h, base hebdomadaire et WAL continu
faitRestauration déjà exercée, et une alerte qui surveille l'absence de sauvegarde — pas seulement son échec
faitRTO chronométré et documenté : 12 min pour la flotte, 47 min pour un nœud de zéro — septembre 2026, chez le fournisseur actuel, avec un critère de bout en bout (s'authentifier et entrer dans le système). Ce qui manque pour en faire une preuve de portabilité : le répéter chez un autre fournisseur
pas encoreUn exercice de sinistre avec perte réelle du primaire, chronométrant la promotion manuelle de bout en bout. Le RPO d'aujourd'hui est un échantillon de retard de réplication, pas le résultat d'une panne provoquée

Gouvernance

faitLigne principale protégée, travail sur branche et publication par revue ; opérations destructrices bloquées par politique
faitRegistre de publication : date, digest et commit de chaque recréation de service
faitDécisions d'architecture consignées avec le coût que chacune impose — c'est l'Acte III de cette page
partielLa barrière qualité tourne sur le poste, par exécution manuelle. Elle est rigoureuse (vraie base, isolation armée, couverture qui fait échouer), mais ce n'est pas une chaîne automatisée de bout en bout : sans exécution distante à la revue, le filet de sécurité dépend de ma mémoire
  Pourquoi publier la colonne « pas encore ». Parce que l'alternative est pire. Qui évalue ce type de système demandera précisément le test de charge, le N-1 et l'exercice de sinistre — et découvrir la lacune en conversation vaut moins que la trouver écrite ici, avec la raison et l'étape suivante. Une lacune déclarée est un plan ; une lacune découverte est une surprise.

Envie de le voir tourner ?

Je peux démontrer une mise en production canari de bout en bout — y compris le retour arrière automatique quand la validation échoue.

contact@magnosilva.com LinkedIn Voltar ao início