Déploiement industriel d’un serveur ASP.NET avec Mono et Apache

Introduction

La maturité technologique de Mono dans sa toute dernière version stable 2.4.3 est incontestable au vu de la roadmap parcourue et du pourcentage atteint sur les réécritures de classes du framework. Mais qu’en est-il de la configuration fine, des performances, de la montée en charge ? Bref, Mono est-il prêt au passage en production de niveau industriel ?

Depuis plusieurs années déjà, la pile ASP.NET est complète, et toutes les classes de base sont reprises. La prise en charge de WinForms a bien sûr posé plus de problèmes, ce qui est tout à fait compréhensible vu les différences fondamentales entre Win32 et X11. Les détracteurs de Mono supposent que ce dernier passera toute son existence à implémenter six mois après Microsoft les nouveautés de .NET. C’est oublier que Mono propose des librairies .NET spécifiques à Linux ou à certains métier.

Ceci étant dit, il est rare d’entendre parler d’un hébergeur proposant Mono, ou d’un éditeur de logiciel supportant ses solutions sous cet environnement. Pourquoi aussi peu de références ? Le système est pourtant suffisamment mature, et dispose de sérieux atouts, en particulier en termes de performance. C’est en tout cas le retour d’expérience que ma société a obtenu, lors de la mise en place de Mono dans le cadre d’un banc de test d’industrialisation de la solution.

Objectifs

L’objectif de ce banc de test était de vérifier la faisabilité du portage industriel du serveur de web services écrits en ASP.NET sur un environnement Linux. J’insiste sur ce caractère d’industrialisation : le fonctionnement d’une application ASP.NET simple sous Linux est déjà opérationnel depuis longtemps, et la migration ne nécessite qu’une recopie du contenu du répertoire virtuel. Soit. Mais sortons d’une application simple, et étudions désormais un ‘vrai’ serveur SOA. Comment fonctionne l’authentification ? Quid de l’impersonification du processus ? La connexion à la base de données nécessite-t-elle une configuration particulière ? Où se trouvent les fichiers de configuration en Mono ?

Nous avons du éplucher les forums et tâtonner de longues journées pour trouver les réglages expliqués ci-dessous : nous espérons que les réunir en un article aidera à promouvoir Mono à un rang ‘valide pour applications industrielles’. Nous verrons plus loin qu’il dispose de vrais avantages sur la pile .NET officielle fournie par Microsoft.

Préconfiguration

Les tests ont été menés sur une distribution OpenSuse 10.3, ainsi que sur une CentOS 5 (base RedHat). On détaillera dans le présent article l’installation pour l’OpenSuse. L’OS est installé avec les options ‘Développement C/C++’, ‘Développement .NET’, ‘Serveur Web et LAMP’. Apache est en version 2.0.13, Mono en 2.4.3. On ne l’installe pas à partir de l’OS car la version proposée est une vieille 1.2.6. Les versions 11 de SuSE propose la version 1.9, à ma connaissance.

1

Bien que l’installation du client Oracle soit censée être simplifiée par la présence d’un script autonome se chargeant de la mise en place complète de l’environnement, celui-ci nous a posé de nombreux problèmes, et nous avons finalement réalisé une bonne partie de la configuration à la main. Mais ce n’est pas le présent sujet d’étude.

Installation de l’application

Comme nous l’avions expliqué précédemment, le contenu du serveur virtuel a purement et simplement été recopié depuis une version Windows basée sur IIS. Le serveur virtuel avait été déployé en mode précompilé, avec un fichier .asmx et plusieurs fichiers dll correspondant aux classes utilisées et référencées par l’implémentation de ce service. Un fichier de configuration de l’application est également présent, pour porter les settings nécessaires à notre serveur. Le tout est recopié dans /srv/www/htdocs pour les tests.

Configuration de MOD_MONO

Le module MOD_MONO doit être installé dans Apache pour que Mono puisse traiter l’ASP.NET à l’intérieur de ce serveur HTTP. La mise en place du module est assez simple sous SuSE et on ne détaillera pas.

Il est par contre essentiel de configurer /etc/apache2/conf.d/mod_mono.conf en rajoutant en fin de fichier la ligne

MonoServerPath :/usr/bin/mod-mono-server2

