.Net, Azure and occasionally gamedev

Keep Azure Web App Slots on same domain

2017/10/07

Preface

Azure web apps have a feature called slots. For each web app you can create 1+4 slots (the web app itself takes the first slot) and the four remaining slots are actually also webapps themselves.

The intendent usage of these slots is for different environments (dev, staging, prod) and to warm up a slot before deploying it to the actual website accessed by users.

This allows you to test your code by deploying it to e.g. staging (which you can limit the access to quite easily) and only deploy it to production once ready.

By warming up a slot you remove the cold start time web applications have (even an empty Asp.Net Core application takes ~1min to startup once deployed).

The "slot swap" feature helps tremendously as it just swaps the dns records once the web app is running. Which means your website will have zero downtime between updates.

Here's a great guid on how to warm up slots.

However this post is dedicated to the fact that by default each Azure web app runs on its own domain.

Azure web app slots

If you have an application "marcstan" (as is my application for this blog), azure will automatically create a url "marcstan.azurewebsites.net" for it.

Creating a slot "staging" for said web app will result in a new web app (called "staging" in the portal mapped under the application "marcstan") with a new access url "marcstan-staging.azurewebsites.net".

The slot itself will have all the same features available as the original web app, including the ability to map a custom domain name to it.

However it must be a valid domain name, which means you can't use a subdirectory like "marcstan.net/staging".

Instead only "staging.marcstan.net" would be valid.

Since I don't like using subdomains and much prefer using subfolders I wanted to see if it was possible, but there is no direct support for it.

IIS Reverse proxy

Based on the tutorial for a reverse proxy I was able to figure out a solution, although the suggested solution did not work right away.

In the tutorial all the tags are missing the

xdt:Locator="Match(name)"

specifier on each "<add />" tag.

By using the Microsoft.Web.Xdt nuget package I was able to test the transform locally and quickly spot the mistake (only the first of the four "<add />" was ever inserted).

var transform = new Microsoft.Web.XmlTransform.XmlTransformation(@"applicationHost.xdt");
var xdoc = new XmlDocument();
xdoc.Load(@"applicationhost.config");
if (transform.Apply(xdoc))
{
    xdoc.Save(@"applicationHost_new.xdt");
}

After adding the "Match(name)" Locator everything worked as expected.

Unexpected logic

Initially the web app started and marcstan.net/staging served the content from marcstan-staging.azurewebsites.net however - much to my surprise - after swapping slots it no longer worked.

I quickly figured out that all virtual applications are swapped as well (as are all settings that aren't pinned to the current slot) and there is no way to prevent this (i.e. there is no "slot" checkbox like there is for app settings).

This behaviour actually makes sense when you consider that the "swap" is actually just swapping the host names for the slots in question. Here's a detailed step-by-step of what exactly happens when slots are swapped.

So I simply added the same "/staging" virtual application to my staging slot and tried again ..and voilĂ  it worked!

This has the downside that you will need to add a new virtual application for every new environment to all slots.

E.g. if your deployment route is dev -> testing -> staging -> release you will need four "/staging" child applications (one in each slot) just to get the "/staging" url working and if you want "/dev" and "/testing" urls you need 8 more!

But for my scenario (only staging to preview the changes) it works just fine.

Step by step guide

1. Create a slot

Create the slot for your application as usual and don't assign a custom domain to it (you won't need it).

This slot will only be accessed by the child application we create next.

2. Create a child application

This will be the "public" facing endpoint.

In your original web application, create a child application in the application settings panel (I called mine "staging").

To do so, give it a name and the directory from which to run it. Also be sure to check the "application" checkbox.

azure child application (Azure child application)

Repeat the same steps in your secondary slot (or all slots that you intend to swap around)

E.g. I have created a "/staging" application in both my production and staging web application slots.

3. Configure reverse proxy

Next we need to upload two files to these child applications. I like to use Kudo for it as it is quick and easy to access, but FTP/S works too.

Go to your Kudo service page (yourwebappname.scm.azurewebsites.net) and navigate to the folder path you selected.

Note that the folder won't exist by default, so you will need to use "+" -> "add folder" (in my case "site\wwwroot" existed, but "staging" did not).

Now upload "applicationHost.xdt" to the "site" directory. The content is:

<?xml version="1.0"?>  
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">  
    <system.webServer>
        <proxy xdt:Transform="InsertIfMissing" enabled="true" preserveHostHeader="false" reverseRewriteHostInResponseHeaders="false" />
        <rewrite>
            <allowedServerVariables>
                <add name="HTTP_X_UNPROXIED_URL" xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" />
                <add name="HTTP_X_ORIGINAL_HOST" xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" />
                <add name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" />
                <add name="HTTP_ACCEPT_ENCODING" xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" />
            </allowedServerVariables>
        </rewrite>
    </system.webServer>
</configuration>

Next upload the following content to "site\wwwroot\staging\web.config" (or whichever sub application directory you created.

Don't forget to modify the TWO "https://yourwebappname-staging.azurewebsites" urls to insert your own staging url:

<?xml version="1.0" encoding="utf-8"?>  
<configuration>  
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Proxy" stopProcessing="true">
                    <match url="(.*)" />
                    <action type="Rewrite" url="https://yourwebappname-staging.azurewebsites.net/{R:1}" />
                    <serverVariables>
                        <set name="HTTP_X_UNPROXIED_URL" value="https://yourwebappname-staging.azurewebsites.net/{R:1}" /> 
                        <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" /> 
                        <set name="HTTP_X_ORIGINAL_HOST" value="{HTTP_HOST}" />
                        <set name="HTTP_ACCEPT_ENCODING" value="" />
                    </serverVariables>
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

The "applicationHost.xdt" will transform your sites applicationHost.config file and allow usage of parameters that are turned off by default.

The web.config then takes advantage of them and basically internally forwards all trafic between the user endpoint (yourwebsite.example/staging) and the azure staging slot (yourwebappname-staging.azurewebsites.net).

Again, repeat these steps for the production and staging slots, but be sure to only ever use https://yourwebappname-staging.azurewebsites.net or whatever your staging url is.

4. Restart website

The application transform is only applied on web start, so restart the web app via azure or Kudo.

Note that you need to restart the web app that is hosting the child application and NOT the staging slot itself!

Once restarted the child application should begin to serve everything correctly.

5. (Optional) Block staging url

Finally, if you need to you can restrict access to the staging url.

Your staging slot will have its IP addresses listed under "Properties" and they are static unless you upgrade your app service plan.

Result

You should now have one production application and a staging slot. Both should have a "/staging" child application that points to your azure staging url.

Your production application should be visible at your domain and your staging slot should be visible at your domain/staging.

Swapping them should put your staging environment to your production slot and vica versa.

tagged as Azure and Webapps