Leveraging Bicep to deploy Azure resources - Part 4
In this concluding post, we will witness Bicep in action as we create an environment with an Azure subscription and Azure Functions from start to finish.
Our objective is to create a subscription that includes a resource group and an Azure Function.
For the sake of conciseness, we will omit the creation of management groups here. However, in real-world scenarios, it is advisable to manage them using Bicep for better organization and governance.
Setting up the environment
- Open Visual Studio, create a new solution.
We will systematically create the required resources, starting with the subscription.
- To deploy Azure resources, it is imperative to have the necessary permissions, specifically being designated as an Owner at the root scope level. This authorization can be granted using the following Azure CLI command.
1az role assignment create --assignee <id_user_assignee> --scope "/" --role "Owner"
"id_user_assignee" refers to the user account under which Azure CLI commands for deployments will be executed.
It's important to note that the ability to grant access to other users also requires authorization. Being a Global Administrator in Microsoft Enterprise ID alone is not sufficient; explicit declaration is needed to confirm the user's capability to manage access to all Azure subscriptions and management groups in the tenant.
To achieve this, navigate to Microsoft Entra ID, access Properties, and select "Yes" in the corresponding section.
Creating a new subscription
- Create a new file named create.subscription.bicep in the solution.
- Add the following content within it.
1metadata description = 'Creates a subscription'
2
3targetScope = 'tenant'
4
5param billingScope string = '/providers/Microsoft.Billing/billingAccounts/432a9fe6-a481-55xxxxxxxxxxxxxxxxxxxxxxxx'
6
7param subscriptionName string = 'EOCS_Bicep'
8
9resource alias 'Microsoft.Subscription/aliases@2020-09-01' = {
10 scope: tenant()
11 name: subscriptionName
12 properties: {
13 workload: 'Production'
14 displayName: subscriptionName
15 billingScope: billingScope
16 }
17}
18
19output subscriptionId string = alias.properties.subscriptionId
Several noteworthy aspects deserve attention at this point.
In Bicep, the targetScope is a property that allows us to specify the deployment scope for the resources defined in a Bicep file. The targetScope property determines where the resources will be deployed when the template is executed.
We have the ability to define certain variables using the "param" keyword.
The ID of the recently created subscription is accessible through the output variables.
The billing scope is essential information for creating a subscription, and obtaining it can be a somewhat intricate process. Follow these steps.
- Execute the command 'az billing account list' in Azure CLI and note the name property.
- Execute the command 'az billing profile list --account-name "<name_property>" --expand "InvoiceSections"'
- Note the id property in the invoiceSections
This property is the billing scope.
In real-world scenarios involving a CI/CD pipeline, some variables should be configured in the current environment and subsequently replaced at runtime by a dedicated task. As an illustration, the subscription's name might be contingent on the environment (dev, staging, or production) in which it is generated, necessitating a name that appropriately reflects this context.
This code is specifically designed for straightforward Microsoft accounts. However, it needs adaptation, particularly for usage with Enterprise Agreement setups.
Deploying the file
To deploy the file, open Azure CLI and execute the following command.
1az deployment tenant create --template-file create.subscription.bicep --location westus
It's evident that the subscription has been successfully created.
Creating a new resource group
Having defined a subscription, the next step is to include a resource group within it.
- Create a new file named create.resourcegroup.bicep in the solution.
1metadata description = 'Creates a resource group'
2
3targetScope = 'subscription'
4
5param resourceGroupName string = 'Infra'
6param location string = deployment().location
7
8resource infraResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
9 name: resourceGroupName
10 location: location
11}
The targetScope is now subscription.
The resource group's location is acquired through deployment().location. This variable is actually supplied when the deployment is executed with Azure CLI, requiring us to specify the location explicitly. More on this later.
Deploying the file
Deploying a resource group requires being within a specific subscription. Therefore, it is essential to switch to the designated subscription first, ensuring that resources are provisioned in the correct location.
1az account set --subscription EOCS_Bicep
2az deployment sub create --template-file create.resourceGroup.bicep --location westus
In the preceding commands, it's evident that we specified the location where the resource group should be deployed. This parameter is subsequently reused by the Bicep file with the deployment().location helper.
Creating a new Azure Function
Ultimately, we deploy an Azure Function within the resource group. It's important to note that an Azure Function requires an associated hosting plan and a storage account.
- Create a new file named create.azurefunction.bicep in the solution.
1param appName string = 'func-company-project-catalogueservice'
2
3param storageAccountType string = 'Standard_LRS'
4
5param location string = resourceGroup().location
6
7param runtime string = 'dotnet'
8
9var functionAppName = appName
10var hostingPlanName = appName
11var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions'
12var functionWorkerRuntime = runtime
13
14resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
15 name: storageAccountName
16 location: location
17 sku: {
18 name: storageAccountType
19 }
20 kind: 'Storage'
21 properties: {
22 supportsHttpsTrafficOnly: true
23 defaultToOAuthAuthentication: true
24 }
25}
26
27resource hostingPlan 'Microsoft.Web/serverfarms@2021-03-01' = {
28 name: hostingPlanName
29 location: location
30 sku: {
31 name: 'Y1'
32 tier: 'Dynamic'
33 }
34 properties: {}
35}
36
37resource functionApp 'Microsoft.Web/sites@2021-03-01' = {
38 name: functionAppName
39 location: location
40 kind: 'functionapp'
41 identity: {
42 type: 'SystemAssigned'
43 }
44 properties: {
45 serverFarmId: hostingPlan.id
46 siteConfig: {
47 appSettings: [
48 {
49 name: 'AzureWebJobsStorage'
50 value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
51 }
52 {
53 name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
54 value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
55 }
56 {
57 name: 'WEBSITE_CONTENTSHARE'
58 value: toLower(functionAppName)
59 }
60 {
61 name: 'FUNCTIONS_EXTENSION_VERSION'
62 value: '~4'
63 }
64 {
65 name: 'WEBSITE_NODE_DEFAULT_VERSION'
66 value: '~14'
67 }
68 {
69 name: 'FUNCTIONS_WORKER_RUNTIME'
70 value: functionWorkerRuntime
71 }
72 ]
73 ftpsState: 'FtpsOnly'
74 minTlsVersion: '1.2'
75 }
76 httpsOnly: true
77 }
78}
79
80output azureFunctionId string = functionApp.id
- The targetScope is not explicitly indicated; by default, it is set to resourceGroup, and it is at that scope that we intend to deploy our function.
Deploying the file
We execute the following command in Azure CLI.
1az deployment group create --resource-group Infra --template-file create.azurefunction.bicep
And there we have it: our resources are provisioned, and we can confidently execute these scripts in another data center if the need arises. Furthermore, these Bicep scripts can be version-controlled, allowing for redeployment as needed.
A final note: where is the current deployment's state stored ? In other words, how does Bicep determine the existence of resources or those that need creation? In contrast to Terraform, where this state must be explicitly stored, the seamless integration between Azure and Bicep relieves developers of this responsibility (Azure automatically manages this for us).
Final thoughts
If you wish to delve deeper into this topic, acquire the following books, which encompass all the concepts emphasized in this series and delve into more advanced ones.
Terraform: Up and Running: Writing Infrastructure as Code (Brikman)
Getting started with Bicep: Infrastructure as code on Azure (Berson)
Do not hesitate to contact me shoud you require further information.