Infrastructure as Code - Deploy an Azure Function using Bicep
Tags: azure, devops, cloud, serverless
Published: 2022-04-25 17:58:52

## Introduction
In this guide, we will look at the topic _Infrastructure as Code_ using the example of an Azure Function. But what is Infrastructure as Code? Based from the official Microsoft documentation, Infrastructure as Code can be described as follows:

> Infrastructure as Code (IaC) is the management of infrastructure (networks, virtual machines, load balancers, and connection topology) in a descriptive model, using the same versioning as DevOps team uses for source code. Like the principle that the same source code generates the same binary, an IaC model generates the same environment every time it is applied. IaC is a key DevOps practice and is used in conjunction with continuous delivery.

A key property of IaC is the so-called _Idempotence_, which means that the deployment command always brings the target system into the desired state, regardless of the initial state. This can be achieved either by configuring the target system or by creating a fresh environment.

In simpler terms, IaC ensures that we always have the desired configuration of our target system. In classic on-prem scenarios, the target systems are unique, e.g. staging and production systems may differ. These differences can cause the application to work on the staging system but then fail on the production system (e.g. missing permissions, network access, etc.). IaC tackles this issues and helps to reduce such risks. Additionally, since the required infrastructure is now available as code, it can be easily versioned via Git.

For IaC various tools are around:
- [Azure Resource Templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview)
- [Azure Bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep)
- [Terraform](https://www.terraform.io/)
- [Pulumi](https://www.pulumi.com/)
- [Chef](https://www.chef.io/products/chef-infra)
- [Puppet](https://puppet.com/)
- others...

The built-in tools for Azure are ARM and Bicep Templates, the other tools listed are third party tools for which additional costs may apply.
In this guide we will cover only ARM and (mainly) Bicep. ARM and Bicep offer the advantage that they are developed directly by Microsoft and therefore always have access to the latest features. The only downside is that these technologies are not suitable for multicloud scenarios, as they are developed exclusively for the Microsoft Azure Cloud. 
Also, another plus point for ARM and Bicep, both are free and fully supported by Microsoft :wink:.

### ARM vs. Bicep
ARM and Bicep both offer basically the same functionalities, but Bicep is a newer technology compared to ARM, so it can happen that some ARM features are not yet available in Bicep (for example like [User-defined ARM Functions](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/user-defined-functions)).

ARM templates are JSON files, all resources are represented as JSON. Bicep on the other hand is a declarative language, comparable to Terraform. Let's have a look:

_Example ARM Template_:
```json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "storageAccountName": {
      "type": "string",
      "defaultValue": "[format('toylaunch{0}', uniqueString(resourceGroup().id))]"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-06-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot"
      }
    }
  ]
}
```

_Example Bicep Template_:
```
param location string = resourceGroup().location
param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
  }
}
```
I think it's obvious that the Bicep template is significantly shorter and even if you've never worked with either technology, it's much easier to see what's going on inside the Bicep template than in the ARM template.

In my opinion, Bicep offers the following advantages over ARM templates:
:crown:	Less code
:crown:	Much easier to read
:crown:	Fully integrated into VS Code with awesome intellisense capabilities

Even if you need ARM templates, you can compile Bicep into ARM templates. This actually happens in the background when you deploy a Bicep template to Azure. During the deployment the Bicep template is compiled into an ARM template and then the ARM template is submitted to Azure.

It is also possible to decompile an ARM template and thus convert an ARM to a Bicep template.

Personally, I think if you don't have a strong reason to use ARM templates, you should definitely use Bicep, as the syntax is much simpler and more readable.

## Let's get started

### Perquisites 
- Azure Account with an active subscription. In case you do not have an Azure Account, go ahead and create one for free [here](https://azure.microsoft.com/en-us/free/)
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
- [Bicep Tools](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/install) for Azure CLI and VS Code 

> :information_source: It is absolutely possible to create the Bicep templates with the editor of your choice, however, I strongly recommend you to try the VS Code Extension for Bicep as it greatly simplifies the template development. 

### Azure Function Resources Overview
Before we actually getting started, let's take a look at what resources we typically need for an Azure Function:
![Azure Function Resources Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmbrgxi5nf707mysw02i.png)

**Function App**
This is the actual "Function App" service, where the code is running.

**App Service Plan**
The App Service Plan is comparable to a web server and describes how the Function App is hosted. The App Plan also specifies how the Function App scales, the resources available per instance, and which "advanced" features can be used, such as Azure Virtual Network connectivity. In addition, different prices apply depending on the choice of the hosting plan. The following plans are available:
- Consumption Plan (= serverless)
- Premium Plan
- Dedicated hosting plan 


For more information, please checkout the official documentation on [Azure Functions hosting options](https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#overview-of-plans).

**Storage Account**
When using the Consumption/Premium hosting plan, the function code and binding configuration files are stored in Azure Files in the main storage account. In addition, certain platform features may use the storage account for internal operations, such as [Azure Durable Functions](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview).

Fore more information, please checkout the official documentation on [Azure Function Storage Considerations](https://docs.microsoft.com/en-us/azure/azure-functions/storage-considerations).

**Application Insights (optional)**
Application Insights is the preferred monitoring service of Azure Functions and offers built-in logging functionalities. Application Insights collects log, performance, and error data. By automatically detecting performance anomalies and featuring powerful analytics tools, you can more easily diagnose issues and better understand how your functions are used. 

> :information_source: Application insights is an optional service that is billed separately. However, the cost are very low and it is highly recommended to use it with your Function App.

For more information, please checkout the official documentation on [Monitoring Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring) and [Azure Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview).

**Key Vault (optional)**
Azure Key Vault is a cloud service for securely storing and accessing secrets. A secret is anything to which access should be strictly controlled, such as API keys, passwords, certificates, or cryptographic keys.

Azure Key Vault Secrets can only be accessed via a secured and authenticated connection. In the context of the Azure Function, a system or user assigned identity must be created with which the Azure Function authenticates itself to the Key Vault service. The permissions must then be set up on the Key Vault in form of an access policy.

>:information_source: Azure Key Vault is an optional service and is primarily used for secure storage and retrieval of sensitive data. Storing sensitive data directly in the function is not recommended, as this data is stored in plain text, meaning that anyone who has access to the storage account can read this information. In addition to secrets, certificates and keys can also be stored in the Key Vault, and the Key Vault also offers various other functionality such as the encryption of data via public and private key. This service is also charged separately. 
 
To learn more about [Azure Key Vault](https://docs.microsoft.com/en-us/azure/key-vault/general/overview) or on how to [Integrate Azure Key Vault into your Azure Function](https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references), please checkout the official documentation.

### Templates
Now that we know what resources we need for our function, we can proceed with the development of the templates.

#### Storage Account
This template creates the Storage Account, for later use the connection string is constructed and exported. The connection string is needed later in order to connect the Function App to the storage account.

As briefly mentioned before, certain function features, such as Durable Functions, require certain Storage Services. If further storage services are required, they can be added to this template.

{% embed https://gist.github.com/manukanne/2a790627eb7d57172561b05368b5b8f2 %}

#### Application Insights
Creates the Application Insights Resource for monitoring the Function App. To connect the Application Insights instance to the Function App, the Instrumentation Key must be exported for later assignment.

{% embed https://gist.github.com/manukanne/824f4788b66d55d8c9bb0856172befc3 %}

#### App Service Plan
Deploys the App Service Plan of the Function, here the plan id is exported for later usage.

{% embed https://gist.github.com/manukanne/1801dfe99166fc550807f3ac3c3790be %}

> :warning: The operating system varies depending on the language, please make sure to use the correct OS based on the chosen programming language, for more details please checkout the [official documentation](https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#operating-systemruntime).

#### Function App (without settings)
Deploys the function app without settings, here also the previously exported plan id is passed into the template (the plan id specifies the selected hostion option of the Azure Function). 
Also a Managed Identity is assigned, as discussed before this is needed for the authentication against the Key Vault. The identity information, such as principal id and tenant id, as well as the name of the Function App are exported for later use.

{% embed https://gist.github.com/manukanne/4d7a5524d39a6ba1ab25bcb174f7f32f %}

#### Key Vault
Creates the Key Vault. The previously configured managed identity is passed to the template and the necessary permissions for this managed identity are then set up using an access policy. In addition, any required secrets are created here and then exported for later referencing.

{% embed https://gist.github.com/manukanne/1752dc4a6f32bc4cedf3cc2e3528efa5 %}

#### Function App Settings
Creates the Function App Settings. Here various settings of the Function App are set, such as the Storage Account, the Application Insights instance, Function App runtime and any other custom parameters including secrets.

{% embed https://gist.github.com/manukanne/82b4c6465ca3fc280bb71e7a83347cfa %}

#### Main
In order to create the function this template must be deployed. This template glues everything together and deploys all previously created templates in the correct order. The dependencies of the resources are also defined here, e.g. the function app should only be deployed if the deployment of the storage account, the application insights instance and the app service plan was successful. 

{% embed https://gist.github.com/manukanne/7b8329ebde1ed6603eaecfd7d6bcf17c %}

### Deployment
For creating the function, a resource group is required in advance. If the desired resource group is already present, this step can be skipped.

```bash
az group create -n <name> -l <location>
```

For the deployment various parameters have to be specified including:
- Location
- Storage Account name
- Storage Account SKU
- Application Insights name
- App Service Plan name
- Function App operating system
- Function App name
- Function App runtime

We have two options for specifying the parameters:
_Inline parameters_:
```bash
az deployment group create \
  --resource-group testgroup \
  --template-file <path-to-bicep> \
  --parameters exampleString='inline string' exampleArray='("value1", "value2")'
```

_Parameter file_:
```bash
az deployment group create \
  --name ExampleDeployment \
  --resource-group ExampleGroup \
  --template-file storage.bicep \
  --parameters @storage.parameters.json
```
Since we need have to specify a few parameters I suggest that we go with the parameter file variant. A parameter also offers the following advantages:
- Is part of the repository and can therefore be versioned in Git
- A separate configuration can be created for each environment (e.g. staging, production, etc.)
- Facilitates deployment via CI/CD pipelines

The parameter looks like this, please just adjust the parameters as needed:

{% embed https://gist.github.com/manukanne/51d45cbafbaab98674144af618ea7d9a %}

> :information_source: For Azure there are recommendations from Microsoft on how resources should be named, for more details please checkout [Recommended abbreviations for Azure resource types](https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations). Of course, these are only recommendations, resources can be named in any way you like.

To deploy the Azure Function we can use the following command:
```bash
az deployment group create \
  --name <deployment-name> \
  --resource-group <resource-group-name> \
  --template-file bicep\main.bicep
  --parameters @<path-to-parameters>
```
> :warning: For some names there are certain limitations, e.g. special characters, character lengths, etc. For example, the name of a storage account must be between 3 and 24 characters, should not contain any special characters and the name must be globally unique. If an error occurs during deployment, please read the error message carefully and adjust the names if necessary.

That's it! Deployment takes a few seconds and then the Azure Function is ready to go.

## Conclusion
Infrastructure as Code is an extremely powerful tool and an important building block that paves the way for further DevOps steps, such as the automated creation and configuration of environments via CI/CD mechanics.
On top of that, Microsoft offers with Bicep and VS Code two excellent tools with which the creation of templates is a breeze.

Even if we have only looked at the tip of the iceberg, I hope I could awaken your interest and that you liked my blog post.

Thanks for reading, if you have any questions feel free to leave a comment :smiley: 

Here you can find the repository with the full source code:
{% embed https://github.com/manukanne/tutorial-az-func-iac %}