Accueil Nos publications Blog [IIS] Application Request Routing

[IIS] Application Request Routing

http-load-balancing-using-application-request-routing-485-topoJ’ai récemment eu l’occasion d’animer une soirée, chez SOAT, où j’abordais IIS et différents usages de IIS dans des architectures de production. Un des scénarios se basait sur ARR (Application Request Routing). Or, en discutant avec plusieurs personnes ce soir-là, je me suis aperçu qu’il s’agit d’un outil que les gens ne connaissaient pas forcément. Le but de ce billet est donc de vous faire une présentation d’ARR et de sa configuration dans un cas d’usage précis.

Qu’est-ce qu’Application Request Routing ?

Application Request Routing est une extension à IIS qui permet de gérer les requêtes HTTP entrantes et celles sortantes en fonctionnant tel un proxy. C’est à dire que toutes les requêtes entrantes passeront par ARR qui va ensuite les router vers la bonne web farm. Il en va de même pour les réponses en sorties émises par les web farms qui passent par ARR avant d’être retournées aux clients.

L’extension peut être utilisée avec IIS 7.0 et les versions suivantes. Vous pouvez l’installer en suivant les liens de téléchargement sur le site d’IIS ou directement au travers de Web Platform Installer.

ARR permet notamment de faire du load balancing, la mise en plage de règles de routage, l’offloading SSL, la compression HTTP, une gestion d’affinité, etc.

L’usage dont je souhaite vous parler aujourd’hui est celui qui, à mon sens, est le plus intéressant dans ARR, à savoir sa capacité à faire de la répartition de charge et à son support de règles de routage.

Outre ces différentes fonctionnalités, l’outil a de nombreux avantages, parmi les suivants :

  • Il peut s’installer directement sur une web farm. Ainsi, dans certains scénarios les plus simples, il devient possible d’utiliser ses fonctionnalités sans augmenter les coûts de l’infrastructure de production.
  • Sa gestion se fait directement au travers de la console de management de IIS. Ainsi, la courbe d’apprentissage est minimale pour un administrateur qui connaît déjà IIS.

Néanmoins, l’outil n’a pas pour vocation à supplanter totalement le matériel dédié à des scénarios de routage ou de loadbalancing (type F5 BigIp) notamment car il lui manque des fonctionnalités cruciales (mode haute disponibilité, support DDoS). Il est toutefois possible d’imaginer des scénarios où ARR est combiné à d’autres produits : F5 BigIp pour les architectures les plus complexes ou même NLB pour ajouter le support de la haute disponibilité tout en restant dans un mode de fonctionnement relativement simple d’installation.

Mise en œuvre d’un routage sur un unique serveur local

Dans un premier temps, je vous propose de découvrir un usage d’ARR purement local. Plus concrètement, il s’agit d’un scénario dans lequel ARR est installé directement sur le même serveur web que vos applications.

Le but est simple, votre projet est exposé au travers d’un nom de domaine unique (ex. www.monsite.com). Néanmoins, sur votre serveur web, ce nom de domaine n’est pas mappé à un simple site web. En effet, vous avez déployé plusieurs applications qui correspondent aux différentes fonctionnalités accessibles sur votre site :

  • l’application API, qui devrait être accessible au travers de l’url https://www.monsitesite.com/api ;
  • l’application Account, qui devrait être accessible au travers de l’url https://www.monsite.com/mon-compte/ ;
  • l’application Public, qui devrait être accessible à la racine du site, soit https://www.monsite.com.

Au passage, notez que diviser une grosse application monolithique en plusieurs applications de plus petites tailles est une approche qui pourrait vous faire gagner en temps de maintenance, de gestion de la dette technique, de tests, etc.

Jusque-là, ce cas de figure peut être résolu simplement en créant des applications à l’intérieur d’un web site sous IIS. Cependant, cela se corse si l’on ajoute une contrainte supplémentaire mais extrêmement importante : chacune de ces applications devrait s’exécuter avec son propre pool d’applications (si vous ne le faîtes pas déjà, c’est une bonne pratique que je vous invite à appliquer ; cela permet d’utiliser des identités différentes et surtout d’isoler vos applications dans des processus différents).

Lorsqu’une application qui possède des règles de réécriture qui doivent transporter une requête d’une application vers une autre (ex. de l’application Public vers l’application Account), un problème de taille apparaît. En effet, l’application qui sera invoquée après réécriture de l’URL le sera dans le worker process de l’application invoquée avant réécriture d’URL.

Si on y regarde d’un peu plus près, alors on constate que le module de rewrite de IIS s’exécute après la création du worker process. La requête réécrite continuera donc à s’exécuter dans ce même processus. Cela peut alors poser des problèmes de sécurité ou de perte de cache, par exemple.

