Running .NetCore Apps Faster and Cheaper on Azure

I have been using Azure from almost the very first public beta days as I have previously noted down. Most of the time when I discuss the price/performance details with other fellows in the industry and especially the ones running .NetCore at scale on AWS, I usually heard that AWS being cheaper and faster more reliably. Although the definition of cheaper and faster more reliably definitely varies by context, company and the app itself, still I found it a challenge and worthy business problem. However, I cannot say this is really an argument anymore for me after trying Azure App Services on Linux

As the official documentation discusses, Azure App Service is a fully managed compute platform that is optimised for hosting websites and web applications. Developers can use App Service on Linux to host web apps natively on Linux for supported application stacks. As you can see from the Languages section lists, the application stacks that are currently supported are almost anything you can imagine…

Why?

To give a bit of background, I’m currently working with HelloHub team to push their first MVP out. It is an exciting project in the short-range social network domain. The backend has been running in Azure App Service on Windows. Even though I developed the whole backend piece on my Mac using Rider IDE and .NetCore 2.2, still as a habit, I setup the Azure Devops deployment pipeline and Terraform scripts to run on Windows. For a small scale project like this and in early stages of a Startup, this may not really sound a big issue. However, after seeing roughly ~£50 (SKU price is £40.81 without bandwidth and other charges) on my bill, I started to wonder how much this can be reduced. Eventually any business will prefer cheaper and faster horses to run…

So my aim was clearly reducing ~£50 per month hosting cost of a tiny API running for a SPA Chat app backend. To give a bit more details, the SKU running was B1, 1 Core, 1.75 GB RAM in North Europe data centre.

How?

My first attempt was obviously update the App Service Plan SKU to free tier. To do this, I have updated my terraform script from:

///
resource "azurerm_app_service_plan" "hhasp" {
  name                = "${var.app_service_plan_name}-${var.environments[terraform.workspace]}"
  resource_group_name = azurerm_resource_group.hhrg.name
  location            = var.region

  sku {
    tier = "BASIC"
    size = "B1"
  }
}
///

to following.

///
resource "azurerm_app_service_plan" "hhasp" {
  name                = "${var.app_service_plan_name}-${var.environments[terraform.workspace]}"
  resource_group_name = azurerm_resource_group.hhrg.name
  location            = var.region

  sku {
    tier = "Free"
    size = "F1"
  }
}
///

I ambitiously submitted this change in my Terraform file and expected Azure Devops Pipeline to update my App Service Plan to Free package. This not only failed but also created a trade off. The failure was not obvious but looked to me that you can only create a Free App Service Plan but not downgrade an existing one just using Terraform. This could be a bug somewhere at Azure end and after spending a couple of hours of trial and error, I have accepted it is not working. The trade off I realised was Free plan doesn’t support always on feature. Potentially this was an issue. The summary of this step was I needed to find another way to run a B1 App Service Plan cheaper.

In this context, I hit to Azure Price Calculator website to look at different option. Curiously clicking the Operating System dropdown revealed that running on Linux is almost 4X cheaper 🤩 (4.16x to be exactly geeky…)

So from ~£40.81 per month to £9.79 was a bit of shock to me. I previously followed the Linux option and ended up a lot disappointment in some experimentation to run an existing app as is. However, looks like things moved along very well and the service became production ready.

As a next natural step, I have updated my Terraform script to following as the first step…

///
resource "azurerm_app_service_plan" "hhasp" {
  name                = "${var.app_service_plan_name}-${var.environments[terraform.workspace]}"
  resource_group_name = azurerm_resource_group.hhrg.name
  location            = var.region
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "Basic"
    size = "B1"
  }
}
///

Pretty much adding kind = "Linux" and adding reserved = true did the trick. Rest of the config didn’t need any change. I again submitted this change and hoping to get it done this time. But no, this time I hit to this naively… 🙃

Of course updating infrastructure means, updating build pipeline. I have updated the build tasks to use Ubuntu machine but forgot the tasks were using Visual Studio Solution Build task set…🤦🏻‍♂️ I ended up using dotnet CLI commands to following build definition in the hope of seeing the light at the end of the tunnel!

This time I was really really hopeful to get this done. Build passed well but the deployment failed. The reason was really annoying though. After changing the deployment target, I had to change the release definition to use Azure App Service deploy for Linux task. By default Azure Devops UI selects the v4 of this task and it never looks suspicious. However, when you deploy successfully, runtime issues starts to cripple and seen all over the logs 🕷. I initially thought there is an issue with my dependencies or some DLLs were not compatible with native Linux runtime. However, I just realised adding further logs that the variable substitution of v4 was not working 😱. You can follow the issue on this open ticket. So, using v3 of the task helped me replacing the deployment setting, connection string etc.

Finally I got it running on Linux natively with these hit and learn steps. Now, I would like to share more findings life after deployment

What?

First thing I realised very happily was Azure runs NetCore app on Docker without you as a developer not changing anything at all. Cleverly at the deploy time they containerise your app (my assumption here), and run on Linux.

So technically, you are running you .NetCore app on Azure 4x cheaper on Docker by just redeploying without any change in the app at all.

I didn’t run a heavy load test yet but my initial tests and monitoring logs showed me that my previous Windows deployment was returning me some data from CosmosDb without cache around ~57ms where as this new Linux deployment never sees response time more than ~35ms.

TL; DR

I cannot say this boldly that Linux deployment is ~2x faster for everyone but for me it is really faster than Windows deployment. However, I can 100% definitely say that it is cheaper, 4X cheaper 🤩 (4.16x to be exactly geeky…)