# Host Azure Function on Linux consumption plan

The dynamic consumption plan allows you to pay only for the resources you use when your function is running. Thus, if there are no incoming requests and a function does nothing, you don't pay for it.

I will use Bicep to provision all necessary resources, but it is also possible to work with the Azure portal for that purpose.

What we need to start with is to create the storage account. Azure Function requires to have one to store configuration files and we are going to utilize it to store the code of our function.

The following code creates a storage account and the `release` container.

```plaintext
param appName string
param location string

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: '${appName}storage'
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_ZRS'
  }
  properties: {
    accessTier: 'Hot'
    minimumTlsVersion: 'TLS1_2'
  }
}

resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
  name: 'default'
  parent: storageAccount
}

resource releasesContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
  name: 'releases'
  parent: blobServices
  properties: {
    publicAccess: 'None'
  }
}
```

Microsoft suggests using managed identity over connection strings. Therefore, our function will not have any shared keys to access the storage account. Instead, we create a user-assigned managed identity and grant it Blob Data Owner permissions. An identity with such a role has full access to the storage blob container and data required for the normal operation of a function.

```plaintext
var storageBlobDataOwnerRoleId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'

resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2021-09-30-preview' = {
  name: appName
  location: location
}

resource functionAppStorageBlodDataOwnerUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storageAccount.id, storageBlobDataOwnerRoleId, identity.id)
  scope: storageAccount
  properties: {
    roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataOwnerRoleId)
    principalId: identity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}
```

The last resource we must provision before creating the function is a server farm with a dynamic plan.

```plaintext
resource hostingPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: appName
  location: location
  sku: {
    name: 'Y1' 
    tier: 'Dynamic'
  }
  properties:{
    reserved: true
  }
}
```

At this point, all our preparations are done, and we can proceed to create our function.

```plaintext
param packageName string

resource functionApp 'Microsoft.Web/sites@2022-09-01' = {
  name: '${appName}-func'
  location: location
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${identity.id}' : { }
    }
  }
  kind: 'functionapp,linux'
  properties: {
    reserved: true
    enabled: true
    serverFarmId: hostingPlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'dotnet'
        }
        {
          name: 'WEBSITE_RUN_FROM_PACKAGE'
          value: '${storageAccount.properties.primaryEndpoints.blob}releases/${packageName}'
        }
        {
          name: 'WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID'
          value: identity.id
        }
        {
          name: 'AzureWebJobsStorage__accountName'
          value: storageAccount.name
        }
        {
          name: 'AzureWebJobsStorage__credential'
          value: 'managedIdentity'
        }
        {
          name: 'AzureWebJobsStorage__clientId'
          value: identity.properties.clientId
        }
      ]
    }
  }
}
```

The `functionApp` resource is provisioning the function with the user-assigned managed identity we created earlier. The `kind` element is set to `functionapp,linux`, indicating that we would like to use the Linux function.

The most interesting part here is the `appSettings` block. Let's delve into it.

`FUNCTIONS_EXTENSION_VERSION` and `FUNCTIONS_WORKER_RUNTIME` are settings that define the function's version and runtime. I'm using the 4th version of the Azure function, which, at the time of writing, is the latest and recommended version. Regarding the runtime, it can be not only `dotnet` but also `node`, `java`, etc.

`WEBSITE_RUN_FROM_PACKAGE` specifies the path to the zip file containing the release of a function. As you can see, it points to the storage account and the container we created earlier. The parameter `packageName` determines the name of the zip file.

`WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID` needs to be set if we use a managed identity for accessing the storage account. Otherwise, it can be omitted.

The `AzureWebJobsStorage` settings specify the function storage account and identity required to access it.

The complete [Bicep file](https://github.com/MasDenBy/blog-samples/blob/main/az-function-linux-consumption/deploy.bicep) can be found on GitHub.

## Links

[Azure Functions Consumption plan hosting](https://learn.microsoft.com/en-us/azure/azure-functions/consumption-plan)

[Storage Blob Data Owner role](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-blob-data-owner)

[Storage considerations for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/storage-considerations?tabs=azure-cli)

Image credits: [Jane\_\_ml](https://www.instagram.com/jane__ml/)