Et bien c’est là qu’ARR entre en jeu. Dans ce cas de figure, nous sommes intéressés par sa capacité à router (réécrire) des requêtes vers des fermes de serveurs. Ici, nos fermes ne contiendront en fait qu’un seul serveur, la machine locale. L’astuce étant qu’ARR est capable d’exécuter ces règles très tôt, et notamment avant la création du worker process de l’url réécrite.

La fonctionnalité de routage d’ARR est particulièrement intéressante, car elle s’effectue au niveau de la couche 7 du modèle OSI, là où les équipements traditionnels (type F5 BipIp) opèrent généralement sur les couches 3 et 4. Cela signifie que les règles de routage écrites pour ARR peuvent manipuler des éléments tels que des en-têtes HTTP ou des variables serveurs.

Notez ici que les règles de routage s’expriment au travers du même module que la réécriture classique de IIS. Ainsi, il est courant d’avoir des compétences en interne connaissant cette syntaxe plutôt que d’utiliser celle disponible sur les load balancers hardware classiques (type F5 BigIp).

Mise en place des fermes

Après avoir installé ARR par-dessus une instance de IIS, vous devriez voir apparaître un nouveau nœud dans la console de management. Celui-ci est intitulé Server farms et, comme son nom l’indique, permet de gérer les fermes de serveurs placées derrières ARR.

Nous allons ici créer une ferme par module applicatif. J’ai choisi de les appeler en suivant une convention du type Monsite_NomDuModule. Chaque ferme ne contient qu’un seul serveur, à savoir le serveur courant (d’où le 127.0.0.1). Néanmoins, le serveur courant est indiqué avec un port bien précis (ex. le port 5000 pour l’application API ou encore le port 5002 pour l’application Public).

Ces différentes actions peuvent être facilement réalisées au travers de la console d’administration.

Tout d’abord le choix du nom de la ferme.

Puis le second écran permet d’y rattacher différents serveurs. Ici, uniquement l’instance locale sur le port 5000.

Elles peuvent également l’être en ligne de commande via appcmd.exe (dans c:\windows\system32\inetsrv). L’outil en ligne de commande permet alors de manipuler le fichier applicationHost.config.

La commande suivante peut être utilisée pour créer la ferme.

appcmd.exe set config  -section:webFarms /+"[name='Monsite_Public']" commit:apphost

La commande suivante peut être utilisée pour ajouter le serveur local à la ferme que nous venons de créer.

appcmd.exe set config  -section:webFarms /+"[name='Monsite_Public'].[address='127.0.0.1']" /commit:apphost

Au niveau du fichier applicationHost.config, cela se traduit par le nœud ci-dessous.

<webFarms>
    <webFarm name="Monsite_API" enabled="true">
        <server address="127.0.0.1" enabled="true">
            <applicationRequestRouting httpPort="5000" />
        </server>
    </webFarm>
    <webFarm name="Monsite_Account" enabled="true">
        <server address="127.0.0.1" enabled="true">
            <applicationRequestRouting httpPort="5001" />
        </server>
    </webFarm>
    <webFarm name="Monsite_Public" enabled="true">
        <server address="127.0.0.1" enabled="true">
            <applicationRequestRouting httpPort="5002" />
        </server>
    </webFarm>
</webFarms>

Mise en place des sites

Une fois la création des fermes terminée, il faut placer à la mise en place des sites.

Je vais créer quatre sites web dans mon instance de IIS.

Les trois premiers correspondent simplement aux trois modules applicatifs que j’ai pu évoquer plus tôt (API, Account et Public). Lors de la création des sites web, je prends bien soin de lier chaque site à son propre application pool. J’en profite également pour que chaque site possède une liaison uniquement sur l’adresse 127.0.0.1, et sur le port correspondant à ceux attribués lors de la création des fermes (ex. 127.0.0.1:5000 pour le site API).

Là encore ces différentes actions peuvent être facilement réalisées au travers de la console d’administration. Elles peuvent également l’être en ligne de commande via appcmd.exe (dans c:\windows\system32\inetsrv).

Par exemple, pour ajouter une application pool :

appcmd add apppool /name:Monsite_Public /managedRuntimeVersion:"v4.0"

Pour ajouter un site web :

appcmd add site /name:Monsite_Public /bindings:"https://127.0.0.1:5002" /physicalPath:"c:\inetpub\arr\public"

Pour définir l’app pool d’un site :

appcmd set app /app.name:Monsite_Public/ /applicationPool:Monsite_Public

Du côté de mon fichier applicationHost.config, j’ai donc maintenant la configuration suivante.

<applicationPools>
    <add name="DefaultAppPool" />
    <add name="Monsite_API" />
    <add name="Monsite_Account" />
    <add name="Monsite_Public" />
</applicationPools>

