Posted in:

With Azure App Service, you can host multiple "Web Apps" in a single "App Service Plan". The App Service Plan governs how much you pay. There are multiple pricing tiers, allowing you to host your websites on more powerful VMs, but you can also scale out your App Service Plan to multiple servers.

When you scale out an App Service Plan, each Web App hosted on the plan is replicated across all the servers. For example, if you had three Web Apps, and an App Service Plan with three instances, then all three of your Web Apps would be running on each instance, and traffic would be load balanced between them:

App Service Multiple Instances

But what if you had different scaling requirements for each web app? Maybe for one web app you only want a single instance, while another web app you'd like multiple instances? That way you could make more efficient use of the instances in your App Service Plan by only running as many instances of each website that you actually need.

Per-App Scaling

This is possible thanks to the "per-app-scaling" feature of App Service, which is available on the Standard pricing tier and above, and enables "high density hosting"

In this post I'll show you how to use Azure PowerShell to configure a simple scenario where we have three web apps (creatively named App 1, App 2 and App 3), and we want 1, 2, and 3 instances respectively on each one, with our App Service Plan scaled out to three instances. So we'd like the web apps to be arranged something like this:

App Service Multiple Instances

Creating an App Service Plan

First, we need to create our App Service Plan and enable per-app scaling. I'm going to use the new Azure PowerShell Az module to configure this. Normally, I like to use the Azure CLI, but we're still waiting for it to support per-app scaling, so this was a good opportunity for me to try the Az module for the first time.

Just like with the Azure CLI, if we're using the PowerShell Az module for the first time, we need to log into Azure with Connect-AzAccount and make sure we're using the correct subscription with Set-AzContext

# Get logged into Azure
Connect-AzAccount

# make sure we've selected the right subscription
Set-AzContext -SubscriptionName "My Subscription"

Next, we'll create a resource group for our App Service Plan with New-AzResourceGroup, and then create the App Service Plan itself with New-AzAppServicePlan, choosing the Standard pricing tier, setting up three workers, and enabling per site scaling with the -PerSiteScaling flag:

$ResourceGroup = "HighDensityTest"
$Location = "westeurope"
New-AzResourceGroup -Name $ResourceGroup -Location $Location

$AppServicePlan = "HighDensityTest"
New-AzAppServicePlan -ResourceGroupName $ResourceGroup -Name $AppServicePlan `
                            -Location $Location `
                            -Tier Standard -WorkerSize Small `
                            -NumberofWorkers 3 -PerSiteScaling $true

Create Web Apps

To help me test I built a very simple ASP.NET Core website with a single Razor webpage, that reads an app setting called "AppName" and also shows the machine name of the server instance that responded to the request:

@page
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

<h1>Web App: @Configuration["AppName"]</h1>
<h2>Served by @Environment.MachineName</h2>

I then created the following PowerShell function to create and configure a WebApp for high density hosting. It first creates the web-app with New-AzWebApp, then it publishes the application code (which I created with dotnet publish on my ASP.NET Core app and then zipped up the publish folder). Next, we use Get-AzWebApp to get details of that web app and update the SiteConfig.NumberOfWorkers to the desired number of instances for this web app. I also add a new application setting containing the site name, which my web app is going to display when the page loads. Finally, we write those settings back to the web app with Set-AzWebApp.

function New-HighDensityWebApp {
    param( [string]$ResourceGroupName, 
           [string]$AppServicePlanName, 
           [string]$WebAppName,
           [int]$NumberOfWorkers,
           [string]$ArchivePath)

    New-AzWebApp -ResourceGroupName $ResourceGroup -AppServicePlan $AppServicePlan `
        -Name $WebAppName
    
    Publish-AzWebApp -ArchivePath $ArchivePath -ResourceGroupName $ResourceGroup -Name $WebAppName -Force
    
    # Get the app we want to configure to use "PerSiteScaling"
    $newapp = Get-AzWebApp -ResourceGroupName $ResourceGroup -Name $WebAppName

    # Modify the NumberOfWorkers setting to the desired value.
    $newapp.SiteConfig.NumberOfWorkers = $NumberOfWorkers
    $newapp.SiteConfig.AppSettings.Add( [Microsoft.Azure.Management.WebSites.Models.NameValuePair]::new("AppName",$WebAppName))
 
    # Post updated app back to azure
    Set-AzWebApp $newapp
}

