1. var bob = new Person();
2. bob.sayHello();
3. bob.wave();
##################################################################################################################

Global Azure Functions with Azure Front Door

How to deploy an Azure Function to the entire world simultaneously and split traffic depending on where your customer is

##################################################################################################################
4. var cup = new Coffee();
5. bob.drink(cup);
6. var cupTwo = new Coffee();

Recently I’ve been doing a lot of thinking about how to serve global customers with a serverless architecture. The applications I’ve been building recently have started getting more users from across the world, and those users have been complaining of poor latency. I’ve come up with a solution to this, which if you want to cut to the chase you can visit here.

The Problem

What causes users to have poor latency when they visit across the world to an Azure Function based in an individual region? The answer is a combination of simple physics and complex networking. First of all let’s look at the setup I had and what was causing the issue:

diagram showing distance as crow flies between australia and uk

So here we’ve got a simple example of an Azure Function which interacts with a database in the UK and returns the results to an end user in Australia. The data travels as light along optical fibres (if we’re lucky) between the UK and Australia. Even if we look at the optimal case - travelling as light in a straight line - it takes approximately 0.1 seconds to do the journey to and from Australia. In reality the time is much, much worse than that.

Real internet connections are not so simple. The internet is a series of interelated nodes that pass traffic onto each other. So the journey from Australia to the UK looks more like this:

Realistic distance between australia and uk with lots of node hopping in between

At each node the connection can lose a bit of time. Some connections can get terminated because someone digs up a cable, some machines can malfunction etc. There’s also just processing time that it takes for the routing from one node to the next to work. The result is an average roundtrip time of almost 0.3 seconds, as shown here. This doesn’t sound like a lot, but can wreak havoc on your user experience when it’s powering a user interface, especially when packets get lost on the way.

The Solution

To fix this it’d be ideal if Azure Functions allowed you to deploy globally. However that currently isn’t possible out of the box, so I’ve created an ARM template and demo that lets you effectively roll your own Global Azure Function. My solution relies upon both of those by making use of another service, Azure Front Door. This is a neat service which works in the following way:

  1. You declare a front end host. This is a url which your users call.
  2. Whenever a user hits that url, their request is automatically routed through hundreds of local entry points to the Azure network.
  3. The service then directs the request to the closest backend
  4. The request travels to the closest backend on the private Azure network - which doesn’t hop about like the general internet

Front Door has a bunch of other features like a Web Application Firewall, take a read if you’re interested here.

So if you deployed an Azure Function to every region in the world that supported it, then put an Azure Front Door in front of them, then by calling the Azure Front Door you’d get rerouted to your local Azure Function and effectively have a global Azure Function. Here’s a diagram of what that means:

Global Azure Function architecture diagram

Each user is generally very close to an Azure Front Door endpoint, called a Point of Presence, denoted with the blue redirect boxes. When they call the Azure Front Door URL they get redirected along the Azure backbone, which much better approximates the straight line optimal condition than the normal internet. It then reaches the closest Azure Function, which deals with it and returns the data to the user.

This looks complicated, and is, but I’ve made an ARM template and deployment script that make it simple here. Let’s go through the details of how the ARM template works first. It’s a standard Azure Functions ARM template, but instead of taking a single location, it takes an array of locations:

ARM template locations variable

Next I make heavy use of the ARM template Copy feature. First of all we need a storage account in each region to store the code for the Azure Function:

Copy index storage account

Here you can see I’m setting up a copy, which replicates the resource the number of times listed in the ‘count’ property. The copyIndex function will then return the index of whatever resource is currently being created. You can therefore use the length of the array of locations to set the count, to create one resource per location, and the copyIndex function to access the location string for each resource. This creates a copy of each storage account for each region. The server farms are set up in the same way.

Next I set up the Function Apps themselves. There’s two bits of particular note here, the dependsOn and the AzureWebJobsDisableHomepage setting:

Function app setup

The dependsOn setting uses the copyIndex method to make each and every function app depend on its particular local storage account and server farm. This allows ARM to parallelize the deployments as much as possible without ever deploying a function app before its dependencies are ready.

The AzureWebJobsDisableHomepage setting is set to true. This is based on advice in the article here. Basically Front Door pings the home page of the Azure Function App to check which is the closest from each Point of Presence and which is currently functioning. This can then result in a lot of traffic, which can cost a lot. So switching off the home page reduces the data transfers and reduces the cost.

There’s a single Application Insights resource for the entire deployment. This is something to be careful of over time. The cost in global data transfer could become prohibitive if there’s a lot of logs:

App Insights set up

Next is the front door set up. This is mostly standard and covered better in the official documentation. The only different bit is the set up of the backend pool to cover every Azure Function. This uses the copy function again:

Front Door setup

This is simple to run. Login in to your Azure account in powershell, create a resource group with az group create:

az group create

Then execute the ARM template and pass in the name. There is a bit of a flaw due to long region names and the 24 character limit on a storage account names - australiasoutheast is 18 characters. I’ve an idea for improving that in future, using an array of objects with a short name for each location as well as the long name.

deploying arm template

So this will now spin up a resource group which spans the entire world:

Global Resource Group

This is great, but how do you deploy to a global Azure Function? This is going to be a real pain if you have to deploy to each one individually. I’ve built a deploy script in powershell which deploys in parallel to each Azure Function. It’s still not as fast as deploying to one, but a lot faster and easier than deploying to all of them. I’ve created a C# Azure Function with the default HTTP starter. Execute the deploy script to deploy it to each Function App:

Deploy the global function code using ../deploy.ps1 script

So now you have a global function app. You can add whatever http triggers you like and you just change the base domain from {name}.azurewebsites.net to {name}fd.azurefd.net:

Example of Global Azure Function

Now to test if the theory works. Are different functions triggered when I call from different locations? Given the current worldwide lockdown I don’t think travel is particularly feasible at the moment, so I just use a VPN - I use NordVPN. That lets me route all of my internet access through any region in the world:

NordVPN South East Asia

All of the logs from across the world are going in to the application insights resource, so we can see if the logs correlate with the requests. I went for a little world tour, and got the following logs:

App Insights logs showing South East Asia

Lets check out the performance too:

Global Azure Function Performance

As you can see the performance of the app is generally consistent barring some variance which is to be expected with cold starts etc.

Conclusion

This is an interesting (I think so anyway) demonstration that it is possible, and in fact relatively straightforward once you’ve got your head around the ARM template copy function, to deploy an Azure Function globally. One of the major strengths of a Serverless Architecture vs using, for example, servers running constantly in each region, is that pay for what you use is even more potent. To run this app without Azure Functions, and instead to put an App Service in each region, would be prohibitively expensive even if there was no traffic. As it is this solution comes in at about £16 a month which is the minimum price of a running Front Door. It’s too early to tell how expensive it would be going forward on a usage basis, but I suspect a lot cheaper than losing international customers.

© 2021 - Built by Daniel Bass