Using IIS Application Request Routing to load balance on Azure
As previously discussed, cloud applications should be written to be stateless, and use the standard Azure round-robin load balancer. However, some applications need a sticky session, in which case, one option you have is to roll your own software load balancer. However the Application Request Routing (a software load balancer) module of IIS presents you with another option. The advantages of using ARR are
- It’s standard, well tested code doing the balancing.
- There are a number of load balancing strategies available.
- Client affinity (sticky sessions) is an option.
The topology of your deployment will look something like this:
The ARR Role will have ARR and the Web Farm Framework installed, and expose a public facing endpoint. It will be responsible for routing traffic to the Web Role instances. The Web Role will have an internal endpoint, but is otherwise unchanged.
Installing ARR
Download ARR and The Web Farm Framework IIS modules:
http://www.iis.net/download/ApplicationRequestRouting
http://www.iis.net/download/WebFarmFramework
Create a Web Role project in your solution, copy the MSI files you downloaded into the solution, and set them to ‘copy if newer’. This will ensure they are packaged up with your application.
Add an elevated startup task (see how here) which will install these components. Your Startup.cmd file should contain these lines:
webfarm_amd64_en-US.msi /quiet requestRouter_amd64_en-US.msi /quiet
Configuring ARR
In your service definition file, set your role to run elevated (see how here).
In your WebRole class (which inherits from RoleEntryPoint) add an override to the ‘Run’ method. The first thing to do is to update the binding configuration of the virtual directory to include the correct host name, and change the physical path of the default site to wwwroot, instead of the web pages packaged with your role. This can be done using the Microsoft.Web.Administration namespace:
using (ServerManager serverManager = new ServerManager())
{
bool voteCommit = false;
Configuration config = serverManager.GetApplicationHostConfiguration();
ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites");
ConfigurationElementCollection sitesCollection = sitesSection.GetCollection();
ConfigurationElement siteElement = sitesCollection[0];
ConfigurationElementCollection bindingsCollection = siteElement.GetCollection("bindings");
ConfigurationElement bindingElement = FindElement(bindingsCollection, "binding", "protocol", @"http");
if (bindingElement["bindingInformation"] as string != @"*:80:yourservice.cloudapp.net")
{
bindingElement["bindingInformation"] = @"*:80:yourservice.cloudapp.net";
}
ConfigurationElementCollection siteCollection = siteElement.GetCollection();
ConfigurationElement applicationElement = FindElement(siteCollection, "application", "path", @"/");
ConfigurationElementCollection applicationCollection = applicationElement.GetCollection();
ConfigurationElement virtualDirectoryElement = FindElement(applicationCollection, "virtualDirectory", "path", @"/");
if (virtualDirectoryElement["physicalPath"] as string != @"%SystemDrive%\inetpub\wwwroot")
{
virtualDirectoryElement["physicalPath"] = @"%SystemDrive%\inetpub\wwwroot";
}
serverManager.CommitChanges();
}
You then need to create a web farm, and add the redirection rule to point incoming requests to your farm. The web farm in this case is configured to use client affinity (sticky sessions).
using (ServerManager serverManager = new ServerManager())
{
// create the server farm
Configuration config = serverManager.GetApplicationHostConfiguration();
ConfigurationSection webFarmsSection = config.GetSection("webFarms");
ConfigurationElementCollection webFarmsCollection = webFarmsSection.GetCollection();
var el = FindElement(webFarmsCollection, "webFarm", "name", farmName);
if (null != el) return;
ConfigurationElement webFarmElement = webFarmsCollection.CreateElement("webFarm");
webFarmElement["name"] = farmName;
webFarmsCollection.Add(webFarmElement);
ConfigurationElement applicationRequestRoutingElement = webFarmElement.GetChildElement("applicationRequestRouting");
ConfigurationElement affinityElement = applicationRequestRoutingElement.GetChildElement("affinity");
affinityElement["useCookie"] = true;
// create the rewrite rule
Configuration config = serverManager.GetApplicationHostConfiguration();
ConfigurationSection globalRulesSection = config.GetSection("system.webServer/rewrite/globalRules");
ConfigurationElementCollection globalRulesCollection = globalRulesSection.GetCollection();
ConfigurationElement ruleElement = globalRulesCollection.CreateElement("rule");
ruleElement["name"] = serverFarm;
ruleElement["patternSyntax"] = @"Wildcard";
ruleElement["stopProcessing"] = true;
ConfigurationElement matchElement = ruleElement.GetChildElement("match");
matchElement["url"] = "*";
matchElement["ignoreCase"] = true;
ConfigurationElement actionElement = ruleElement.GetChildElement("action");
actionElement["type"] = @"Rewrite";
actionElement["url"] = string.Concat(@"http://", serverFarm, @"/{R:0}");
globalRulesCollection.Add(ruleElement);
serverManager.CommitChanges();
}
These tasks need only to be run once.
You should then set up a simple loop, which will inspect the instances running, and modify the farm according to changes in the topology. Adding a server is straight forward, and can be done with some code like this:
public static void AddServer(string farmName, string ipAddress, int portNumber)
{
using (ServerManager serverManager = new ServerManager())
{
Configuration config = serverManager.GetApplicationHostConfiguration();
ConfigurationSection webFarmsSection = config.GetSection("webFarms");
ConfigurationElementCollection webFarmsCollection = webFarmsSection.GetCollection();
ConfigurationElement webFarmElement = FindElement(webFarmsCollection, "webFarm", "name", farmName);
ConfigurationElementCollection webFarmCollection = webFarmElement.GetCollection();
var server = FindElement(webFarmCollection, "server", "address", ipAddress);
if (null != server)
{
// server already exists
return;
}
ConfigurationElement serverElement = webFarmCollection.CreateElement("server");
serverElement["address"] = ipAddress;
ConfigurationElement applicationRequestRoutingElement = serverElement.GetChildElement("applicationRequestRouting");
applicationRequestRoutingElement["httpPort"] = portNumber;
webFarmCollection.Add(serverElement);
serverManager.CommitChanges();
}
}
You would need additional code to for servers being, removed, and IP address changes.
Simple!




