Infrastructure-as-Code grows muscles on Azure

Java modules en jlink

[post-meta]

In Cloud development we have become accustomed to using the infrastructure-as-code (IaC) principle to define infrastructure and related configuration together with CI/CD pipelines to deploy the infrastructure to our favorite Public Cloud environment. In my role as a Cloud engineer I have been using ARM-templates with Microsoft Azure to describe infrastructure in complex customer projects for the past years. The ARM-templates concept has been good for me because it helped me with creating well structured and deterministic infrastructure deployment definitions, but it is not all ‘paved roads’ when working with ARM-templates. When talking about ARM-templates with fellow engineers you could get the impression to steer away from using ARM-templates ASAP. Most of the comments are related to the understandability, readability and learnability qualities of the format. There are great tools out there, like VSCode extensions that will help you with understanding and writing ARM-templates through intellisense and code completion features. A weakness though of the format is that ARM-templates are base on JSON. JSON is great for moving payloads between backend and frontend in application development, ‘repurposing’ it for describing infrastructure may be seen as stretching it a bit too far.

ARM-template building blocks

As an example, the first one below is an excerpt of a template concerned with creating a VNET in Azure:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 "resources": [ { "apiVersion": "2019-11-01", "type": "Microsoft.Network/virtualNetworks", "name": "[parameters('vnetName')]", "location": "[resourceGroup().location]", "tags": { "deploymenttype": "[parameters('deploymenttype')]", "environment": "[parameters('environmentTag')]", "functionalscope": "[parameters('functionalScope')]" }, "properties": { "addressSpace": { "addressPrefixes": [ "[variables('vnetAddressPrefixAdapted')]" ] }, "copy": [{ "name": "subnets", "count": "[length(parameters('vnetSpecification').subnets)]", "input": { "name": "[concat(parameters('vnetSpecification').subnets[copyIndex('subnets')].name)]", "properties": { "addressPrefix": "[replace(parameters('vnetSpecification').subnets[copyIndex('subnets')].addressPrefix, 'x', parameters('networkSpace'))]", "serviceEndpoints": "[parameters('vnetSpecification').subnets[copyIndex('subnets')].serviceEndpoints]", "delegations": "[parameters('vnetSpecification').subnets[copyIndex('subnets')].delegations]" } } }] } } ], 
 

Next to defining the resource, the template uses functions to concatenate strings (concat), manipulate strings (replace) and employs array (length) and copy operations (copy/copyIndex) to create a loop for adding subnets to the VNET. The next example defines a template output (principalId) which refers to the principalId of a system-assigned-managed-identity associated to an Azure app-service resource:

1 2 3 4 5 6
 "outputs": { "principalId": { "type": "string", "value": "[reference(concat(resourceId('Microsoft.Web/sites/', parameters('functionAppName')), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').principalId]" } } 
 

Both examples will do the job, the markup is not ideal when keeping the qualities mentioned above in mind though. Despite the quirks and challenges, many engineers are using ARM-templates on a daily basis as the go-to ‘mechanism’ for IaC under Azure.

Project Bicep

Microsoft has taken feedback on authoring ARM-templates to heart through Project Bicep. Project Bicep is a Domain Specific Language (DSL) for ARM-templates. In the summer of 2020 the project released version 0.1, a preview that got me interested in the initiative. The example below contains the bicep representation for deploying an Azure Storage account.

 1 2 3 4 5 6 7 8 9 10 11 12 
param location string = 'westeurope' param name string = 'profit4cloudsa' param environment string = 'dev'
resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: '${name}-${environment}' location: location kind: 'Storage' sku: { name: 'Standard_LRS' } } 
 

Bicep files carry the .bicep filename extension. Version 2.20+ of the az cli tool includes bicep support. Bicep support currently consist of taking in a bicep file and transpiling it to an ARM-template. The command to transpile the example.bicep file to its ARM-template equivalent: az bicep build --files example.bicep In the bicep example above the parameters are hardcoded, alternatively they can be supplied at deployment time through the –parameters command-line option. The generated ARM-template can be found at the end of this article. Defining an output is much simpler when compared to the ARM-template output shown above.

 1 
output stgId string = stg.properties.id 
 

Even with this simple example, it is clear that the bicep file is much cleaner and easier to understand. It uses constructs that will look familiar to software engineers. Some of the advantages of bicep over ARM-templates are:

  • Directly call parameters or variables in expressions without a function (no more need for parameters(name))
  • No quotes on property names (e.g. “location”)
  • String interpolation: ‘${name}sa’ instead of concat(parameters(‘name’), ‘sa’)
  • Direct . property access of a resource (e.g. sa.properties.id instead of reference(parameters(‘sa’)).properties.id)
 

ARM-templates support linked (and nested) templates, Bicep supports a similar mechanism through modules. ARM-templates support dependency engineering via the dependsOn, Bicep support automatic dependency detection and handling. It also support explicit dependency annotations. Instead of generating the ARM-template, you could go for a direct deployment using the bicep file through the command (for example): az deployment group create -f ./example.bicep -g p4cexample --parameters location=westeurope Next to invoking the bicep functionality through the az cli, you can also install the bicep transpiler through the ‘az bicep install’ command. The bicep transpiler also provides decompiler functionality. Using the decompiler you could generate a bicep file from an existing ARM-template. Generating a bicep file which reflects the resources deployed to a resourcegroup in Azure can be accomplished by the following commands (assuming the resourcegroup is named p4cexample): Export-AzResourceGroup -ResourceGroupName "p4cexample" -Path ./allresources.json bicep decompile allresources.json At the start of Ignite 2021 (a few days ago), Project Bicep released version 0.3 as GA. This version is production ready and receives full support from Microsoft. It also has 100% parity with what can be accomplished with ARM-templates. In case you would like to receive more information related to IaC, ARM-templates or Bicep (and possible migration paths), feel free to contact us. Happy IaC-ing….


ARM-template generated from bicep file

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "location": { "type": "string", "defaultValue": "westeurope" }, "name": { "type": "string", "defaultValue": "profit4cloudsa" }, "environment": { "type": "string", "defaultValue": "dev" } }, "functions": [], "resources": [ { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2019-06-01", "name": "[format('{0}-{1}', parameters('name'), parameters('environment'))]", "location": "[parameters('location')]", "kind": "Storage", "sku": { "name": "Standard_LRS" } } ] } 
[post-meta]

Marc Woolderink