Suite à de nombreuses tentatives infructueuses, nous avons réalisé que le plus simple pour que Mono retrouve la librairie correspondant au client Oracle était de mettre l’adresse en absolu dans le fichier /etc/mono/config, où la ligne

<dllmap dll="oci" target="libclntsh.so" os="!Windows"/>

est alors remplacée par

<dllmap dll="oci" target="/opt/oracle/product/lib/libclntsh.so" os="!Windows"/>

Il doit être possible d’affecter le LD_LIBRARY_PATH de l’utilisateur Apache pour que cette librairie soit trouvée sans avoir à préciser le chemin en dur, mais je n’ai pas trouvé dans quel fichier de configuration il faudrait mettre cette information. Si vous avez une solution, vous êtes évidemment les bienvenus.
Une fois les manipulations effectuées, ne pas oublier de relancer Apache, avec

service apache2 restart

Connexion à la base de données

Nous ne sommes pas au bout de nos peines. Pour la même raison que précédemment, nous n’avons pas affecté la variable ORACLE_HOME pour l’utilisateur Apache de notre serveur de services web.

Il doit y avoir moyen, mais suite à plusieurs heures de recherche infructueuse, nous nous sommes finalement rabattus vers une solution toute aussi fonctionnelle, bien que moins élégante, et qui consiste à utiliser du code .NET au démarrage de l’application pour affecter la variable d’environnement :

Environment.SetVariable("ORACLE_HOME", "/opt/product/oracle/R11g1/");

Une fois cette étape passée, nous avons pu pour la première fois nous connecter à notre serveur de web services et utiliser notre client de manière fonctionnelle. Nous avons été frappés dès le début par la performance de l’ensemble.

Aucune remarque sur la gestion de session, qui est indispensable pour garder les informations de connexion dans le cas d’une identification propriétaire. Aucune modification nécessaire dans le fichier de configuration ni sur l’installation pour que celle-ci fonctionne. Nous n’avons pas encore testé les modes STATESERVER et SQLSTATESERVER, permettant de déporter la gestion des sessions sur un service Windows ou dans une base de données, mais le mode par défaut INPROC est plus performant, et donne entière satisfaction.

Quelques premiers tests plus loin, et toujours pas de différence avec un serveur Windows. Il a fallu arriver dans des parties métier plus avancées pour rencontrer des erreurs. La première est apparue sur un appel de procédure stockée, plus précisément sur la méthode DeriveParameters() de la classe OracleCommandBuilder, utilisée pour récupérer les paramètres de la procédure stockée. A l’appel de cette méthode, nous avons obtenu l’erreur suivante :

“The requested feature is not implemented.”

Après vérification dans le code source de Mono, il apparaît effectivement que cette méthode n’est pas encore implémentée. J’avais envie de rajouter « avis aux amateurs », mais il faut savoir que le provider Oracle de Mono n’est pas très activement maintenu. Il semble que ce ne soit pas une priorité pour Novell. Suite à ce problème (ainsi que d’autres que je détaillerai éventuellement dans un prochain article), la personne anciennement en charge de cette fonctionnalité m’a conseillé de me tourner vers un provider commercial Oracle fonctionnant en mode Mono. Il y en a plusieurs, et nous avons adopté celui de Devart. Que les aficionados de l’OpenSource ne m’en veuillent pas trop : je promets de repasser au provider libre dès que celui-ci sera d’une qualité suffisante, mais ma priorité numéro 1 est que les logiciels que ma société édite fonctionnent bien.

Pour contourner cette restriction, nous avons décidé de créer nous-mêmes les paramètres attendus et avons rencontré un nouveau problème lors de la création des paramètres Oracles pour l’appel de la procédure stockée :

“Size is not set.”

Erreur d’autant plus étrange que nous avions bien précisé la taille du paramètre dans le constructeur avec la ligne suivante :

OracleParameter op = new OracleParameter("coucou", TypeParam, 32);

Il semble que la taille ne soit pas prise en compte. La solution a consisté à forcer les tailles de données dans chacun des paramètres :

OracleParameter op = new OracleParameter("coucou", TypeParam, 0);op.Size = 32;

Une autre erreur a été localisée sur le fournisseur Oracle pour ADO.NET fourni par Mono : la surcharge suivante de OracleParameter n’existe pas :