Now with this function in place, I can easily create my three web apps, each with a different number of workers

$ArchivePath = "publish.zip"
New-HighDensityWebApp -ResourceGroupName $ResourceGroup -AppServicePlanName $AppServicePlan `
                      -WebAppName "mheath-hd-1" -NumberOfWorkers 1 -ArchivePath $ArchivePath
New-HighDensityWebApp -ResourceGroupName $ResourceGroup -AppServicePlanName $AppServicePlan `
                    -WebAppName "mheath-hd-2" -NumberOfWorkers 2 -ArchivePath $ArchivePath
New-HighDensityWebApp -ResourceGroupName $ResourceGroup -AppServicePlanName $AppServicePlan `
                    -WebAppName "mheath-hd-3" -NumberOfWorkers 3 -ArchivePath $ArchivePath

Testing it out

To test it out, let's make a request to my first web application that should have one instance:

(iwr "https://mheath-hd-1.azurewebsites.net/").content

No matter how many times I issue the command, I should always see the same instance name in the response:

<h1>Web App: mheath-hd-1</h1>
<h2>Served by RD0003FF55813B</h2>

However, if I do the same for the web app configured for three workers:

(iwr "https://mheath-hd-3.azurewebsites.net/").content

Then I'll see it cycling through each of the three instances in my web app as I make requests:

<h1>Web App: mheath-hd-3</h1>
<h2>Served by RD0003FF8F5D22</h2>

...
<h1>Web App: mheath-hd-3</h1>
<h2>Served by RD0003FF55813B</h2>

...
<h1>Web App: mheath-hd-3</h1>
<h2>Served by RD0003FF8F4B8F</h2>

That's great - it's working!

Limitations

So it's really nice and easy to configure per-app scaling. But there are a few limitations to be aware of.

First, the Azure Portal doesn't have any UI that lets us view or configure these settings, so you will need to script this with PowerShell or ARM templates.

Second, there is no control over scheduling of the web apps onto the individual nodes like you would have with a container orchestrator like Kubernetes, where you have concepts like "affinity" and "anti-affinity". For example, if I have four web apps: A, B, C and D, and two server instances, and I'd like A and B to be hosted together on one instance, and C and D hosted together on the other, there is no way to request that.

I also tried seeing what would happen if I asked for four workers for a particular web app on my three instance App Service Plan. It didn't error, but it seemed that only two servers were hosting my web app. Maybe it put two instances of the web app on two of the three servers, but I had no easy way of telling whether that was the case.

Summary

Per-app scaling is a welcome addition to Azure App Service that could help you make more efficient usage of an App Service Plan cluster (which could be very valuable for expensive hosting plans like ASE). But there is still a need for improved tooling and visibility, and this feature currently lacks the flexibility to control exactly how you want the web apps distributed across the server instances.

The PowerShell script and sample web app code are available on GitHub at markheath/app-service-per-app-scaling

Comments

Comment by Thomas Levesque

Maybe it put two instances of the web app on two of the three servers,
but I had no easy way of telling whether that was the case.

You can check the WEBSITE_INSTANCE_ID environment variable

Thomas Levesque
Comment by Mark Heath

wouldn't that return the same value for both if two instances were running on the same server? I might experiment with a static variable holding a random number

Mark Heath
Comment by Alex Klaus

Great article!
Do you know if there is a way of doing the same thing in Azure DevOps Releases pipeline?

Alex Klaus
Comment by Mark Heath

There's nothing stopping you running Azure CLI scripts from a DevOps release pipeline. And you can also achieve the same thing through ARM templates. I don't know if DevOps releases pipelines have any specific features to simplify turning on the high-density feature

Mark Heath
Comment by Chris R

This is pretty easy to determine... either through the portal > go to the app service and click on Advanced Tools... OR go directly to the sites kudo at https://mysite.scm.azureweb... (insert scm into the url as shown)... there is an INSTANCE drop-down in the menu that will show you how many instances the app is deployed to.
@markheath1010:disqus Thanks for the procedure, it was easy to follow, even for those of us that are still somewhat novices to powershell!

Chris R