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.
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.
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.
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.
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 can be found on GitHub.
Links
Azure Functions Consumption plan hosting
Storage considerations for Azure Functions
Image credits: Jane__ml