OracleParameter(string, type, string, bool, int, int, string, direction)

Ce n’est pas à proprement parler un problème si on développe directement en Mono, mais cette signature est utilisée dans le code généré sur un DataSet typé en provenance de Microsoft Visual Studio .NET 2005. La qualité de Mono est telle qu’on s’attend presque automatiquement à ce que toutes les surcharges soient disponibles.

C’est dans ce genre de contexte que l’utilisation de code Open Source amène un plus incomparable. Nous avons pu récupérer le code source de OracleParameter, rajouter le constructeur voulu et tout recompiler. La modification a bien sûr été proposée à l’équipe Mono par l’intermédiaire du site BugZilla dédié, pour intégration si elle satisfait aux critères de qualité.

Clickonce

Vu que le serveur applicatif fonctionne, pourquoi ne pas également installer sous Linux notre serveur de déploiement ? Là, les choses sont encore plus simples. Il faut spécifier les types mimes correspondant aux extensions liées à ClickOnce, dans le fichier /etc/apache2/mimes.types, en ajoutant les lignes suivantes :

AddType application/x-ms-application application
AddType application/x-ms-manifest manifest
AddType application/octet-stream deploy
AddType application/x-msdownload dll

Un premier test nous remonte toutefois l’erreur ci-dessous :

2

Le problème vient du fait que Mono prend bien en charge les fichiers de configuration, mais refuse de les laisser remonter par défaut sur un appel de client, pour des raisons de sécurité (présence possible de mots de passes, de chaînes de connexion, etc.). Il faut ouvrir cette possibilité pour le fichier .exe.config de notre client à déployer par ClickOnce, en ajoutant dans le fichier /etc/mono/2.0/web.config la ligne suivante :

<add verb="*" path="*.exe.config" type="System.Web.StaticFileHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

Le même problème existe sur les fichiers de type dll, et il faudra mettre en commentaires la ligne ci-dessous :

<!--<add verb="*" path="*.dll" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />-->

En effet, le Handler par défaut pour ce filtre de fichier est forbidden, à savoir que Mono interdit que ce fichier soit remonté lors d’une demande client. A nouveau, ceci est compréhensible du point de vue de la sécurité, mais dans le cas d’un déploiement ClickOnce, nous avons besoin de remonter nos dlls. Si cela pose vraiment problème, il est toujours possible de renommer les fichiers .dll en .dll.download. Le processus ClickOnce supporte d’ailleurs cette façon de télécharger les références.

Performances

Le premier avantage d’une utilisation de Mono / Apache par rapport à Microsoft .NET / IIS est sans conteste la performance brute mono-utilisateur. Attention, je ne parle pas de montée en charge, mais uniquement du temps de réaction du serveur sans aucune charge qu’un seul utilisateur. Dans un premier temps, nous n’avons pas passé trop de temps à déterminer si le serveur web ou la pile Mono était responsable de cette différence, vu que les deux sont plus ou moins liés.

La première installation de Linux sur une machine à faible capacité nous a surpris avant même de mettre en place les traces chronométrées : la différence de temps de chargement était visible sans même avoir à mesurer quoi que ce soit. Notre première réaction a été de vérifier les caractéristiques de la machine utilisée, car à force de virtualiser et de faire du déport d’affichage, on finit parfois par ne plus bien se rappeler des spécifications hardware… Elles étaient largement inférieures à notre machine de référence, pourtant plus lente à exécuter les mêmes services web. Nous avons alors éliminé les possibilités d’erreurs : pointage des deux serveurs sur la même base de données, mise en place sur la même machine, désactivation de tous les services d’un côté comme de l’autre, pour arriver à la conclusion suivante : OpenSuse 10.3 + Apache 2.0 + Mono 2.4.3 est environ 20% plus rapide que Windows 2003 Server + IIS 6.0 + Microsoft .NET 2.0, à machine équivalente, pour des actions unitaires.

Nous ne prétendons pas avoir mené un benchmark exhaustif et pouvoir analyser finement les différences, voire les prédire en fonction d’autres contextes. Mais bon, les chronomètres font foi, et quelles que soient les raisons ou précautions à prendre sur ces résultats, en mode mono-utilisateur, notre serveur est 20% plus rapide avec Mono.

