Posted in:

Following on from my post about what Bicep is, I wanted to provide an example of a Bicep template, and an Azure Function app seems like a good choice, as it requires us to create several resources. There's the Function App itself, but also we need a Storage Account, an App Service Plan, and an Application Insights instance.

There's a helpful example here in the official docs, but I wanted to build my own to learn more about the syntax, and to try to produce a minimal template that was customized for my needs.

Parameters and Variables

A Bicep file can have parameters, and those parameters can be given default values. For my template I wanted the user to supply an "app name" which would be the name used for the Function App. The location is also a parameter, but it defaults to the location of the resource group.

But you can also create variables based on the values of the parameters. In this example, I'm using the appName variable to generate names for the Storage Account, Hosting Plan and App Insights instance. You can see that I've used the uniqueString method to get unique names for various resources and also the substring method to keep the Storage Account name within the valid limits.

param appName string
param location string = resourceGroup().location

// storage accounts must be between 3 and 24 characters in length and use numbers and lower-case letters only
var storageAccountName = '${substring(appName,0,10)}${uniqueString(resourceGroup().id)}' 
var hostingPlanName = '${appName}${uniqueString(resourceGroup().id)}'
var appInsightsName = '${appName}${uniqueString(resourceGroup().id)}'
var functionAppName = '${appName}'

Storage Account

Next, we specify the Storage Account resource. We can give it an identifier (storageAccount) to refer to it later in the template, and Visual Studio Code extension for Bicep will give us autocomplete to greatly simplify defining this resource. On top of name and location, which are set to variable values, the only other things to set up are the Storage Account kind, and pricing tier (known as "sku" in these templates), and again the VS Code autocomplete points us in the right direction for these.

resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: storageAccountName
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
    tier: 'Standard'
  }
}

App Insights

Application Insights has always been a bit of a pain to automate the creation of, and it seems that although Bicep offers some help, this was the bit I struggled with the most.

We need to say that the kind is web for Function Apps, and I discovered after some failed attempts that you do need to provide the properties section for this to work. I simply copied the contents of properties from another template I found, but wasn't sure what to put for WorkspaceResourceId so I just left that out. That's exactly the kind of guesswork I was talking about in my previous post that I'd like to see eliminated. It should be straightforward to discover which properties are and aren't needed for each Azure resource type, what their values mean, and what the defaults are if you leave them out.

Another annoyance is that the Azure Portal likes there to be a special tag on the App Insights resource which points to the Function App it is linked to. This is a bit of a pain to set up and hopefully the Portal can be improved in the future to deduce these connections without needing you to set them up explicitly.

resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' = {
  name: appInsightsName
  location: location
  kind: 'web'
  properties: { 
    Application_Type: 'web'
    publicNetworkAccessForIngestion: 'Enabled'
    publicNetworkAccessForQuery: 'Enabled'
  }
  tags: {
    // circular dependency means we can't reference functionApp directly  /subscriptions/<subscriptionId>/resourceGroups/<rg-name>/providers/Microsoft.Web/sites/<appName>"
     'hidden-link:/subscriptions/${subscription().id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Web/sites/${functionAppName}': 'Resource'
  }
}

App Service Plan

For the App Service Plan, we want to use the consumption plan, and this highlights the mismatch between the names that ARM gives things, and the names they are called in the official documentation. An App Service Plan is a "Server Farm" in ARM template speak, and the consumption plan is called "Dynamic" with the name "Y1".

resource hostingPlan 'Microsoft.Web/serverfarms@2020-10-01' = {
  name: hostingPlanName
  location: location
  sku: {
    name: 'Y1' 
    tier: 'Dynamic'
  }
}

Function App

Finally we have the Function App itself, and this one is perhaps the most complex. Again I borrowed some of this from another sample I found, and this means that I have included a few settings (e.g. httpsOnly and clientAffinityEnabled) that probably aren't strictly necessary for a minimal template. The serverFarmId shows a good example of how easy it is to refer to another Bicep resource (the hostingPlan).

