Aller au contenu principal

Ressources

SaaS single-tenant : un environnement par client

Single tenant vs multi tenant : choisir ce qui est dédié à chaque client SaaS, puis créer, mettre à jour, restaurer et supprimer un environnement sans fork.

L'équipe CTN Solutions · publié le · 29 min de lecture

Faits vérifiés sur les sources primaires le

Un grand compte peut poser une condition à la signature : disposer de « son » environnement. L'éditeur SaaS (logiciel fourni en ligne par abonnement) promet alors un environnement par client avant d'avoir décidé ce qui sera réellement dédié, comment le créer à l'identique, le mettre à jour sans fork, restaurer ce seul client et le supprimer proprement. Chaque engagement qui n'est pas outillé peut devenir une procédure manuelle, puis une branche de code propre à un client, à maintenir à chaque version.

Le terme « single-tenant » ne dit pas quelle couche est dédiée. AWS déconseille d'ailleurs de l'employer : même une pile entièrement dédiée à un locataire (tenant) reste exploitée selon un modèle multi-locataire, avec une identité, un onboarding et une exploitation partagés. Dans cet article, « single-tenant » désigne un environnement dont certaines couches sont réservées à un seul client, et la question utile est de savoir lesquelles. L'article sépare ces couches, de l'application au compte cloud, puis suit le cycle de vie d'un environnement : création, mise à jour, restauration, mesure et suppression.

Faits datés

Versions et comportements cités (documentation Kubernetes v1.37, PostgreSQL 16, 18 et 19 en version candidate, Argo CD v3.6.1, guides d'AWS, de Microsoft et de Google Cloud) ont été vérifiés le sur les sources officielles listées en fin d'article ; le laboratoire du chapitre 5 a été reproduit le 18 octobre 2026 avec PostgreSQL 16.15.

Vérifié le

Définir ce qui doit être dédié à chaque client

Six niveaux se combinent, et chacun isole autre chose :

  • Tenant applicatif : le client est un identifiant de locataire porté par l'application, sur une infrastructure partagée ; le même code et la même base servent plusieurs clients.
  • Base de données dédiée : une base par client, sur une instance partagée ou sur une instance qui lui est réservée.
  • Namespace : dans un cluster Kubernetes, une unité logique de gestion qui regroupe les ressources d'un client.
  • Nœuds ou pool de nœuds dédiés : des machines qui n'exécutent que les charges d'un seul client.
  • Cluster dédié : un plan de contrôle et des nœuds propres au client.
  • Compte cloud dédié : un compte, ou un abonnement, qui porte les ressources et la facture d'un seul client.

Les guides d'AWS emploient un autre vocabulaire : une ressource « silo » est dédiée à un locataire, une ressource « pool » est partagée, et le modèle « bridge » mélange les deux, par exemple une couche web mutualisée devant une logique et un stockage dédiés. Silo et pool s'appliquent à toute la pile d'un locataire ou à une partie seulement.

Kubernetes n'a pas de notion native de locataire. Sa documentation présente le namespace comme un moyen de segmenter les charges d'un locataire en une unité logique de gestion, mais précise que l'isolation par namespace exige la configuration de plusieurs autres ressources, des plugins réseau et des bonnes pratiques de sécurité.

Deux confusions à écarter

  • L'isolation n'est pas l'authentification. Le livre blanc SaaS Architecture Fundamentals d'AWS rappelle qu'un utilisateur peut être authentifié et autorisé, et accéder pourtant aux ressources d'un autre locataire : rien dans l'authentification ni dans l'autorisation ne bloque nécessairement cet accès. Chaque requête doit donc être limitée aux données de son locataire, et ce contrôle se teste.
  • Partitionner n'est pas isoler. Le même livre blanc précise que partitionner les données ne garantit pas qu'elles soient isolées.

Ce qui reste partagé, et doit s'écrire

Même un environnement entièrement dédié repose sur des services communs : AWS range l'identité, l'onboarding, les opérations, le déploiement et les métriques dans un plan de contrôle global à tous les locataires. Le dossier de chaque client nomme donc ce qui reste partagé :

  • le plan de contrôle de l'éditeur et le processus d'onboarding ;
  • la chaîne de livraison et le registre d'images ;
  • l'identité, quand un fournisseur d'identité commun sert tous les clients (le choix de cet outil est traité dans « Keycloak, c'est quoi et quand le choisir (ou pas) face aux IdP managés et aux alternatives open source ») ;
  • la destination des sauvegardes, les journaux et les métriques ;
  • les outils d'administration et les accès de l'équipe d'exploitation ;
  • sur un cluster PostgreSQL partagé, les rôles : ce sont des objets globaux, communs à toutes les bases du cluster, et leur export couvre les rôles de tous les clients (chapitre 5).

Ce découpage vaut pour une flotte que l'éditeur exploite, éventuellement dans son propre cloud. La livraison du produit dans l'environnement du client, parfois sans accès à Internet, pose d'autres questions, traitées dans « Livrer votre SaaS on-premise ou en air gap : ce que le client va exiger ».

Ce que l'on dédie se décide par exigence écrite : clause du contrat, annexe de sécurité, résidence des données, performance mesurée. Un environnement dédié répond à une exigence ; il ne constitue ni une preuve de conformité, ni une certification.

Choisir entre namespace, cluster et compte cloud

Pour Azure Kubernetes Service (AKS), Microsoft rappelle que le modèle d'isolation retenu influe sur le niveau d'isolation, l'effort, la complexité opérationnelle et le coût. Les documentations d'AKS et de Google Kubernetes Engine (GKE) ajoutent que Kubernetes ne garantit pas une isolation parfaitement sûre entre locataires.

Le namespace : des objets séparés, un cluster commun

Un namespace par client sépare les objets d'API et porte le contrôle d'accès fondé sur les rôles (RBAC, Role-Based Access Control), les quotas et les politiques réseau. Il laisse pourtant plusieurs choses en commun :

  • le réseau : par défaut, tous les pods d'un cluster peuvent communiquer entre eux et le trafic n'est pas chiffré ; une NetworkPolicy n'est appliquée que si le plugin réseau (CNI, Container Network Interface) la prend en charge, sinon elle est ignorée. La documentation Kubernetes recommande une politique de refus par défaut, complétée par une règle qui autorise le DNS ;
  • le DNS du cluster, qui permet par défaut de résoudre les services de tous les namespaces ;
  • les ressources non namespacées, auxquelles l'isolation par namespace ne s'applique pas : définitions de ressources personnalisées (CRD, Custom Resource Definitions), StorageClasses, webhooks ; les nœuds et les PersistentVolumes n'appartiennent à aucun namespace ;
  • les nœuds eux-mêmes : selon le guide Amazon EKS, namespaces, RBAC, quotas et politiques réseau n'empêchent pas les pods de clients différents de partager un nœud.

Le guide Amazon EKS range ce modèle dans l'isolation « douce » (soft multi-tenancy) et rappelle qu'en SaaS, le client n'utilise pas l'API Kubernetes : c'est l'application de l'éditeur qui crée les objets nécessaires.

Des nœuds dédiés : moins de voisins bruyants, pas de frontière forte

Des nœuds réservés à un client se mettent en place avec des taints et des tolérances, des affinités et des sélecteurs de nœud. Selon la documentation Kubernetes, ils réduisent le risque de voisin bruyant (un client dont la charge dégrade celle des autres) et se refacturent plus simplement que des conteneurs isolés (sandbox), par nœud plutôt que par pod ; mais le kubelet et, sans plan de contrôle virtuel, le serveur d'API restent partagés, et un attaquant peut encore se déplacer latéralement. GKE, qui documente des pools de nœuds dédiés par taints, tolérances et affinités, précise que la séparation des charges ne doit jamais servir de frontière de sécurité principale.

Le cluster dédié : la frontière forte, au prix d'une flotte

Le guide Amazon EKS rappelle que Kubernetes est un orchestrateur mono-locataire, avec un plan de contrôle partagé, et que le cluster est la seule construction qui fournisse une frontière de sécurité forte. L'isolation forte (hard multi-tenancy) passe donc par un cluster par client. Le même guide en écrit les inconvénients : un plan de contrôle par cluster, aucun partage de calcul, une fragmentation des ressources, un outillage de flotte à acheter ou à construire, une création plus lente que celle d'un namespace. Il ajoute qu'elle peut être nécessaire dans des secteurs très régulés ou pour des SaaS qui exigent une isolation forte.

Microsoft décrit le même choix sous le nom de déploiements mono-locataires automatisés : un cluster et ses ressources par client, décrits en code (IaC, Infrastructure as Code) en envisageant le modèle Deployment Stamps, avec un serveur d'API séparé, un déploiement progressif et des coûts attribuables, mais un coût plus élevé et une maintenance à répéter sur chaque cluster. La documentation Kubernetes conseille aussi des noms de namespace uniques sur toute la flotte, même en clusters séparés, pour pouvoir passer plus tard du dédié au partagé.

Le compte cloud dédié : une frontière facile à décrire

Un compte AWS par client donne, selon AWS, une frontière d'isolation très facile à décrire. Quand le nombre de clients grandit, les limites sur le nombre de comptes et l'efficacité opérationnelle deviennent des contraintes ; un réseau privé virtuel (VPC, Virtual Private Cloud) par client est l'option suivante, avec ses propres limites à surveiller.

Sans Kubernetes : machines virtuelles ou services managés

Kubernetes n'est pas un prérequis. Le modèle Deployment Stamps de Microsoft, des copies indépendantes de l'application et de ses données, s'applique aussi bien à des machines virtuelles (IaaS, Infrastructure as a Service) qu'à des services managés (PaaS, Platform as a Service). Il permet d'isoler les données d'un client, d'héberger un grand client à part et de déployer par anneaux. Microsoft conseille de décrire l'infrastructure en code et de déployer au moins deux stamps. Quand le produit tient en quelques services et qu'aucun cluster n'est déjà exploité, cette voie peut suffire.

La matrice suivante applique ces niveaux à trois clients aux contraintes différentes ; coût et effort y sont relatifs.

Exemple : matrice d'arbitrage pour trois clients
Couche ou décisionClient AClient BClient C
Contrainte expriméeRestauration de ses seules données et export complet à la sortie ; aucune exigence sur l'infrastructureAnnexe de sécurité : aucun calcul partagé avec un autre client, région imposée, fenêtre de maintenance propreCompte cloud séparé à son nom (facturation et accès d'audit séparés), clés de chiffrement propres
Tenant applicatifIdentifiant de locataire, même codeMême code, même version que les autresMême code, même version que les autres
Base de donnéesBase dédiée sur une instance partagéeBase dédiée sur une instance dédiéeInstance dédiée dans le compte dédié
NamespaceNamespace dédié, politique réseau de refus par défautNamespace dédié, politique réseau de refus par défautNamespace dédié dans un cluster dédié
NœudsPartagésPool de nœuds dédié (taints, tolérances et affinités) ou cluster dédiéNœuds du cluster dédié
ClusterPartagéPartagé avec nœuds dédiés si l'annexe l'accepte, sinon dédiéDédié
Compte cloudCompte de l'éditeurCompte de l'éditeurCompte dédié
Alternative sans KubernetesPossible si le produit est simplePossible (stamp dédié de machines virtuelles ou de services managés)Possible (stamp dans le compte dédié)
Anneau de déploiementPrécoceTardif, dans sa fenêtreTardif, dans sa fenêtre
Ce qui reste partagéCluster, nœuds, instance de base, DNS du cluster, chaîne de livraison, plan de contrôlePlan de contrôle du cluster s'il est partagé, chaîne de livraison, registre, identité, supervisionChaîne de livraison, registre, plan de contrôle de l'éditeur, supervision centrale, outils d'administration
Preuves à produireRefus réseau et DNS testés depuis un autre namespace, restauration cibléePlacement vérifié (aucun pod d'un autre client sur ses nœuds), restauration cibléeAccès entre comptes limités au plan de contrôle, restauration dans le compte, suppression vérifiée
Coût et effort relatifsLes plus basIntermédiairesLes plus élevés (compte, cluster et instance par client)
Ce que ce choix ne prouve pasL'isolation des nœudsL'isolation du plan de contrôle si le cluster est partagéL'isolation des services restés partagés (chaîne de livraison, registre, plan de contrôle de l'éditeur, supervision centrale, outils d'administration)

Décider quand mutualiser et quand conserver le dédié

Dédier une couche se justifie par une exigence qui ne se satisfait pas autrement : une clause contractuelle explicite, une résidence des données, des clés de chiffrement propres, une cadence de mise à jour différente, un voisin bruyant mesuré. Mutualiser reste préférable quand l'isolation applicative et une base dédiée couvrent les exigences écrites, ou quand le coût d'une flotte devient disproportionné au regard du contrat.

AWS et Microsoft prévoient des modèles mixtes : le bridge d'AWS, le modèle partitionné verticalement de Microsoft, qui traite certains locataires en mutualisé et d'autres en dédié. Microsoft range aussi le déplacement d'un locataire vers une infrastructure dédiée, et le rééquilibrage, parmi les étapes de son cycle de vie : faites de ce passage une opération outillée et répétable. Revoyez périodiquement chaque environnement dédié : il se justifie par une clause, pas par l'habitude. La décision économique est traitée dans « Hébergement sur mesure : faut-il accepter ce client SaaS ? ».

Créer un environnement à partir d'un contrat déclaratif

Le SaaS Lens du cadre AWS Well-Architected demande d'intégrer chaque locataire par un processus unique, automatisé et reproductible, plus lourd en silo, puisqu'il faut provisionner l'infrastructure, et parfois relever des limites de compte, avant d'activer le client. Microsoft range parmi les anti-modèles le déploiement et les tests manuels et les personnalisations réservées à un locataire ; il recommande le même code et les mêmes types de ressources pour chaque client, et un déploiement cohérent et automatisé, ressources dédiées comprises.

La traduction pratique est un dossier client déclaratif, un fichier par client, versionné et validé avant tout déploiement, que lit une seule chaîne pour tous les clients ; il sert les deux façons de tenir la liste des locataires que décrit Microsoft, en configuration (fichiers ou paramètres de pipeline) ou en données (catalogue alimenté par un plan de contrôle).

Cycle de vie d'un environnement client dédié

De l'onboarding à la suppression, chaque étape part du dossier client et se termine par une vérification ou une preuve.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. Le dossier client déclaratif décrit l'environnement attendu, du niveau d'isolation à la politique de sauvegarde.
  2. La validation refuse un dossier incomplet ou hors catalogue ; le dossier revient en correction et rien n'est créé.
  3. L'environnement est créé par la même chaîne que pour tous les clients, puis les tests inter-clients vérifient le réseau, le DNS, les droits et le placement.
  4. Les mises à jour avancent anneau par anneau ; une santé dégradée arrête l'anneau avant le suivant.
  5. En exploitation courante, une restauration ciblée passe par une destination neuve et une garde de destination, puis par les contrôles métier et les témoins des autres clients avant toute reconnexion.
  6. L'offboarding est une étape voulue, close par une preuve de suppression enregistrée dans le dossier.
Modèle explicatif d'après la documentation Kubernetes, Microsoft Learn, AWS et PostgreSQL et le laboratoire du 18 octobre 2026.

Microsoft liste les points à décider dès l'onboarding : résidence des données, exigences de conformité, objectifs de reprise propres au client, c'est-à-dire la durée d'interruption admissible (RTO, Recovery Time Objective) et la perte de données admissible (RPO, Recovery Point Objective), besoin d'environnements de préproduction. Le modèle ci-dessous les porte, avec les autres paramètres de l'environnement.

Exemple : modèle de dossier client déclaratif.

# Dossier client déclaratif : exemple fictif, aucune donnée réelle.
apiVersion: environnements.exemple/v1
kind: DossierClient
metadata:
  client: client-c            # identifiant interne, jamais un nom commercial
  contrat: REF-A-RENSEIGNER   # référence du contrat ou de l'annexe de sécurité
spec:
  region: eu-west-3           # dans la liste fermée des régions proposées
  isolation:
    niveau: cluster           # tenant, namespace, pool-de-noeuds ou cluster
    compteCloud: dedie        # editeur ou dedie
    servicesPartages:         # ce qui reste commun, écrit noir sur blanc
      - chaine-de-livraison
      - registre-images
      - plan-de-controle-editeur
      - supervision-centrale
      - outils-administration
  application:
    version: "4.12.3"         # version de la ligne supportée, jamais une branche client
    anneau: tardif            # canary, precoce ou tardif
    fonctionnalites: []       # options activées par configuration, pas par fork
  taille:
    profil: M                 # profil du catalogue (quotas et répliques associés)
  domaine: client-c.example   # domaine réservé aux exemples
  preproduction: true         # environnement de préproduction prévu au contrat
  maintenance:
    fuseau: Europe/Paris
    jour: mardi
    debut: "06:00"
    fin: "08:00"
  sauvegarde:
    politique: standard               # nom d'une politique du catalogue
    objectifsDeReprise: au-contrat    # RTO et RPO propres au client
    restaurationCiblee: obligatoire   # restauration d'un seul client testée
    derniereRestaurationTestee: null  # date écrite par le test, jamais à la main
  offboarding:
    conservation: definie-au-contrat
    preuveDeSuppression: obligatoire

Avant toute création, la validation refuse un dossier incomplet ou hors catalogue : champ absent, valeur hors de la liste fermée, version hors de la ligne supportée, politique de sauvegarde sans restauration testée.

Dans une chaîne GitOps, le générateur Git « files » de l'ApplicationSet d'Argo CD (version 3.6.1) lit des fichiers JSON ou YAML d'un dépôt et en fait les paramètres d'un modèle ; les commits qui modifient ces fichiers sont découverts automatiquement. Un dossier par client peut ainsi produire une Application par client, sans copie de manifeste. Le mécanisme d'amorçage et ses précautions sont détaillés dans « Argo CD App-of-Apps et ApplicationSet : amorcer un cluster sans masquer les dépendances ».

Vérifier l'isolation avant d'ouvrir l'environnement

Microsoft demande de tester le modèle d'isolation. À chaque onboarding et après chaque changement de plateforme, rejouez les contrôles suivants sur le cluster réel :

  • depuis un pod du client A, une connexion vers un service du client B est refusée (politique de refus par défaut active, plugin réseau qui l'applique) ;
  • depuis le namespace du client A, la résolution DNS des services du client B est bloquée, ou le risque est accepté par écrit ;
  • kubectl auth can-i --list, lancé avec l'identité de chaque compte de service du client A (--as) dans le namespace du client B (--namespace), montre que ces comptes n'ont aucun droit dans ce namespace, ni sur les ressources de niveau cluster au-delà des droits de découverte accordés par défaut à tout compte authentifié ;
  • aucun pod d'un autre client ne tourne sur des nœuds dédiés (étiquettes, taints et tolérances vérifiés) ;
  • Secrets, PersistentVolumeClaims et StorageClasses ne sont pas partagés entre clients, et leur politique de récupération est connue ;
  • une requête applicative faite avec l'identité d'un utilisateur du client A sur un objet du client B est refusée : c'est l'isolation applicative qui est testée, pas seulement l'authentification.

Mettre à jour une flotte sans multiplier les forks

Un fork par client multiplie les versions à maintenir. Microsoft recommande d'éviter de forker ou de personnaliser l'infrastructure pour un locataire, et de préférer la configuration et les feature flags (fonctionnalités activées par configuration). AWS va plus loin pour le silo complet : aucune variation ni personnalisation ponctuelle, chaque pile exécute la même configuration et la même version, et une nouvelle version part vers l'environnement mutualisé comme vers chaque silo.

Les différences entre clients passent donc par le dossier : options activées, anneau de déploiement, fenêtre de maintenance. Jamais par une branche de code propre à un client.

Des anneaux et des fenêtres de maintenance

Le guide de Microsoft sur les mises à jour d'une solution multi-locataire part des exigences des clients (fenêtres de maintenance, contraintes, retour arrière) et décrit trois approches : les Deployment Stamps, les feature flags et les anneaux de déploiement (canary, adoption précoce, utilisateurs), qui diffusent une mise à jour progressivement.

Sur une flotte d'environnements dédiés, prévoyez trois anneaux : canary (environnements de test de l'éditeur), précoce (clients qui l'acceptent par contrat), tardif (clients qui imposent leur fenêtre). Une santé dégradée arrête l'anneau avant le suivant. AWS décrit une publication de la même version vers le pool et vers chaque silo, Microsoft un déploiement progressif : les deux convergent sur une version unique, pas sur le calendrier.

Savoir quelle version tourne chez chaque client

Microsoft insiste sur un point : savoir à tout moment quelle version de l'infrastructure, du logiciel ou d'une fonctionnalité chaque locataire utilise. Pour les données, son guide demande de planifier les mises à jour de schéma sur tout le parc, de suivre la version de schéma de chaque locataire et de garder l'application compatible avec au moins une version de schéma précédente. Un changement destructif se découpe alors en plusieurs versions, dont la dernière attend que tout le parc ait franchi la précédente.

Un exemple outillé : RollingSync d'Argo CD

Avec Argo CD v3.6.1, les Progressive Syncs d'un ApplicationSet offrent une mise en œuvre possible des anneaux. La stratégie RollingSync met à jour les Applications par groupes définis par des étiquettes, attend que toutes les Applications d'un groupe soient Healthy avant de passer au suivant, et borne le parallélisme par maxUpdate. Deux précautions : la fonction est à activer explicitement, en bêta selon la documentation v3.6.1, et elle désactive l'autosync des Applications générées. La place de cette chaîne dans une plateforme interne est traitée dans « Platform engineering pour une scale-up : par où commencer sans sur-ingénierie ».

Restaurer, mesurer et supprimer un seul environnement

La restauration d'un seul client dépend du découpage des données. Microsoft le dit pour une base partagée : restaurer un seul locataire peut exiger de restaurer la base dans une ressource séparée, puis d'y récupérer sélectivement les données de ce locataire ; des bases dédiées permettent une reprise plus fine. Dans les deux cas, la restauration vise une destination neuve, jamais l'environnement supposé endommagé, et se valide avant toute reconnexion de l'application.

Ce que la documentation PostgreSQL précise pour une base par client

  • pg_dump n'exporte qu'une base. Les objets globaux, communs à toutes les bases d'un cluster PostgreSQL (rôles, tablespaces, droits sur les paramètres de configuration), relèvent de pg_dumpall, dont l'option --globals-only les exporte seuls.
  • Le format personnalisé (-Fc) produit une archive destinée à pg_restore, compressée par défaut, qui permet de sélectionner et de réordonner les éléments à la restauration.
  • La restauration se fait dans une base réellement vide, créée depuis template0, et les rôles qui possèdent des objets ou ont reçu des droits doivent exister avant.
  • Avec --create, la base indiquée par -d ne sert qu'à émettre les DROP DATABASE et CREATE DATABASE initiaux : toutes les données vont dans la base dont le nom figure dans l'archive. Avec --clean en plus, cette base est supprimée puis recréée.
  • -d accepte aussi une chaîne de connexion, dont les paramètres l'emportent sur les options contraires de la ligne de commande.
  • Par défaut, pg_restore continue après une erreur SQL et affiche le nombre d'erreurs à la fin ; --exit-on-error l'arrête à la première. --single-transaction applique tout ou rien et implique --exit-on-error, mais le binaire refuse de le combiner avec --create (incompatibilité documentée à partir de la version 19, voir plus bas).

La documentation de pg_dump 18 le présente d'ailleurs comme un outil d'export et précise que, hors cas simples, il n'est généralement pas le bon choix pour les sauvegardes régulières de bases de production. Le laboratoire qui suit utilise donc pg_dump comme un mécanisme de démonstration, pas comme une politique de sauvegarde ; celle-ci, avec les objectifs de reprise, est traitée dans « Plan de reprise d'activité dans le cloud : un PRA n'est pas une politique de sauvegarde ».

Laboratoire : restaurer le client A sans toucher au client B

Le laboratoire a été reproduit le 18 octobre 2026 sur une machine virtuelle jetable sous Ubuntu 24.04.5 LTS, avec les binaires PostgreSQL 16.15 du paquet Ubuntu, tous outils dans la même version. Le scénario : deux clients, A et B, chacun dans sa propre base, sur un même cluster PostgreSQL joignable par un socket local seulement. Les deux bases ont le même schéma (formules, clients, factures, avec clés étrangères, contraintes d'unicité et de contrôle, séquence de numérotation, plus une table réservée au témoin) et des données synthétiques distinctes : 3 formules, 5 clients et 12 factures pour A ; 2 formules, 4 clients et 9 factures pour B.

Le protocole prend une référence de B (empreintes des tables stables, témoin qui écrit à chaque étape), exporte A avec pg_dump -Fc et les objets globaux avec pg_dumpall --globals-only, altère A (une écriture postérieure à l'export, deux factures supprimées, une formule changée), crée explicitement la destination client_a_restauree depuis template0, soumet des tentatives volontairement fausses à une garde de destination, restaure A sous garde pendant qu'une boucle du témoin écrit dans B, valide la copie et B, puis rejoue le piège --create sur un second cluster jetable.

La garde est un script Python qui lit la commande pg_restore proposée et refuse, avant toute connexion, ce que sa politique n'autorise pas. Elle échoue fermée : chaque refus sort avec le code 3, et une erreur imprévue l'arrête aussi sans lancer pg_restore. Sa première phase, sans connexion, contrôle l'environnement de l'appelant, n'accepte que les options d'une liste d'autorisation (toute autre option est refusée, y compris une option combinée comme -Cd), exige un seul -d sous forme de nom simple, puis vérifie la destination et l'archive (extrait ci-dessous) : empreinte SHA-256 égale au manifeste d'export, nom de base stocké égal à la source attendue. La deuxième vérifie en lecture seule, sur la seule destination autorisée, la base, le rôle, le socket, le port, l'identifiant système du cluster, l'identifiant d'objet (OID, Object Identifier) de la base, consigné à sa création et le fait que la destination est vide. La troisième lance le binaire épinglé avec un environnement nettoyé et les paramètres de connexion de sa politique, jamais ceux de l'appelant.

La politique du laboratoire n'autorise qu'une destination, client_a_restauree, et protège client_a, client_b, postgres, template0 et template1. Voici un extrait simplifié de la première phase de la garde.

# Extrait simplifié : phase 1 de la garde, avant toute connexion.
# options : options hors -d, formes courtes ramenées aux formes longues.
# Liste d'autorisation : toute autre option est refusée, dont --create,
# --clean, -h, -p, -U, -f et les options combinées comme -Cd.
AUTORISEES = {"--exit-on-error", "--single-transaction", "--verbose"}
if any(nom.startswith("PG") for nom in os.environ):
    refuser("variable PG* héritée de l'appelant")
if set(options) - AUTORISEES:
    refuser("option hors liste d'autorisation")
if len(cibles) != 1:
    refuser("un seul -d attendu")
cible = cibles[0]
if "=" in cible or "://" in cible:
    refuser("chaîne de connexion dans -d")
if cible in politique["protected_databases"]:
    refuser("base protégée")
if cible not in politique["allowed_destinations"]:
    refuser("destination hors liste d'autorisation")
if "--exit-on-error" not in options:
    refuser("--exit-on-error manquant")
if sha256(archive) != manifeste["sha256"]:
    refuser("archive différente de l'export")
# Chaque refus sort avec le code 3 : pg_restore n'est jamais lancé.

La commande acceptée par la garde reste simple : pg_restore -d client_a_restauree --exit-on-error archives/client_a.dump.

Laboratoire : résultats observés le 18 octobre 2026
ContrôleObservé
Garde de destination9 tentatives fausses refusées en code 3, sans lancement de pg_restore : huit avant toute connexion (dont client_b, une chaîne de connexion vers client_b, --create, PGDATABASE=client_b et -Cd), une après lecture seule (destination déjà restaurée)
Écritures pendant les refusJournal du serveur pendant les huit refus avant connexion : aucune ligne concernant client_b, une seule connexion (celle du marqueur, sur la base postgres) ; empreintes de B identiques avant et après
Restauration gardéeCode de sortie 0, aucune ligne ERROR dans la fenêtre de restauration
Objets et lignes4 tables, 3 séquences, 7 index ; 3, 5, 12 et 0 lignes par table ; empreintes md5 identiques à la référence
Contraintes et séquences11 contraintes identiques et validées ; séquences dans l'état de l'export ; tests négatifs (clé étrangère, deux contrôles CHECK, unicité) rejetés sans consommer de séquence
SchémaSchéma de l'archive (pg_restore -s) et schéma de la copie (pg_dump -s) identiques, soit 86 lignes significatives, après retrait des commentaires, des lignes vides et des lignes \restrict et \unrestrict
Contrôles métierVue par client identique à l'export ; aucune facture orpheline ; factures supprimées revenues ; formule modifiée rétablie ; écriture postérieure à l'export absente de la copie (attendu) et toujours présente dans la base endommagée
Client BEmpreintes identiques à la référence à chaque contrôle ; 22 écritures du témoin, synthèse métier constante (9 factures, dernier numéro 5008)
Durées observéespg_restore : 0,036 s ; du début de la restauration, garde comprise, à la fin de la validation : 2,919 s (valeurs d'une exécution)

Depuis PostgreSQL 16.10, pg_dump et pg_restore, quand ils produisent une sortie texte, y écrivent une commande psql \restrict à jeton aléatoire, refermée en fin de fichier par \unrestrict (correctif de la vulnérabilité CVE-2025-8714) : deux sorties texte d'un même schéma diffèrent par ce jeton, d'où le retrait de ces deux lignes avant comparaison. Enfin, la restauration a été si courte qu'une seule écriture de la boucle du témoin est tombée dans sa fenêtre ; les autres l'ont suivie.

Le piège de --create, observé sur un cluster jetable

Le piège a été rejoué sur un second cluster jetable, avec la même archive, pour laisser pg_restore créer une base nommée client_a sans toucher à celle du scénario. La table des matières de l'archive ne montre l'entrée de type DATABASE, au nom de client_a, qu'avec pg_restore -l --create.

Laboratoire : le piège --create et les cas voisins, observés le 18 octobre 2026 (PostgreSQL 16.15)
CasOptionsObservé
Cible -d postgres, rôles présents--create --exit-on-errorpg_restore crée client_a, s'y reconnecte et y restaure tout ; la base postgres, cible de -d, ne reçoit aucun objet
Base client_a existante et altérée--create, sans --exit-on-errordatabase "client_a" already exists, puis 20 autres erreurs et errors ignored on restore: 21, code 1 ; trois setval exécutés quand même, dont deux ramènent les séquences de la base existante en arrière (numéros de 1012 à 1011, identifiants de 13 à 12) ; l'insertion suivante échoue sur l'unicité du numéro de facture
Même départ--create --exit-on-errorArrêt sur la première erreur ; base existante inchangée
Même départ--clean --create --exit-on-errorBase existante supprimée puis recréée depuis l'archive, code 0 ; l'écriture postérieure à l'export est perdue
Les deux options ensemble--create --single-transactionRefus immédiat du binaire 16.15 : options -C/--create and -1/--single-transaction cannot be used together
Rôles absents, base vide--exit-on-errorArrêt sur role "app_a" does not exist, code 1 ; le schéma déjà créé reste en place : restauration partielle
Rôles absents, base vide--single-transactionMême erreur, code 1 ; rien ne reste
Objets globauxpsql -f globals.sqlCréation des rôles app_a et app_b : le fichier contient les rôles de tous les clients du cluster

Le binaire 16.15 refuse déjà la combinaison de --create et de --single-transaction, alors que la page de documentation de pg_restore ne mentionne cette incompatibilité qu'à partir de la version 19, encore en version candidate le 18 octobre 2026.

Ce que le laboratoire prouve, et ce qu'il ne prouve pas

Pour ce scénario, ces binaires et ces données, il prouve qu'un export logique restauré dans une destination neuve et explicite reproduit l'export d'une base dédiée, qu'une garde simple, qui refuse avant toute connexion ce que sa politique n'autorise pas, arrête une destination fausse sans écriture, que -d ne protège pas la cible quand --create est présent, qu'une fin de commande n'est pas une validation, et que l'export des rôles couvre ceux des autres clients.

Il ne prouve pas :

  • un RTO : la durée observée, 0,036 s pour quelques dizaines de lignes, ne se transpose à aucun volume réel ;
  • un RPO contractuel : le point restauré est l'instant de l'export ;
  • une stratégie de sauvegarde de production, une restauration à un instant donné (PITR, Point-In-Time Recovery) ou une sauvegarde physique ;
  • l'isolation de clients mélangés dans les lignes d'une même table, ni celle d'un namespace, d'un nœud, d'un cluster ou d'un compte cloud ;
  • le comportement d'une vraie application reconnectée à la copie (les contrôles métier sont des requêtes SQL), ni la charge d'une restauration volumineuse sur une instance partagée.

Restauration ciblée : la liste de contrôle

Au moins une fois par client, et après tout changement de la chaîne de sauvegarde :

  • l'archive est identifiée par son empreinte et par le nom de sa base source, et une référence des objets et des lignes est prise à l'export ;
  • la destination est neuve, créée explicitement depuis template0 et inscrite dans une liste d'autorisation ; l'identité du cluster, du rôle et du point de connexion est vérifiée ;
  • ni --create ni --clean, aucune chaîne de connexion dans -d, aucune variable PG* héritée ; --exit-on-error ou --single-transaction ;
  • les rôles et les autres objets globaux existent avant la restauration et sont traités à part ;
  • la copie est contrôlée : lignes et empreintes par table, contraintes validées, séquences, contrôles métier de l'application ; les écritures postérieures à l'export sont décrites séparément ;
  • les témoins du client voisin (empreintes des tables stables, requête métier) sont identiques avant, pendant et après ;
  • une validation interne précède toute reconnexion de l'application à la copie, et la durée est consignée sans être présentée comme un RTO.

Mesurer ce que coûte un environnement dédié

Selon Microsoft, des ressources dédiées s'imputent par étiquettes, groupes de ressources ou abonnements, alors que l'imputation devient très complexe sur des ressources partagées, et la documentation Kubernetes note que des nœuds dédiés se refacturent par nœud plutôt que par pod ; le calcul de rentabilité d'un client dédié relève de l'article sur l'hébergement sur mesure cité au chapitre 2.

Supprimer un environnement et le prouver

Microsoft distingue la désactivation, un état temporaire, de l'offboarding, qui demande de fixer la durée de conservation des données du client et de prévoir un éventuel retour. Trois mécanismes rendent cette étape délicate :

  • avec la politique par défaut (sync) d'Argo CD v3.6.1, le contrôleur ApplicationSet supprime une Application qui n'est plus générée, et ses ressources avec elle, sauf preserveResourcesOnDeletion: true ; la politique create-update empêche ces suppressions (mais pas la suppression de l'ApplicationSet lui-même, selon la même documentation). Retirer le dossier d'un client du dépôt peut donc supprimer son environnement : l'offboarding doit être une étape voulue ;
  • un PersistentVolume (PV) appartient au cluster, pas au namespace de sa demande (PVC, PersistentVolumeClaim) : avec la politique Retain, le volume reste « released » après la suppression du PVC, et la ressource de stockage externe survit à celle du PV ; Delete supprime les deux, et les volumes dynamiques héritent de la politique de leur StorageClass, Delete par défaut ;
  • les copies de sauvegarde et les journaux suivent leur propre durée de conservation, fixée au contrat.

À chaque offboarding :

  • le dossier du client est retiré par une étape voulue, avec une politique ApplicationSet connue et des protections levées en connaissance de cause ;
  • namespace, charges et secrets sont supprimés ; PersistentVolumes et ressources de stockage externes sont vérifiés, cas Retain compris ;
  • base, instance ou compte sont supprimés selon le niveau d'isolation ; enregistrements DNS, certificats, accès d'administration et identités techniques sont révoqués ;
  • les copies de sauvegarde et les journaux suivent la durée de conservation du contrat, et leur date de fin est consignée ;
  • une preuve de suppression est enregistrée dans le dossier client.

Pour industrialiser la chaîne de livraison qui alimente ces environnements, la page Éditeurs SaaS : livrer on-premise et vendre via AWS Marketplace décrit ce que nous concevons et industrialisons : les paquets, les registres et une chaîne de livraison versionnée, qui produit les paquets et les preuves attendus de la même manière pour chaque client. L'éditeur conserve le code, la feuille de route et le support produit ; l'exploitation de la chaîne de livraison dans la durée relève de l'infogérance cloud et du maintien en conditions opérationnelles (MCO).

Sources officielles et limites de lecture

Les passages cités de pg_restore sont identiques dans les versions 16.15 et 18.6.

  • AWS signale que les livres blancs SaaS Architecture Fundamentals (2022) et SaaS Tenant Isolation Strategies (2020) sont conservés à titre de référence historique et que certains contenus peuvent être datés ; le second n'est plus disponible qu'en PDF, ses pages web renvoyant vers le SaaS Lens. Le SaaS Lens date de décembre 2020 (dernière mise à jour majeure le 4 avril 2023).
  • Dans le laboratoire, la restauration passe par le superutilisateur avec une authentification trust, acceptable pour un laboratoire jetable seulement.
  • Le statut bêta des Progressive Syncs d'Argo CD est celui de la documentation v3.6.1 ; relisez-le à chaque nouvelle version.

Sources

  1. Multi-tenancy, Kubernetes Documentation, https://kubernetes.io/docs/concepts/security/multi-tenancy/, consulté le 08/11/2026.
  2. Namespaces, Kubernetes Documentation, https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/, consulté le 08/11/2026.
  3. Persistent Volumes, Kubernetes Documentation, https://kubernetes.io/docs/concepts/storage/persistent-volumes/, consulté le 08/11/2026.
  4. Tenant Isolation, Amazon EKS Best Practices Guide, Amazon Web Services, https://docs.aws.amazon.com/eks/latest/best-practices/tenant-isolation.html, consulté le 08/11/2026.
  5. Tenant isolation, SaaS Architecture Fundamentals, Amazon Web Services, https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/tenant-isolation.html, consulté le 08/11/2026.
  6. Data partitioning, SaaS Architecture Fundamentals, Amazon Web Services, https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/data-partitioning.html, consulté le 08/11/2026.
  7. Removing the single-tenant term, SaaS Architecture Fundamentals, Amazon Web Services, https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/removing-the-single-tenant-term.html, consulté le 08/11/2026.
  8. Control plane vs. application plane, SaaS Architecture Fundamentals, Amazon Web Services, https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/control-plane-vs.-application-plane.html, consulté le 08/11/2026.
  9. Full stack silo and pool, SaaS Architecture Fundamentals, Amazon Web Services, https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/full-stack-silo-and-pool.html, consulté le 08/11/2026.
  10. Silo, Pool, and Bridge Models, SaaS Lens, AWS Well-Architected Framework, https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/silo-pool-and-bridge-models.html, consulté le 08/11/2026.
  11. Bridge model, SaaS Lens, AWS Well-Architected Framework, https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/bridge-model.html, consulté le 08/11/2026.
  12. General design principles, SaaS Lens, AWS Well-Architected Framework, https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/general-design-principles.html, consulté le 08/11/2026.
  13. Silo isolation, SaaS Lens, AWS Well-Architected Framework, https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/silo-isolation.html, consulté le 08/11/2026.
  14. SaaS Tenant Isolation Strategies, version PDF, sections Account silo isolation (p. 15) et Virtual Private Cloud (VPC) silo isolation (p. 16 et 17), Amazon Web Services, https://docs.aws.amazon.com/pdfs/whitepapers/latest/saas-tenant-isolation-strategies/saas-tenant-isolation-strategies.pdf, consulté le 08/11/2026.
  15. Full stack isolation, SaaS Lens, AWS Well-Architected Framework, https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/full-stack-isolation.html, consulté le 08/11/2026.
  16. Use Azure Kubernetes Service (AKS) in a multitenant solution, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/service/aks, consulté le 08/11/2026.
  17. Considerations for updating a multitenant solution, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/updates, consulté le 08/11/2026.
  18. Architectural approaches for the deployment and configuration of multitenant solutions, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/deployment-configuration, consulté le 08/11/2026.
  19. Architectural approaches for storage and data in multitenant solutions, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data, consulté le 08/11/2026.
  20. Tenant life cycle considerations in multitenant solutions, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/tenant-life-cycle, consulté le 08/11/2026.
  21. Deployment Stamps pattern, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/patterns/deployment-stamp, consulté le 08/11/2026.
  22. Measure consumption, Azure Architecture Center, Microsoft Learn, https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/measure-consumption, consulté le 08/11/2026.
  23. Cluster multi-tenancy, Google Kubernetes Engine, Google Cloud, https://docs.cloud.google.com/kubernetes-engine/docs/concepts/multitenancy-overview, consulté le 08/11/2026.
  24. Isolate your workloads in dedicated node pools, Google Kubernetes Engine, Google Cloud, https://docs.cloud.google.com/kubernetes-engine/docs/how-to/isolate-workloads-dedicated-nodes, consulté le 08/11/2026.
  25. Configure workload separation in GKE, Google Kubernetes Engine, Google Cloud, https://docs.cloud.google.com/kubernetes-engine/docs/how-to/workload-separation, consulté le 08/11/2026.
  26. pg_dump, PostgreSQL 16 Documentation, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/16/app-pgdump.html, consulté le 08/11/2026.
  27. pg_dump, PostgreSQL 18 Documentation, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/18/app-pgdump.html, consulté le 08/11/2026.
  28. pg_restore, PostgreSQL 16 Documentation, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/16/app-pgrestore.html, consulté le 08/11/2026.
  29. pg_restore, PostgreSQL 18 Documentation, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/18/app-pgrestore.html, consulté le 08/11/2026.
  30. pg_restore, PostgreSQL 19 Documentation (RC1), The PostgreSQL Global Development Group, https://www.postgresql.org/docs/19/app-pgrestore.html, consulté le 08/11/2026.
  31. pg_dumpall, PostgreSQL 16 Documentation, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/16/app-pg-dumpall.html, consulté le 08/11/2026.
  32. 25.1. SQL Dump, PostgreSQL 16 Documentation, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/16/backup-dump.html, consulté le 08/11/2026.
  33. Release 16.10, PostgreSQL release notes, The PostgreSQL Global Development Group, https://www.postgresql.org/docs/release/16.10/, consulté le 08/11/2026.
  34. pg_restore.c (tag REL_16_15), PostgreSQL source, https://github.com/postgres/postgres/blob/REL_16_15/src/bin/pg_dump/pg_restore.c, consulté le 08/11/2026.
  35. Git Generator: Files, Argo CD v3.6.1, Argo Project, https://github.com/argoproj/argo-cd/blob/v3.6.1/docs/operator-manual/applicationset/Generators-Git.md, consulté le 08/11/2026.
  36. Progressive Syncs, Argo CD v3.6.1, Argo Project, https://github.com/argoproj/argo-cd/blob/v3.6.1/docs/operator-manual/applicationset/Progressive-Syncs.md, consulté le 08/11/2026.
  37. Controlling Resource Modification, Argo CD v3.6.1, Argo Project, https://github.com/argoproj/argo-cd/blob/v3.6.1/docs/operator-manual/applicationset/Controlling-Resource-Modification.md, consulté le 08/11/2026.
  38. Application Pruning & Resource Deletion, Argo CD v3.6.1, Argo Project, https://github.com/argoproj/argo-cd/blob/v3.6.1/docs/operator-manual/applicationset/Application-Deletion.md, consulté le 08/11/2026.
  39. Using RBAC Authorization, section API discovery roles, Kubernetes Documentation, https://kubernetes.io/docs/reference/access-authn-authz/rbac/#discovery-roles, consulté le 08/11/2026.
  40. Authorization, section Checking API access, Kubernetes Documentation, https://kubernetes.io/docs/reference/access-authn-authz/authorization/#checking-api-access, consulté le 08/11/2026.

Parlons de votre contexte.

Cet article expose une pratique générale ; votre situation a ses propres contraintes. Décrivez-la, nous répondons avec un périmètre.

Ouvrir le formulaire