Today I came across a request around how can you deploy your Azure Functions using ARM templates. As someone who always likes a challenge I thought I’d dust off my template skills and see if we could come up with something.
Quickstarts!
It seemed invetiable that there would be a handy template residing in the awesome Azure Quickstart repo and sure enough I found this one as my starting point:
https://github.com/Azure/azure-quickstart-templates/tree/master/101-function-app-create-dynamic
Out of the box this template will provision a new Function App with a Consumption Plan. A great place to start – so let’s take a quick tour and see what shape I bent it in to.
{ "$schema": "http://schemas.management.azure.com/schemas/2015-01-01-preview/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "appName": { "type": "string", "metadata": { "description": "The name of the function app that you wish to create." } }, "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS", "allowedValues": [ "Standard_LRS", "Standard_GRS", "Standard_ZRS", "Premium_LRS" ], "metadata": { "description": "Storage Account type" } }, "codeStorageAccount": { "type": "string", "metadata": { "description": "Storage Account Name for where the zip file to deploy resides" } }, "codeStorageContainer": { "type": "string", "metadata": { "description": "Container where the zip file resides" } }, "codeZipFile": { "type": "string", "metadata": { "description": "Name of Zip file to deploy" } }, "connectionName": { "type": "string", "metadata": { "description": "Name of connection to storage account for this function" } }, "connectionValue": { "type": "string", "metadata": { "description": "Value of connection to storage account for this function" } } },
So at the top we have our standard $schema and contentVersion entries before we configure the parameters that this deployment will use. If you compare my snippet above to the original template you will notice I have added some additional parameters which I’ll make use of later in the template.
The only change I inflicted on the variables section of the template was to update the hostingPlanName variable to conform to my naming standard where all App Service Plans are prefixed asp:
"variables": { "functionAppName": "[parameters('appName')]", "hostingPlanName": "[concat('asp',parameters('appName'))]", "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]" },
Skipping through the file a bit let’s look at the next bit I changed – the properties of the resources section:
"properties": { "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]", "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]", "FUNCTIONS_EXTENSION_VERSION": "latest", "[parameters('connectionName')]": "[parameters('connectionValue')]" }
Here I have added the last parameter – what’s going on here? For my simple GeoCodeFile Function there is only one connection for the storage account where the files are manipulated, in building out this deployment I learnt that the connections you define are stored as environment variables. To find these open your Azure Function click on Function app settings then click on Configure App Settings, on the blade that opens sure enough we can see our properties and connection:
With that in mind let’s have another look at that parameter I defined:
"[parameters('connectionName')]": "[parameters('connectionValue')]"
I’m probably guilty of making this more complex than it needs to be but I was intrigued to see if you can make the parameter name dynamic (you can it seems!). So essentially using this method you do not need to hardcode your connection names in the template, if you have mulitple connections you can simply add more parameters and add an index for example you could have connectionName2 if you need it.
So if we stopped at this point of the proceedings we would have a template that deployed a Function App on a Consumption based plan with our connections pre-configured – but no function yet!
MSDeploy to the rescue!
The excellent post available at: https://blog.kloud.com.au/2016/09/04/azure-functions-deployment-strategies gives a great overview of deployment strategies for Functions and is well worth a read – whilst it does mention MSDeploy it doesn’t show that you can use this as part of the template like so:
{ "name": "MSDeploy", "type": "extensions", "location": "[resourceGroup().location]", "apiVersion": "2015-08-01", "dependsOn": [ "[concat('Microsoft.Web/sites/', variables('functionAppName'))]" ], "tags": { "displayName": "webdeploy" }, "properties": { "packageUri": "[concat('https://',parameters('codeStorageAccount'), '.blob.core.windows.net/', parameters('codeStorageContainer'), '/', parameters('codeZipFile'))]", "dbType": "None", "connectionString": "" } }
The section above has been added as another entry in the resources section. Looking through the code you can see I have added a dependsOn – this ensures we don’t try and deploy the code until the Function App has been provisioned.
The other line of interest here is the packageUri property – MSDeploy needs to be able to download the zip file in order to deploy the code. Here I am building up the URI for the blob storage from the parameters we defined at the top – name of the storage account, container the Zip is in and the name of the Zip file. One thing to note when creating the Zip file – make sure you Zip the folder and not the contents. When you unpack the Zip file it should create the Folder within which your code resides, the folder name needs to match the name of the Function.
Deploy…
In order to deploy our shiny template we can use standard deployment methods as outlined in the documentation – I went with the powershell option:
New-AzureRmResourceGroupDeployment -name EgDeployment -resourcegroupname rgDemoFunctionApp -templatefile .\desktop\functionapp\azuredeploy.json -templateparameterfile .\Desktop\functionapp\azuredeploy.parameters.json
Note that the command above requires the resource group to have already been provisioned.
My complete Template file and accompanying parameter file are available at:
https://github.com/ianalderman/AZF_Samples/tree/master/DeploymentExample
Ian
How do you guys find this method of deployment acceptable ?? I have to pre-deploy my code (to blob storage) before I deploy it ? Seriously?! You really need to rethink what you’re doing with Azure Functions and deployment. Why are you not supporting using MSDeploy directly ? It’s been around forever and people are familiar with it.
Hi Alex thanks for the feedback. In the post https://blog.kloud.com.au/2016/09/04/azure-functions-deployment-strategies details how you can use MSDeploy directly – hopefully that meets your needs?
In this instance the request was how can we encapsulate the whole thing in the template. Hope this helps
Ian
Sir, when i deploy using the MSDeploy ….it creates the folder under wwwroot/package and deploys under package. And function does not run as expected….throws error – Object ref
I pay a visit every day some web pages and information sites to read articles or reviews,
however this blog provides feature based articles.