Today’s blog post is about basic Infrastructure as Code principles, about Azure Bicep Language, and how and when to use it to simplify Azure resource deployments.
Deploying resources in Azure is usually quick and easy. We can do it manually via Azure Portal. However, that can negatively impact resource consistency and security. The solution for that is deploying resources by using the practice of Inrastruce as Code where we can.
Infrastructure as a Code
IaaC is a concept that is very near and dear to me. We can use it to describe the infrastructure that we need in the form of a code and save it in a source control system. The real power of that is that we can do entire infrastructures’ deployments in an automated, consistent, and repeatable way. We can also apply software engineering and DevOps practices to our source code to make sure it is versioned, tested, and working as our environment grows. Side benefits of that are that we have complete documentation of our environment in a code. We can use it to rebuild the infrastructure in case of a disaster or create a testing environment identical to our production.
We can write that code in IaaC tools like ARM templates, Terraform, or CLI.
ARM templates
Azure Resource Manager Templates are IaaC solution that is built into Azure. We can use them to deploy, update, delete and manage resources in Azure.
The benefit of using ARM templates over Azure CLI and PowerShell is that ARM templates are a declarative description, where we don’t have to think about the action, only about defining what we want. Resources then get deployed in the correct order based on dependencies. And if the resource with the same configuration already exists, it will not be changed or deployed again.
While ARM templates are great for deploying things, my experience from working with many different customers showed me that companies don’t use them as much. One of the reasons for that is that ARM templates are based on JSON language, which is challenging to write and read.
Here is the basic schema of ARM Templates:
1
2
3
4
5
6
7
8
9
10
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "",
"apiProfile": "",
"parameters": { },
"variables": { },
"functions": [ ],
"resources": [ ],
"outputs": { }
}
And HERE you can find an example of a full template for creating a simple Windows Virtual Machine in Azure.
You can see that the JSON syntax is easy and repeatable, but it can quickly get very complex and long. Figuring out the names of properties, their parameters, and values is where users struggle while writing these templates.
Project Bicep
That is where Bicep comes to strengthen the ARM. Project Bicep is a Domain Specific Language (DSL) made by the ARM Templates team, and it is here to simplify the writing of ARM templates. It moves away from the JSON syntax, and it is much easier to read and write.
While most syntax of the ARM template has been changed, Bicep is not an entirely new language. It works as a transparent abstraction layer on top of the ARM templates. The goal was not to replace ARM templates but to make a better experience for using ARM templates. What does that all mean, you may ask? It means that we have a new language with a simpler syntax that easy to read and write and with code validation. However, we can’t use Azure Bicep to deploy our resources. Instead, code written in Azure Bicep is compiled to ARM JSON format - to our known ARM templates. And we still use these templates to deploy Azure Resources as we did before. We can also use Decompile to convert existing ARM templates to Azure Bicep.
This approach’s benefits over creating an entirely new language are that Azure Bicep can immediately be used to do everything we can do with ARM templates and we have possibility to work with existing ARM templates without the necessity to re-write them all.
Azure Bicep is now in version 0.3, which is supported by Microsoft Support Plans. It can be used in production.
How to use Azure Bicep
Install tools
Bicep is supported in Azure CLI version 2.20.0+ and PowerShell Az module version 5.6.0+. We still need to get a compiler and authoring environment.
For compiler, we can use Bicep CLI, a cross-platform set of tools for compiling Bicep files into ARM templates.
1
2
az bicep install
az bicep upgrade
Or we can also use THIS Bicep PowerShell module from PowerShell Gallery, if we want to do it directly from PowerShell.
And then we need Bicep Extension for VS Code. The Bicep compiler will validate your code at the end. This Extension for VS Code will also validate your code syntax as you type, and it will provide IntelliSense and tab to complete.
Creating a file
Now that we have all prerequisites and our environment ready, we can start coding. This is a new language, and there will be some learning curve.
Here are a few resources to get you started:
As an example, here is how .bicep
file for adding Virtual Network with Tags would look like:
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
param AzureSpringCleanTags object = {
Project: 'Azure Spring Clean'
Year: '2021'
Blog: 'AzureIs.Fun'
}
param location string = resourceGroup().location
param VNetName string
param addressPrefix string = '10.0.0.0/14'
param subnetPrefix string = '10.0.0.0/24'
param subnetName string
resource vn 'Microsoft.Network/virtualNetworks@2020-06-01' = {
name: VNetName
location: location
tags: AzureSpringCleanTags
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: subnetName
properties: {
addressPrefix: subnetPrefix
}
}
]
}
}
Compiling a file
Now that we have our first .bicep file finished, we need to deploy it. For that we can use same deployment commands that we used with ARM template .json files. They will work the same way with .bicep file.
1
2
3
4
5
#Azure CLI
az deployment group create -f ./main.bicep -g MyTestRG --parameters VNetName=MyTestVNet subnetName=MyTestSubnet
#Azure PowerShell
New-AzResourceGroupDeployment -TemplateFile ./main.bicep -ResourceGroupName MyTestRG -VNetName MyTestVNet -subnetName MyTestSubnet
Decompiling ARM template
Decompiling ARM templates into Bicep Language can be very useful if we already have ARM templates, and we want to work with them in Bicep environment. However, here is a friendly warning from Microsoft:
Decompilation is a best-effort process, as there is no guaranteed mapping from ARM JSON to Bicep. You may need to fix warnings and errors in the generated Bicep file(s), or decompilation may fail entirely if an accurate conversion is not possible.
That being said, decompiling JSON based ARM template is as easy as:
1
bicep decompile ".\azuredeploy.json"
Here is that same ARM template for creating a Windows VM in Azure, from previous example, decompiled to Azure Bicep. Comparing them, even if you are not familiar with any programming languages, you can clearly see that while reading this code it is much easier to understand what is going to be deployed.
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
@description('Username for the Virtual Machine.')
param adminUsername string
@minLength(12)
@description('Password for the Virtual Machine.')
@secure()
param adminPassword string
@description('Unique DNS Name for the Public IP used to access the Virtual Machine.')
param dnsLabelPrefix string = toLower('${vmName}-${uniqueString(resourceGroup().id, vmName)}')
@description('Name for the Public IP used to access the Virtual Machine.')
param publicIpName string = 'myPublicIP'
@allowed([
'Dynamic'
'Static'
])
@description('Allocation method for the Public IP used to access the Virtual Machine.')
param publicIPAllocationMethod string = 'Dynamic'
@allowed([
'Basic'
'Standard'
])
@description('SKU for the Public IP used to access the Virtual Machine.')
param publicIpSku string = 'Basic'
@allowed([
'2008-R2-SP1'
'2012-Datacenter'
'2012-R2-Datacenter'
'2016-Nano-Server'
'2016-Datacenter-with-Containers'
'2016-Datacenter'
'2019-Datacenter'
'2019-Datacenter-Core'
'2019-Datacenter-Core-smalldisk'
'2019-Datacenter-Core-with-Containers'
'2019-Datacenter-Core-with-Containers-smalldisk'
'2019-Datacenter-smalldisk'
'2019-Datacenter-with-Containers'
'2019-Datacenter-with-Containers-smalldisk'
])
@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version.')
param OSVersion string = '2019-Datacenter'
@description('Size of the virtual machine.')
param vmSize string = 'Standard_D2_v3'
@description('Location for all resources.')
param location string = resourceGroup().location
@description('Name of the virtual machine.')
param vmName string = 'simple-vm'
var storageAccountName_var = 'bootdiags${uniqueString(resourceGroup().id)}'
var nicName_var = 'myVMNic'
var addressPrefix = '10.0.0.0/16'
var subnetName = 'Subnet'
var subnetPrefix = '10.0.0.0/24'
var virtualNetworkName_var = 'MyVNET'
var subnetRef = resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName_var, subnetName)
var networkSecurityGroupName_var = 'default-NSG'
resource storageAccountName 'Microsoft.Storage/storageAccounts@2019-06-01' = {
name: storageAccountName_var
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'Storage'
properties: {}
}
resource publicIPName_resource 'Microsoft.Network/publicIPAddresses@2020-06-01' = {
name: publicIpName
location: location
sku: {
name: publicIpSku
}
properties: {
publicIPAllocationMethod: publicIPAllocationMethod
dnsSettings: {
domainNameLabel: dnsLabelPrefix
}
}
}
resource networkSecurityGroupName 'Microsoft.Network/networkSecurityGroups@2020-06-01' = {
name: networkSecurityGroupName_var
location: location
properties: {
securityRules: [
{
name: 'default-allow-3389'
properties: {
priority: 1000
access: 'Allow'
direction: 'Inbound'
destinationPortRange: '3389'
protocol: 'Tcp'
sourcePortRange: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
}
}
]
}
}
resource virtualNetworkName 'Microsoft.Network/virtualNetworks@2020-06-01' = {
name: virtualNetworkName_var
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: subnetName
properties: {
addressPrefix: subnetPrefix
networkSecurityGroup: {
id: networkSecurityGroupName.id
}
}
}
]
}
}
resource nicName 'Microsoft.Network/networkInterfaces@2020-06-01' = {
name: nicName_var
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: publicIPName_resource.id
}
subnet: {
id: subnetRef
}
}
}
]
}
dependsOn: [
virtualNetworkName
]
}
resource vmName_resource 'Microsoft.Compute/virtualMachines@2020-06-01' = {
name: vmName
location: location
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
computerName: vmName
adminUsername: adminUsername
adminPassword: adminPassword
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: OSVersion
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'StandardSSD_LRS'
}
}
dataDisks: [
{
diskSizeGB: 1023
lun: 0
createOption: 'Empty'
}
]
}
networkProfile: {
networkInterfaces: [
{
id: nicName.id
}
]
}
diagnosticsProfile: {
bootDiagnostics: {
enabled: true
storageUri: storageAccountName.properties.primaryEndpoints.blob
}
}
}
}
output hostname string = reference(publicIpName).dnsSettings.fqdn
Azure Bicep for Azure Governance
Infrastructure as Code tools, such as Azure Bicep, can help organizations with resource consistency and apply its governance requirements to our environment. We can automate entire resource deployments, as well as ensure that our resources are consistent. We can quickly provision and update infrastructure environments.
In combination with Azure Policy, we can easily ensure that all our resources are named correctly, in specific Azure Regions, with required Tags and Resource Locks, and within the correct SKUs.
Azure Bicep Blog series:
To learn more about Azure Bicep, you can check out my other blog posts in this series:
# | Title |
---|---|
1 | Simplify Your Resource Deployments With Azure Bicep (This article) |
2 | Key Concepts in Azure Bicep Syntax |
3 | Advanced Concepts in Azure Bicep Syntax |
4 | Using External Configuration File with Azure Bicep |
Azure Spring Clean 2021
This blog post is part of the second annual Azure Spring Clean, an excellent initiative by Joe Carlyle and Thomas Thornton. Azure Spring Clean is gathering community-created content focused on managing and cleaning Azure tenants, and that is something we all need. Make sure you check it out.
Thank you for reading and keeping your cloud clean.
Vukašin Terzić