<sites>
    <site name="Monsite_API" id="2">
        <application path="/" applicationPool="Monsite_API">
            <virtualDirectory path="/" physicalPath="c:\inetpub\arr\api" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="127.0.0.1:5000:" />
        </bindings>
    </site>
    <site name="Monsite_Account" id="3">
        <application path="/" applicationPool="Monsite_Account">
            <virtualDirectory path="/" physicalPath="C:\inetpub\arr\account" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="127.0.0.1:5001:" />
        </bindings>
    </site>
    <site name="Monsite_Public" id="4">
        <application path="/" applicationPool="Monsite_Public">
            <virtualDirectory path="/" physicalPath="c:\inetpub\arr\public" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="127.0.0.1:5002:" />
        </bindings>
    </site>
</sites>

Je vais à présent ajouter un quatrième site web, celui-ci est destiné à être une coquille vide qui me permet uniquement d’écouter sur un binding de type monsite.com:80. Toutes les requêtes entrantes sur mon serveur seront attrapées par http.sys grâce à ce binding puis ARR les prendra en charge et les routera vers la bonne ferme.

L’aspect final de mon fichier applicationHost.config est visible ci-dessous.

<applicationPools>
    <add name="DefaultAppPool" />
    <add name="Monsite_ARR_Router" />
    <add name="Monsite_API" />
    <add name="Monsite_Account" />
    <add name="Monsite_Public" />
</applicationPools>

<sites>
    <site name="Monsite_ARR_Router" id="1">
        <application path="/" applicationPool="Monsite_ARR_Router">
            <virtualDirectory path="/" physicalPath="c:\inetpub\arr\router" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:monsite.com" />
        </bindings>
    </site>
    <site name="Monsite_API" id="2">
        <application path="/" applicationPool="Monsite_API">
            <virtualDirectory path="/" physicalPath="c:\inetpub\arr\api" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="127.0.0.1:5000:" />
        </bindings>
    </site>
    <site name="Monsite_Account" id="3">
        <application path="/" applicationPool="Monsite_Account">
            <virtualDirectory path="/" physicalPath="C:\inetpub\arr\account" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="127.0.0.1:5001:" />
        </bindings>
    </site>
    <site name="Monsite_Public" id="4">
        <application path="/" applicationPool="Monsite_Public">
            <virtualDirectory path="/" physicalPath="c:\inetpub\arr\public" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="127.0.0.1:5002:" />
        </bindings>
    </site>
</sites>

Création des règles de routage

Il faut maintenant passer à l’écriture des règles de routage en elles-mêmes. Attention, même si elles prennent la même forme que le module de rewrite traditionnel de IIS, elles ne sont pas à placer au même endroit.

Côté console de management IIS, vous devez chercher l’option URL Rewrite au niveau du serveur (pas au niveau d’un site donc). Côté fichier de configuration, cela se passe directement dans le fichier applicationHost.config et non pas au niveau du web.config d’un site ou d’une application.

Nous allons avoir besoin de trois règles : une pour le module API, une pour le module Account et une dernière qui fera office de fallback pour le module Public.

La première règle doit rediriger tout le trafic sur monsite.com/api/*** vers la ferme **Monsite_API en réécrivant le contenu de * (le groupe de capture n° 1 donc). Cela me permet d’avoir le contenu de mon application API directement à la racine du website API, sans avoir à créer de répertoire de type ~/api.

En clair, une url entrante de type https://monsite.com/api/test doit être réécrite sous la forme https://Monsite_API/test. La ferme Monsite_API ne contenant qu’un seul serveur, cela aboutirait alors à une URL réécrite de type https://127.0.0.1:5000/test.

Il est extrêmement important de faire en sorte que cette règle ne s’applique que pour les URLs entrantes et pas celles déjà réécrites. En clair, il est nécessaire d’ajouter une condition sur l’hôte HTTP afin de valider que la règle ne s’applique que si la requête cible https://monsite.com et pas si elle cible https://127.0.0.1. Cette condition est nécessaire sur toutes les règles de routage au niveau d’ARR. Sans elle, vous vous exposez à ce que certaines requêtes entrantes atterrissent dans le mauvais module, ou tombent dans une boucle de redirection.

La règle de réécriture pour le module Account est bâtie sur le même modèle. Le résultat est visible dans mon extrait de applicationHost.config ci-dessous.

<rewrite>
    <globalRules>
        <clear />
        <rule name="monsite_api" patternSyntax="Wildcard" stopProcessing="true">
            <match url="api/*" />
            <conditions>
                <add input="{HTTP_HOST}" pattern="monsite.com" />
            </conditions>
            <action type="Rewrite" url="https://Monsite_API/{R:1}" />
        </rule>
        <rule name="monsite_account" enabled="true" patternSyntax="Wildcard" stopProcessing="true">
            <match url="account/*" />
            <conditions>
                <add input="{HTTP_HOST}" pattern="monsite.com" />
            </conditions>
            <action type="Rewrite" url="https://Monsite_Account/{R:1}" />
        </rule>
    </globalRules>
</rewrite>

La dernière règle est un peu plus complexe à mettre en place. Elle doit se comporter comme un fallback, c’est-à-dire gérer tous les cas qui n’auraient pas matché une des deux règles précédentes – ce que je teste avec une condition sur le port courant ciblé (SERVER_PORT).

Au final, voici la configuration que j’obtiens.

<rewrite>
    <globalRules>
        <clear />
        <rule name="monsite_api" patternSyntax="Wildcard" stopProcessing="true">
            <match url="api/*" />
            <conditions>
                <add input="{HTTP_HOST}" pattern="monsite.com" />
            </conditions>
            <action type="Rewrite" url="https://Monsite_API/{R:1}" />
        </rule>
        <rule name="monsite_account" enabled="true" patternSyntax="Wildcard" stopProcessing="true">
            <match url="account/*" />
            <conditions>
                <add input="{HTTP_HOST}" pattern="monsite.com" />
            </conditions>
            <action type="Rewrite" url="https://Monsite_Account/{R:1}" />
        </rule>
        <rule name="monsite_public" enabled="true" patternSyntax="Wildcard" stopProcessing="true">
            <match url="*" />
            <conditions>
                <add input="{HTTP_HOST}" pattern="monsite.com" />
                <add input="{SERVER_PORT}" pattern="5000" negate="true" />
                <add input="{SERVER_PORT}" pattern="5001" negate="true" />
                <add input="{SERVER_PORT}" pattern="5002" negate="true" />
            </conditions>
            <action type="Rewrite" url="https://Monsite_Public/{R:1}" />
        </rule>
    </globalRules>
</rewrite>

Passons au scale-out et ajoutons des serveurs

Admettons que notre entreprise a grandi et que nous devons à présent faire face à un nombre plus important d’utilisateurs. Face à cette problématique, il y a deux approches :

  • Le scale-up, qui consiste à faire monter en puissance notre serveur web afin qu’il soit en mesure de traiter plus de requêtes ;
  • Le scale-out, qui consiste à ajouter de nouveaux serveurs web pour prendre en charge les requêtes entrantes.

C’est cette deuxième approche que je vous propose d’explorer ici.

Nous constatons que la répartition des requêtes entrantes se fait selon les proportions suivantes : 70% du trafic sur la partie publique du site, 20% du trafic sur les APIs et enfin 10% du trafic sur la partie gestion du compte client.

Bonne nouvelle, nous avons du budget pour passer commande de 10 serveurs webs en plus du seul que nous possédions jusqu’à présent.

Et bien l’évolution est assez simple ; notre serveur historique va cesser d’héberger directement les applications et va prendre pour seul et unique rôle celui d’être un ARR qui se charge du routage des requêtes. Sur 7 des nouveaux serveurs, nous installons l’application publique, sur 2 autres l’application API et enfin sur le dernier, l’application Account.

Il ne reste plus qu’à modifier la composition des fermes dans ARR pour y ajouter ces nouveaux serveurs. Je vous propose ci-dessous un exemple de fichier applicationHost.config mis à jour.

<webFarms>
    <webFarm name="Monsite_API" enabled="true">
        <server address="192.168.0.10" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.11" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
    </webFarm>
    <webFarm name="Monsite_Account" enabled="true">
        <server address="192.168.0.20" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
    </webFarm>
    <webFarm name="Monsite_Public" enabled="true">
        <server address="192.168.0.30" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.31" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.32" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.33" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.34" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.35" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
        <server address="192.168.0.36" enabled="true">
            <applicationRequestRouting httpPort="80" />
        </server>
    </webFarm>
</webFarms>

Par défaut, ARR utilisera un algorithme de répartition classique (round robin) tel que les différentes machines d’une ferme devrait recevoir une charge similaire. Notez également qu’aucune affinité particulière n’est utilisée par défaut.

Et voilà ! Tout est prêt, il ne vous reste qu’à profiter de votre nouvelle infrastructure toute prête. Notez tout de même qu’il serait préférable de load balancer le routage ARR en ajoutant une seconde instance et en les plaçant derrière un autre mécanisme tel que NLB ou un F5 BigIP.

Conclusion

J’espère que ce billet vous aura permis de découvrir cette extension qui est à mon sens injustement méconnue. Si le sujet vous intéresse et que vous souhaitez plus d’informations ou si vous rencontrez des problématiques de montée en charge ou d’installation de vos applications, n’hésitez pas à rentrer en contact avec nous.

© SOAT
Toute reproduction interdite sans autorisation de la société SOAT