August 25, 2011

Windows Azure Accelerator for Web Roles with Wildcard Bindings

The release of the Windows Azure Accelerator for Web Roles was a huge deal for me. Aside from the obvious cost savings of (easily) running multiple sites under the same web role, the biggest benefit was an improved deployment scenario.

A normal Windows Azure deployment can take anything up to 20 minutes to complete. So if you make a change to a css file and want to push this out, guess how long it’s going to take; 20 minutes.

Well that sucks, and that’s why the Azure Accelerator is awesome. It makes use of WebDeploy to deploy your web application in seconds. You deploy your site using Web Deploy and the changes get detected by the accelerator, which in turn, recreates a deployment package that is then deployed across all of your instances. Matias Woloski has a more detailed explanation here.

Recently we deployed a multi-tenant application running on Azure that made use of the accelerator. The application was split into two sites, the admin dashboard where users can manage their site content and the public site where that content is displayed.

The dashboard site has a fixed url (e.g. http://dashboard.myapp.com).
The public site needed a wildcard binding. This is so customers can use their own custom domain for their site.

For this to work with the accelerator we had to make a few changes. A major one (for which I’ve submitted a patch) took me hours to figure out so hopefully this will save you the pain.

First of all you’ll need to make some changes to your cloud project’s service definition (ServiceDefinition.csdef). The default is that the Website Manager will listen to all requests on port 80. As a bare minimum you’ll need to give it a specific hostheader. We actually went one step further and used a different port (8080):

<Sites>
  <Site name="Web">
    <Bindings>
      <Binding name="HttpInAdmin" endpointName="HttpInAdmin" hostHeader="admin.myapp.com" />
    </Bindings>
  </Site>
</Sites>
<Endpoints>
  <InputEndpoint name="HttpInAdmin" protocol="http" port="8080" />
  <InputEndpoint name="HttpIn" protocol="http" port="80" />
  <InputEndpoint name="HttpsIn" protocol="tcp" port="443" localPort="443" />
  <!-- unused, just there so instance discovery works -->
  <InternalEndpoint name="UnusedInternal" protocol="http" port="88" />
</Endpoints>

You still need to specify the endpoint for port 80, otherwise your other sites won’t work.

Now you can set up your bindings using the Website manager.

Here’s how we configure our “dashboard” app:

Dashboard config

And here’s the public config (notice the empty host name).

Public config

That’s it. You’re ready to deploy right? Wrong!

When you specify a hostname for a site, the accelerator creates a binding of:

*:80:[host name]

This creates a binding for all IP addresses on port 80 with a specific host header. Whilst this works, if you do not specify the hostname we get:

*:80:

Which doesn’t work. Why? I have no idea, and it seems neither do Microsoft (I’ve got a support case with them to find out why). What’s interesting is that when you deploy a website in the normal way, Azure actually sets up the binding as:

[Web Role IP Address]:80:

So it knows to use the IP address of the primary network interface on the server. Once I was able to see this in action, a patch was pretty simple. Open up /core/services/IISManager.cs in the web site manager application and replace the “GetBindingInformation” method with the one below.

private static string GetBindingInformation(string address, int port, string hostName)
{
	if (address == "*")
	{
		// wildcard doesn't work in Azure. Grab the HttpIn endpoint ip
		address = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["HttpIn"].IPEndpoint.Address.ToString();
	}
	
	return address + ":" + port.ToString(CultureInfo.InvariantCulture) + ":" + hostName;
}

This checks for a wildcard binding and gets the IP address of your “HttpIn” endpoint (the one specified in servicedefinition.csdef).

Build your project and redeploy and you’re good to go.

We set up a wildcard CNAME record to our Azure service url for example:

*  CNAME  myapp.cloudapp.net

This saves us from having to set up a new DNS record each time a customer signs up for a site. DynDNS and DNSimple both support wildcard cname records.

I really hope Microsoft continue to develop this accelerator. I have a prediction that WebDeploy will eventually be an integral part of the Windows Azure deployment experience.

© 2022 Ben Foster