Ken Lawrence 1:11 pm on November 11, 2011 Permalink | Log in to Reply
Rich – the msi install process no longer appears to work. The web farm framework throws an alert stating that it needs to be installed via the Web Platform Installer. See http://things.smarx.com/#Install Application Request Routing for the script, but note that the line:
webpicmdline /accepteula /Products:ARRv2
…should read:
webpicmdline /accepteula /Products:ARR
…to install ARR 2.5
Richard 1:18 pm on November 11, 2011 Permalink | Log in to Reply
Interesting, I’ll check it out and update.
Cheers.
Ken Lawrence 1:18 pm on November 11, 2011 Permalink | Log in to Reply
Grrrr – WordPress screwed my url – try:
http://tinyurl.com/c972j3c
Arvin 2:22 am on January 26, 2012 Permalink | Log in to Reply
There’s a way to install ARR without using Web platform Installer: http://blogs.iis.net/wonyoo/archive/2011/04/20/how-to-install-application-request-routing-arr-2-5-without-web-platform-installer-webpi.aspx. Also you may want to set your ApplicationPool Idle time to 0 so your applciation pool is always On.
Richard 9:20 am on January 26, 2012 Permalink | Log in to Reply
Thanks Arvin, great tip.
Arvin 10:00 pm on January 30, 2012 Permalink | Log in to Reply
No problem! One question for you, have you tried importing your rules from XML using ServerManager class? In AppCmd, I used this one to clear and Import:
appcmd.exe clear config -section:webFarms /commit:apphost
type rules.xml | appcmd.exe set config /in
Arvin 9:33 am on January 31, 2012 Permalink | Log in to Reply
sorry, the command to clear is appcmd.exe clear config -section:system.webServer/rewrite/globalRules
Anonymous 10:07 am on January 31, 2012 Permalink | Log in to Reply
Hi Arvin, not something I have tried personally, but it looks useful – thanks for another useful tip!