Attention, à nouveau : je parle bien d’un mode mono-utilisateur, qui n’a absolument rien à voir avec la réalité d’un serveur de services web. Lors de la montée en charge, on a constaté que Apache / Mono ne répondait pas aussi bien que IIS / MS.NET, et même loin de là si on n’utilise pas les extensions Novell pour la performance de Mono. Je ferai peut-être un article plus détaillé là-dessus un de ces jours, mais à la louche, on tient deux fois moins d’utilisateurs dans le premier mode que dans le deuxième.

Petite remarque sur les extensions Mono : Novell vend une extension de SuSE Entreprise pour améliorer les performances de Mono. Je ne sais pas ce qui se passe en dessous, si il y a de l’optimisation des librairies système, de Mono, de MOD-Mono ou d’Apache, mais le fait d’installer ces extensions a donné un surplus de performance réellement impressionnant. Pour être honnête, la différence de montée en charge sans ces extensions était telle qu’on ne pouvait pas décemment remplacer des serveurs Windows / IIS par ceux en Linux / Apache / Mono. Avec, il manque encore de la performance, mais à la vitesse où Mono évolue, on peut se dire qu’il ne manque plus que quelques versions pour atteindre un niveau équivalent.

Avantages

Le fait de pouvoir intervenir dans le code est extrêmement positif. N’étant pas des participants assidus aux projets Open Source, l’argument du code modifiable selon nos besoins nous paraissait un peu éloigné de nos considérations.

Nous raisonnions en termes de complexité du projet, et pensions ne jamais être en mesure de proposer des modifications. Oui, quand on voit la taille impressionnante du projet Mono, on a beau être un programmeur aguerri, on ne sait pas trop par quel bout aborder tout ça…
En pratique, la complexité s’efface lorsqu’on se penche sur un code particulier (on ne peut que saluer la qualité des commentaires de code de Mono). Je n’ai pas eu de souci à intervenir dans la classe OracleParameter pour rajouter une signature de constructeur, et ce de manière très simple. Ca me fait d’ailleurs penser que je n’ai pas regardé si ma modification avait été acceptée par Mono…

Conclusion

Malgré quelques petits problèmes sur la configuration de l’accès à la base de données, il apparaît que Mono se prête bien à une utilisation dans un cadre industriel.
Le but des tests dont nous rapportons les résultats ci-dessus était de démontrer la faisabilité d’un déploiement du serveur de services web en mode Linux pour de nouveaux clients.

Non seulement cette faisabilité est aujourd’hui démontrée, avec une première installation prévue pour les prochains mois, mais elle a également amené de bonnes surprises en termes de temps de réponse, dans des charges faibles.

Dans un premier temps, il était prévu de ne migrer que les clients en faisant la demande. A la suite de ces résultats, nous envisageons de migrer nos propres serveurs (nous hébergeons les données de certains de nos clients) dans un futur proche.

Et pour plus tard

Cette expérience positive nous encourage à envisager également des tests de Mono pour la partie interface.

Clairement, il paraît difficile d’aborder le problème sur une application WinForms. Mais le passage à WPF, avec une grammaire XAML de description de l’interface séparée du moteur de rendu lui-même, va certainement simplifier fortement cette migration, vu que l’implémentation du moteur pourra être réalisée en code natif Linux.

A condition bien sûr que Mono continue d’avancer au même rythme. Le rapprochement entre Novell et Microsoft a donné de bons résultats pour Silverlight. En ce qui concerne WPF, entre le fait qu’il est en train de se faire phagocyter par Silverlight, et que même Miguel de Icaza ne croit pas en une implémentation prochaine de WPF en Mono, j’ai de furieux doutes. Le mode de développement préférentiel risque donc de devenir Silverlight, en utilisant le mode Out Of Browser pour simuler des applications Line Of Business. Tout ceci pose des questions, mais le caractère Open Source et la qualité des développements sont un gage de pérennité considérable. Et surtout, qui aurait dit il y a un an que Mono aurait déjà un compilateur C# 4.0, et fournirait le support d’une partie de WCF, de presque tout Silverlight 3, etc. ?

Annexes

SUSE Linux Enterprise Mono Extension : http://www.novell.com/products/mono/

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in .NET and tagged , . Bookmark the permalink.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload