Les concepts de Software as a Service ou de Cloud Computing sont de plus en plus répandus dans l’informatique d’aujourd’hui. Cela a pour conséquence que les éditeurs de logiciel ne vendent plus directement un logiciel mais le service que rend leur logiciel. Cette nouvelle façon de déployer du logiciel a changé la donne dans la façon dont sont conçus ces mêmes logiciels.
Vers un déploiement simplifié
Les architectures multi-tenant (multi-entités ou multi-locataires en français) ont vocation à faire en sorte qu’un logiciel soit capable de gérer un certain nombre de clients en une seule installation. Au lieu d’installer le logiciel une fois pour chaque client, ce dernier est capable de créer des environnement virtuels distincts pour chaque client de sorte à ce que de l’extérieur les autres environnements ne soient pas du tout visibles. En fin de compte, le logiciel répond lui-même aux problématiques de déploiement.
Dans le contexte d’un prestataire de service qui souhaite vendre son service à différents clients, l’architecture multi-tenant semble être la réponse évidente. Il suffit de déployer le logiciel une fois et de créer autant d’environnements que nécessaire, et le tour est joué. L’administration d’un tel système est, du coup, relativement simple.
Au prix d’une grande complexité dans le code
Je travaille en ce moment sur une application web basée sur une architecture multi-tenant. Et clairement ce type d’architecture a un coût dans le développement. Ce coût étant principalement dû à la complexité engendrée. C’est la raison pour laquelle je m’interroge à ce sujet.
Pour assurer l’isolation et l’indépendance des différentes instances, il me semble assez important de séparer les données de chaque instance dans une base de données dédiée. C’est une meilleure garantie de l’indépendance des données, mais c’est également pratique lorsqu’il s’agit de récupérer les données d’une instance pour transférer l’instance sur un autre serveur.
Tout cela implique qu’il n’est pas possible de considérer la base de données comme unique. On ne peut donc pas y accéder via un singleton dont on délègue d’ailleurs volontiers la gestion à notre outil de gestion de dépendances. Le connecteur à la base de données n’est plus un singleton, ce qui signifie que tous les composants qui s’en servent ne peuvent plus non plus être des singletons. D’ailleurs, la configuration du logiciel ne peut plus être non plus un singleton puisqu’il y en a une par instance. Bref, la perte de cette unicité limite très franchement le gain apporté par les outils et principalement ceux qui se chargent de l’injection de dépendance. Un peu comme si on retournait dans le passé.
Le code métier a bien souvent besoin de connaître sur quelle instance il travaille. Un objet représentant l’instance actuelle est ainsi passé en paramètre de la plupart des appels métier. La complexité de l’ensemble du code augmente ainsi fatalement et la testabilité prend du plomb dans l’aile. Difficile de faire du code simple dans une telle architecture.
De plus, une architecture multi-tenant ouvre une nouvelle gamme de risques de sécurité. Il n’est en effet pas envisageable qu’on puisse accéder à des données d’un autre environnement et encore moins de pouvoir les modifier. Pourtant, c’est le même code sur la même machine qui va gérer l’ensemble des environnements. Le risque est bien présent.
SaaS et architecture multi-tenant sont-ils vraiment compatibles ?
L’architecture multi-tenant a de nombreuses qualités quand il s’agit d’exploiter le logiciel en Software as a Service. Le logiciel sait nativement gérer lui-même l’ensemble des instances. Mais cela implique des contraintes auxquelles on ne pense pas forcément au départ.
Lors d’une mise à jour, si une régression est introduite, elle affectera immédiatement l’ensemble des clients. C’est un risque qu’il faut accepter de prendre, mais il peut être largement réduit en travaillant sur la qualité du logiciel. De la même façon, il est préférable de mettre à jour un logiciel à un moment où il est peu utilisé. Un logiciel multi-tenant implique de mettre tout le système à jour en même temps. Si les clients sont localisés un peu partout dans le monde, il est difficile de trouver un moment où tout le monde dort.
En général, un logiciel en SaaS est régulièrement mis à jour et évolue au fur et à mesure. Dans certains cas c’est tout à fait acceptable, si on considère par exemple que le service fourni par le logiciel se présente sous la forme de web services dont la rétrocompatibilité est assurée, les clients n’ont pas de raison de ne pas accepter les mises à jour. C’est davantage problématique quand le produit se présente sous la forme d’une interface graphique qui évolue régulièrement. Certains clients refusent catégoriquement que leur instance évolue et donc soit mise à jour.
Une architecture multi-tenant ne permet pas de mettre à jour certaines instances et pas d’autres. Il faut alors créer différentes installations du logiciel, chacun sur une version donnée. Et si on n’a pas de chance et qu’on n’arrive pas à trouver un petit nombre de versions pour satisfaire tout le monde, on se retrouve vite à déployer chaque client dans une installation distincte. Et là on a tout perdu. On a payé le surcoût lié à la complexité d’une telle architecture et on ne peut pas en profiter. Pire encore, on se retrouve à devoir quand même gérer une multitude d’installations, précisément ce qu’on souhaitait éviter au début.
Dev ou Ops ?
Se demander si les architectures multi-tenant sont pertinentes revient finalement à se demander où nous devons placer le curseur entre les parties Dev et Ops.
Dans les débuts de l’informatique, la moindre machine coûtait tellement cher qu’il était important d’optimiser chaque ligne de code assembleur des programmes de façon à économiser la moindre instruction processeur. Un gros investissement dans le développement des logiciels était nécessaire pour qu’ils soient capables de tourner sur des machines coûtant un prix raisonnable.
Mais, depuis ce temps-là, le monde de l’informatique a beaucoup changé. Les machines coûtent de moins en moins cher alors que leur puissance augmente, les réseaux se sont énormément développés, si bien qu’on n’achète même plus nous-même les machines mais qu’on paie un service d’hébergement (Infrastructure as a Service).
La virtualisation est venue ajouter une nouvelle dimension dans l’hébergement. Aujourd’hui, Docker est en passe de révolutionner une nouvelle fois ce domaine. On parle de plus en plus de Platform as a Service.
Tout cela est possible parce que le prix du matériel baisse sans cesse alors que sa puissance augmente. L’outillage s’est également adapté à ces changements et il est maintenant possible de gérer un parc de machines de manière quasiment automatique.
En parallèle de cette évolution en terme de technique, l’informatique est également de plus en plus populaire et même omniprésente. Mais le nombre de développeurs n’augmente pas aussi vite. Et jusqu’à nouvel ordre, il n’existe pas vraiment d’outils permettant d’automatiser le travail du développeur. Et heureusement pour nous !
Le curseur entre Dev et Ops s’éloigne petit à petit du développement. Il n’a jamais été aussi facile de déployer et d’exploiter du logiciel en masse (et ça sera certainement encore plus simple dans le futur).
Quel est le bon choix ?
Si la tendance actuelle se poursuit dans la même direction (ce qui me semble tout à fait probable), le déploiement sera de plus en plus automatisé et coûtera de moins en moins cher. Il me semble donc que dans le futur les architectures multi-tenant seront de moins en moins intéressantes.
Mais dès aujourd’hui, il me semble que le choix de partir sur une architecture multi-tenant n’est plus une évidence. Le logiciel en lui-même doit-il porter toutes les contraintes liées à son déploiement et la complexité qui va avec ? Grâce aux évolution des outils de déploiement, il est maintenant tout à fait possible de mettre en place en parallèle du logiciel une infrastructure de déploiement automatisée associée qui se charge de gérer ses différentes instanciations avec à chaque fois une configuration et une version bien précise. Ne vaut-il pas mieux développer deux systèmes distincts pour ne pas accumuler toute la complexité au même endroit ? Cela permet d’avoir à la fois un logiciel simple et toute la souplesse nécessaire au niveau du déploiement et de la gestion des différentes versions. N’est-ce finalement pas le moyen d’avoir le beurre et l’argent du beurre ?
L’image d’en-tête provient de Flickr.
En fait une méthode que je trouve efficace pour faire du multi-tenant applicable à Google App Engine dans un premier lieu est d’utilise le namespacing de la plateforme. https://cloud.google.com/appengine/docs/java/multitenancy/
Bon, c’est une réponse facile parce que GAE offre une solution native à un problème complexe. Si on réfléchie bien à comment cette solution est approché et qu’on regarde les dessous d’un produit comme AppScale: https://github.com/AppScale/appscale
On ce rends compte que la plupart des composants impliqués supportent le namespacing d’une manière ou d’une autre.
Pour les bases de données, il y a tellement de manières différentes que c’est difficile d’avoir une bonne manière, mais avec PostGreSQL, J’aime bien l’approche pas Schema, qui peut devenir lourde à supporter si on accepte des schema différent par client, mais reste relativement simple à supporter avec une bonne stratégie de migration des tables.
Bref, aucune solution magique et mon commentaire sur Twitter était peut-être trop optimiste dans le ton, par contre, utiliser une plateforme comme Google App Engine peut simplifier drastiquement toute cette gestion!
Merci pour ces explications. C’est intéressant d’avoir des retours d’expérience d’autres personnes qui sont confrontées aux mêmes problématiques.
La technique du namespace consiste en effet à déléguer la gestion des tenants à des outils. Elle va dans la même direction que celle que je suggérais, à savoir reposer sur une infrastructure capable de gérer ces problématiques plutôt que d’avoir du code qui doit prendre en compte toutes ces contraintes.
Pour ce qui concerne les bases de données, je crée moi aussi une base pour chaque tenant. L’inconvénient c’est qu’on n’a plus une base mais plusieurs et qu’on doit avoir les droits pour créer une nouvelle base. Mais c’est tellement plus pratique lorsqu’il s’agit de déplacer un tenant vers un autre serveur. Ça limite les risques de mélange de données entre les tenants et ça permet de ralentir la croissance en terme d’entrées dans la base et donc faciliter le passage à l’échelle.
Je n’ai pas suivi la succession des arguments, j’ai décroché à « Tout cela implique qu’il n’est pas possible de considérer la base de données comme unique »
Peux-tu détailler pourquoi le fait de gérer plusieurs bases séparées fait que le code qui gère ces bases ne peut pas être le même pour chaque instance ? N’y aurait-il pas besoin de mettre en place un pattern d’isolation ?
Bonjour.
Si tu isoles les données de chaque tenant dans une base dédiée, tu as besoin d’autant de connecteurs à la base de données que ce que tu as de tenants.
Si tu veux rester sur des singletons, il faut que dans tout ton code tu passes un identifiant du tenant en paramètre. A ce moment-là, il faut que les classes responsables de la persistance puissent accéder au connecteur (driver) qui correspond au bon tenant.
N’hésite pas à préciser si je ne réponds pas exactement à la question !
Une technique Django quand on est en PostgreSQL : https://github.com/tomturner/django-tenants Ça marche très bien, rien à modifier dans le code de l’appli (youpi), chaque tenant est stocké dans son schema PostgreSQL.
Sans doute une bonne source d’inspiration sur d’autres framework.
Une fois n’est pas coutume, un vieil article de chez Microsoft qui détaille quelques approches possible : https://msdn.microsoft.com/en-us/library/aa479086.aspx
Merci pour ce retour. Cet outil permet d’adresser la problématique des bases de données multiples pour Django, et d’après ce que je comprends, il répond à la problématique que j’ai citée dans mon article.
Mais récemment je suis tombé sur d’autres limitations liées au frameworks web quand on fait du multi-tenant. Voilà un exemple.
Je travaille sur une application multi-tenant. Certains tenants sont accessibles en https, d’autres non. Certains de nos clients ont fait un audit de sécurité et nous ont rapporté que nous ne mettions pas le paramètre secure sur les cookies. Il se trouve que nous utilisons Play Framework et que nous utilisons son API pour positionner les cookies dans les réponses HTTP. Mais Play Framework ne supporte pas le multi-tenant et il permet simplement de configurer le flag secure de manière globale, ce qui nous est impossible puisque ça signifieraient que les cookies ne sont pas opérationnels sur ceux qui sont en http.
Attention donc, tous les outils ne supportent pas correctement le multi-tenant, parfois des extensions s’en chargent mais ce n’est pas forcément le cas.
Bonjour,
Evidemment aucunes approches ne sauraient répondre au mieux à tous les cas de figure. Cependant, admettons que ton SAAS ne soit pas multi tenant, j’imagine que d’autres désavantages pourraient faire l’objet d’un article similaire, non ?
Quand tu dis : Si les clients sont localisés un peu partout dans le monde, il est difficile de trouver un moment où tout le monde dort.
Je ne vois pas ce qui est différent avec un monolithe ?
Par observation / expérience, je me demande si la dette technique et certaines décisions ne pourraient pas avoir aussi largement contribué au sentiment :
Difficile de faire du code simple dans une telle architecture.
Perso, j’ai un projet pour lequel je me pose la question, je ne vois pas comment je pourrais avec Postgres avoir une approche autre sans créer un monolithe dont les performances seraient mise à mal aux heures « de pointes ». De plus si je dois créer des comptes test API pour un client, il me sera facile de cloner les données.
J’imagine donc une solution kubernetes -> ingress -> pod / client avec chacun Backend / Frontend et DB.
De ce fait mon schema reste le même…
Je ne sais pas si cela à un intérêt mais mon Back est en Elixir/Phoenix avec un ORM.
En tout cas merci pour cet article, sympa de partager ton expérience.