Proper deployment of your Function App using ARM template

In one of my projects I'm using ARM template to deploy both App Service plan and Function App to Azure. The important thing here is the fact, that I'm using a Free tier, so e.g. Always On property is not available for me. In this post I'll show you a small gotcha, which made my functions work improperly.

The structure

Currently the part of my project we're talking about looks like this:

  • App Service plan(Free)
  • Function App containing two functions - one is triggered every 5 minutes, fetches data from a FTP server and pushes it to a queue, the second one receives items from a queue and pushes them to a storage

Really simple, what could wrong here? As you probably know, Function Apps have two modes when it comes to choosing a hosting plan:

  • Consumption Plan(you pay for what you've used)
  • App Service Plan(predefined capacity allocation)

One important thing here is one of features according to Consumption Plan - with it you don't need Always On enabled for your Function App because each time a function is triggered, it is automatically awaken and processed. If you choose App Service Plan, you have to have Always On enabled - if not, your functions may work only occasionally. Since tier Free doesn't allow you to turn on this feature, using wrong consumption plan really breaks your setup.

The problem

When working on the mentioned projected during the last weekend I realized, that when I have a one day break from work, storage is missing items from that day. When I accessed functions to check whether everything is all right, missing items were immediately pushed to it. The same was true for Application Insights attached to my Function App - there was a big gap in the data collected. The conclusion was simple - there's something wrong with a hosting plan. I had to double-check my ARM template.

The template & solution

The part of the template responsible for provisioning my Function App looked like this:

/
{
  "name": "[parameters('liczniknetFunctionAppServicePlanName')]",
  "type": "Microsoft.Web/serverfarms",
  "location": "[resourceGroup().location]",
  "apiVersion": "2014-06-01",
  "dependsOn": [],
  "tags": {
	"displayName": "liczniknet-functionapp-serviceplan"
  },
  "properties": {
	"name": "[parameters('liczniknetFunctionAppServicePlanName')]",
	"workerSize": "[parameters('liczniknetFunctionAppServicePlanWorkerSize')]",
	"numberOfWorkers": 1
  }
}

For some reason I was completely sure, that the default mode for a Function App will be Consumption Plan. There's one small thing - when a Service App plan is being provisioned, it's not aware of resources, which will be deployed further. The solution was quite simple, I was missing two additional properties:

/
{
  "name": "[parameters('liczniknetFunctionAppServicePlanName')]",
  "type": "Microsoft.Web/serverfarms",
  "location": "[resourceGroup().location]",
  "apiVersion": "2014-06-01",
  "dependsOn": [],
  "tags": {
	"displayName": "liczniknet-functionapp-serviceplan"
  },
  "properties": {
	"name": "[parameters('liczniknetFunctionAppServicePlanName')]",
	"computeMode": "Dynamic",
	"sku": "Dynamic",
	"workerSize": "[parameters('liczniknetFunctionAppServicePlanWorkerSize')]",
	"numberOfWorkers": 1
  }
}

With this new setup my Function App is deployed with a proper Hosting Plan and I don't have to awake it manually. 

Working with Azure Functions and VSTS - retrieving secrets

This post is an extension to the post written by Marek Grabarz here. If you haven't got a chance to read, I strongly recommend you to do so - it presents a bit more general approach to automate Azure Functions and can act as baseline when it comes to build your custom solution.

The problem

You have an ARM template and full CI/CD pipeline prepared. All works smoothly and with easy. You're just about to grab a beer and celebrate success when suddenly you realizes, that you haven't put functions' keys to the output. After searching multiple pages you finally finds Marek's post, which explains in detail what is needed to obtain a secret from a function. 

Unfortunately using Azure Active Directory Authentication Library (aka ADAL) with VSTS results with the following error:

/
2017-04-24T08:16:40.9453294Z GAC    Version        Location                                                                                         
2017-04-24T08:16:40.9463285Z ---    -------        --------                                                                                         
2017-04-24T08:16:40.9523277Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:16:40.9683275Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:16:42.5695169Z ##[error]Exception calling "AcquireToken" with "4" argument(s): "user_interaction_required: One of two conditions was encountered: 1. The PromptBehavior.Never flag was passed, but the constraint could not be honored, because user interaction was required. 2. An error occurred during a silent web authentication that prevented the http authentication flow from completing in a short enough time frame"
2017-04-24T08:16:42.6375138Z ##[section]Finishing: Azure PowerShell script: FilePath

All right - maybe using PrompBehaviour.Auto is going to help:

/
2017-04-24T08:42:56.7459955Z GAC    Version        Location                                                                                         
2017-04-24T08:42:56.7459955Z ---    -------        --------                                                                                         
2017-04-24T08:42:56.7519937Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:42:56.7679946Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:42:57.9119830Z ##[error]Exception calling "AcquireToken" with "4" argument(s): "Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application."
2017-04-24T08:42:58.0019817Z ##[section]Finishing: Azure PowerShell script: FilePath

Apparently the way how VSTS authenticates itself is different than doing it locally(when you check logs, you'll see, that it doesn't call Login-AzureRMAccount - instead Add-AzureRMAccount -ServicePrincipal, what could be the reason, why ADAL is problematic in this particular scenario). We have to find another way to get the token for authentication. 

The solution

It seems, that the best way to obtain a token is to call a REST API under https://login.windows.net/{tenantId}/oauth2/token. To do so you need a couple of things:

  • client_id for the service principal VSTS uses
  • client_secret(a key which is connected to the service principal)

The best way to find them is to do following:

1. Go to the Services panel

2. You should see the endpoint defined for VSTS. From here you can click on Manage Service Principal

You'll be forwarded to the old portal. Now go to the Configure tab - from here you can copy client_id needed for the API. You can also find the Keys section which is the last thing we need here - just add another key and copy its value(remember that once you leave this page, you won't be able to retrieve it). Once we're armed with additional data, we can use it to get our token:

/
$tokenEndpoint = "https://login.windows.net/{tenantId}/oauth2/token"
$body = @{
        'resource'= "https://management.core.windows.net/"
        'client_id' = "client_id"
        'grant_type' = 'client_credentials'
        'client_secret' = "client_secret"
}

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = $body
    Method = 'Post'
    URI = $TokenEndpoint
}

$token = Invoke-RestMethod @params
$token | select access_token, @{L='Expires';E={[timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($_.expires_on))}} | fl *

This token can be further used to get a function key like this(assuming you have a master key):

/
$hostKeyRequest = Invoke-RestMethod -Method GET -Uri "https://$functionAppName.azurewebsites.net/admin/HOST/KEYS?CODE=$masterKey" -Headers @{ Authorization = $token }

Summary

Integrating multiple resources in Azure and VSTS can be a little tricky sometimes, but as you can see it still doesn't require much work to get it working. With a simple Powershell script and one call to the API you can authenticate requests from your VSTS instance and make it work with most components available in Azure.