Nona Blog

Setting up a cloud function ARM deployment with Microsoft Azure

In the previous article we discussed setting up a cloud function proof of concept with most of the basic deployment components. Which from a POC function deployment perspective would be sufficient, however, when dealing with deployment pipelines there are some additional considerations:

  1. Creating and updating multiple resources within a group at the same time
  2. A persistent state of resources within a group its and configuration
  3. A way to test that a set of resources within a group as well as its configuration is deployed or removed as expected

Enter ARM

The Azure Resource Manager or ARM is a consistent way in which we can achieve the above mentioned consideration requirements. The AWS equivalent to this would be Cloud Formation.

Let’s begin by creating the main configuration file azuredeploy.json and populating it with the following contents:

{
   "$schema":"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
   "contentVersion":"1.0.0.0",
   "variables":{
      "storageAccountName":"nonaarmtest1",
      "functionAppName":"NonaTestArmApp",
      "storageAccountType":"Standard_ZRS"
   },
   "resources":[
      {
         "type":"Microsoft.Storage/storageAccounts",
         "apiVersion":"2019-04-01",
         "name":"[variables('storageAccountName')]",
         "location":"[resourceGroup().location]",
         "sku":{
            "name":"[variables('storageAccountType')]"
         },
         "kind":"StorageV2",
         "properties":{
            
         }
      },
      {
         "apiVersion":"2016-03-01",
         "type":"Microsoft.Web/sites",
         "name":"[variables('functionAppName')]",
         "location":"[resourceGroup().location]",
         "kind":"functionapp",
         "dependsOn":[
            "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
         ],
         "properties":{
            "siteConfig":{
               "appSettings":[
                  {
                     "name":"AzureWebJobsStorage",
                     "value":"[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
                  },
                  {
                     "name":"FUNCTIONS_WORKER_RUNTIME",
                     "value":"node"
                  },
                  {
                     "name":"WEBSITE_NODE_DEFAULT_VERSION",
                     "value":"10.14.1"
                  },
                  {
                     "name":"FUNCTIONS_EXTENSION_VERSION",
                     "value":"~2"
                  }
               ]
            }
         }
      }
   ]
}

Let’s tackle this one section at a time:

Within the above json there are some initial boilerplate parameters ($schema and contentVersion), but we are only really interested in the variables and resources entries.

Let’s look at variables first as it is the most simple, and a pre-requisite to understanding the resources parameter.

variables is essentially just a list of variables that we define which can be referenced in other areas of our template. It is also interesting to note that there are some pre-configured variables which we can use within the resources configuration section – resourceGroup().location being one of them.

Before creating a new variable I would suggest finding out whether or not it is something that already exists in the current template context, so to avoid unnecessary definitions within your resource deployment.

The resources section is basically where you define individual resources that you would like to add to a particular resource group. Within our cloud function POC we needed at least storage and functionapp resources.

These are identified and configured by the type and kind parameters. Which within the above json template is identified as follows:

  • Microsoft.Storage/storageAccounts (StorageV2) – storage account definition – the storage of the cloud function code, logs and configuration.
  • Microsoft.Web/sites (functionapp) – a function app definition – responsible for building, deploying and scaling cloud functions.

Within each resource we can also define other attributes (which may be resource type specific) such as apiVersion, dependences and the location of the resource (generally the location will be the same as that of the resource group).

More information on ARM template definitions and syntax can be found here –https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax

We can now deploy a group of resources as a stack by specifying:

  • The resource group name (NB to remember the resource group itself is independent of the resources deployed within it- so you will need to create a resource group before attempting an ARM deployment for that resource group. You can create a new resource group using – “az group create --name {group-name} --location {location-name}” – for more information on how to do this please see the previous article on the topic).
  • The template file that will contain the resource definitions – which in our case is azuredeploy.json
  • The deployment mode – https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-modes

The deployment mode is a critical factor to consider from a deployment pipeline perspective. The reason for this is that updating an existing ARM deployment stack can become complicated, especially when dealing with deleting resources.

From my experience using the Complete deployment mode is the best option initially when considering resource deletion, as your json definitions essentially becomes the source of truth for what resources exist within your resource group. However, using the Complete mode also means that each deployment will overwrite as opposed to apply changes to your resource group. For our purposes we are going to stick to Complete mode though for the sake of keeping the scope of this article as clean as possible.

Considering the above our deployment (stack) creation script will look as follows:

az deployment group create --resource-group myResourceGroup --template-file ./azuredeploy.json --mode Complete

Deleting and updating resources within our resource group

Again considering the --mode Complete parameter, the result is that if we remove a resource entry from our azuredeploy.json file – it will attempt to delete that resource from our resource group (as the file in “Complete” mode will reflect the source of truth as to what exists on the Azure cloud).

My suggestion though would be to create an additional script e.g. azuredestroy.json and populate it as follows:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "resources": []
  }

Noting here that we have just removed the variables parameter as well as the contents of the resources parameter (leaving it as an empty array).

We can now run this as a resource deletion script:

az deployment group create --resource-group myResourceGroup --template-file ./azuredestroy.json --mode Complete

Obviously to update an existing resource we can change the contents of the current azuredeploy.json file and re-run the create command.

Deployment Testing

From a testing perspective there are are couple of things we can do.

Firstly we can test the validity of our ARM deployment file with the following command:

az deployment group validate --resource-group myResourceGroup --template-file ./azuredeploy.json

This will do two things:

  1. The output of this command will confirm that the resulting resource stack (considering variables, identifiers and dependencies) is as expected.
  2. The command will fail on any syntax or obvious dependency issues within the definition file.

The second thing we can do is verify (through the Azure CLI) that our resources were deployed or deleted as expected.

The simplest way to do this would be to run a check against an existing resource list once the deployment or destroy script has run e.g

We can verify our storage resource has deployed as expected with the following command:

az storage account list

And similarly the functionapp resource:

az functionapp list

This would probably be best to run after creating, updating and deleting resources.

We could obviously take this a bit further and incorporate the above into an automated script which is compared against some pre-defined expected outputs, however, that is outside of the scope of this article.

Conclusion

We have successfully tackled our additional considerations, especially with regards to persistence and testing. However, this is a basic creation / deletion deployment cycle, which is sufficient for a simple deployment pipeline and may not suffice for a more complicated setup. I would be keen to hear any thoughts or improvements that you might have on the current setup or on the topic in general.

Nona helps funded businesses accelerate their software projects. If you’d like to soundboard your tech project or your development team, book a consultation with us and we can chat through it! 

Richard Miles

Richard Miles

Fullstack Developer - Nona

Add comment