Another thing we need for a Function App is a few "app settings". We should provide the FUNCTIONS_EXTENSION_VERSION and the FUNCTIONS_WORKER_RUNTIME to match the code for our Function App. We also need to link the Function App to the App Insights instance with APPINSIGHTS_INSTRUMENTATIONKEY, which Bicep made really easy - I loved the fact that there was intellisense helping me to pick out this property.

There are two settings (AzureWebJobsStorage and WEBSITE_CONTENTAZUREFILECONNECTIONSTRING) that should contain the connection string of the Storage Account. The fact that we have to go to some lengths to construct this out of various pieces is a bit of an annoyance that I complained about in my previous post. Hopefully this will be simplified in future.

However, I did want to highlight a setting I am not including in the template. Function Apps usually have a WEBSITE_CONTENTSHARE app setting, but the documentation explicitly tells you not to include this in an ARM template, because it will get auto-generated. However, several other examples I saw for Bicep or ARM Azure Function App templates did include this setting and I presume that is because these templates were generated base on an existing Azure resource. This tends to end up with verbose templates that specify more than they need to (or should in this case). For that reason I think it would be nice if VS code offered autocomplete of minimal Bicep templates for each resource type, making it easier to see what you need to provide.

I also haven't set WEBSITE_RUN_FROM_PACKAGE to 1 which is what you would do if you want to use the "run from package" mode of publishing Function Apps (which is a good choice) and were planning to deploy the zips with az functionapp deployment source config-zip. However, if you are going to use func azure functionapp publish that will take care of this app setting for you.

The final thing I want to point out is the ability to configure dependencies with dependsOn. This is an ARM capability that allows us to ensure things get created in the right order (and also supports concurrent creation of resources that don't have dependencies on each other).

resource functionApp 'Microsoft.Web/sites@2020-06-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp'
  properties: {
    httpsOnly: true
    serverFarmId: hostingPlan.id
    clientAffinityEnabled: true
    siteConfig: {
      appSettings: [
        {
          'name': 'APPINSIGHTS_INSTRUMENTATIONKEY'
          'value': appInsights.properties.InstrumentationKey
        }
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
        }
        {
          'name': 'FUNCTIONS_EXTENSION_VERSION'
          'value': '~3'
        }
        {
          'name': 'FUNCTIONS_WORKER_RUNTIME'
          'value': 'dotnet'
        }
        {
          name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
        }
        // WEBSITE_CONTENTSHARE will also be auto-generated - https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings#website_contentshare
        // WEBSITE_RUN_FROM_PACKAGE will be set to 1 by func azure functionapp publish
      ]
    }
  }

  dependsOn: [
    appInsights
    hostingPlan
    storageAccount
  ]
}

Deploying it with the Azure CLI

Deploying this Bicep template couldn't be easier if you already know how to deploy a regular ARM template with the Azure CLI. The latest version of the Azure CLI supports Bicep out of the box with the same command used for ARM templates (az deployment group create).

In this example I show first creating a resource group with az group create and then deploying our template to it with az deployment group create. I also show how we can provide a parameter to the deployment (appName in our example)

$RESOURCE_GROUP = "MyResourceGroup"
$APP_NAME = "myfunctionapp"
$BICEP_FILE = "deploy.bicep"
$LOCATION = "westeurope"

# create a resource group
az group create -n $RESOURCE_GROUP -l $LOCATION

# deploy the bicep file directly
az deployment group create `
  --name mybicepdeployment `
  --resource-group $RESOURCE_GROUP `
  --template-file $BICEP_FILE `
  --parameters "appName=$APP_NAME"

Modularity

A final thing to mention here is that the nice thing about Bicep is that you should only need to do this once. Now I've worked out how to create a Function App in Bicep, I can use this as a "module" in any other application that needs a Function App. This means that I can have even simpler top-level Bicep templates that express what I want in very generic terms (e.g. I need a Function App and a Cosmos DB database), and the lower-level templates which specify the details of how those are configured are not something I need to concern myself with if I'm happy to stick with the defaults I've used elsewhere.

Want to learn more about the Azure CLI? Be sure to check out my Pluralsight course Azure CLI: Getting Started.

Comments

Comment by Thomas Levesque

Thanks for this article, it was very helpful. I only had to make a few minor changes because I also needed a service bus namespace.

Thomas Levesque