- get to know the Azure Resource Manager and Resource Providers
- learn about how ARM templates are structured and what you can do with them
- create a basic template and deploy them via the Azure Portal
- work with parameters and deploy using Azure CLI
Before we can get into Azure Resource Manager and the related templates, we need to clarify some terms:
- Resource – an element manageable via Azure. For example: a virtual machine, a database, a web app etc.
- Resource Group – a container for resources. A resource can not exist in Azure without a ResourceGroup (RG). Deployments of resources are always executed on an RG. Typically, resources with the same lifecycle are grouped into one resource group.
- Resource Provider – an Azure service for creating a resource through the Azure Resource Manager. For example, “Microsoft.Web” to create a web app, “Microsoft.Storage” to create a storage account etc.
- Azure Resource Manager (ARM) Templates – a JSON file that describes one or more resources that are deployed into a given scope (i.e., Resource Group, Subscription, Management Group or Tenant). The template can be used to consistently and repeatedly provision the resources.
- Bicep - An abstraction for ARM templates that (similar to TypeScript for JavaScript) make it a lot easier to create & maintain templates. While this training will use ARM templates to teach you the basics, it is highly recommended to check Bicep out once you got the general idea.
One great advantage when using ARM templates, is the traceability of changes to your infrastructure. Templates can be stored together with the source code of your application in the code repository (Infrastructure as Code).
If you have established Continuous Integration / Deployment in your development process (which we will do on Day 4), you can execute the deployment of the infrastructure from Jenkins, TeamCity, GitHub or Azure DevOps. No one has to worry about an update of the environment – web apps, databases, caches etc. will be created and configured automatically – no manual steps are necessary (which can be error-prone, as we all know).
- Challenge 4: ARM - Azure Resource Manager (ARM) Templates
The Azure Resource Manager is the deployment and management service for Azure. It provides a management layer that enables you to create, update, and delete resources in your Azure environment.
You can access the resource manager through several ways:
- Azure Portal
- Azure PowerShell
- Azure CLI
- plain REST Calls
- SDKs
As you can see in the picture above, the Resource Manager is made of several Resource Providers (RP) that are ultimately responsible for provisioning the requested service/resource. Resource providers can be independently enabled or disabled. To see, what RPs are active in your subscription, you can either check the Portal (Subscription --> Resource Providers) or use Azure CLI (e.g. in the Cloud Shell) to query the resource manager:
$ az provider list -o table
Namespace RegistrationPolicy RegistrationState
-------------------------------------- -------------------- -------------------
Microsoft.ChangeAnalysis RegistrationRequired Registered
Microsoft.EventGrid RegistrationRequired Registered
Microsoft.Logic RegistrationRequired Registered
Microsoft.Security RegistrationRequired Registered
Microsoft.PolicyInsights RegistrationRequired Registered
Microsoft.AlertsManagement RegistrationRequired Registered
Microsoft.Advisor RegistrationRequired Registered
Microsoft.Web RegistrationRequired Registered
microsoft.insights RegistrationRequired Registered
Microsoft.KeyVault RegistrationRequired Registered
Microsoft.Storage RegistrationRequired Registered
Microsoft.Portal RegistrationFree Registered
Microsoft.DocumentDB RegistrationRequired Registered
Microsoft.Search RegistrationRequired Registered
Microsoft.Cdn RegistrationRequired Registered
Microsoft.Cache RegistrationRequired Registered
Microsoft.ResourceHealth RegistrationRequired Registered
Microsoft.Sql RegistrationRequired Registered
(...)
:::tip 📝 Resource providers talk REST (what a surprise ;-)) Here is an example of a REST call that creates a storage account:
REQUEST HEADER
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/mystorageaccount?api-version=2016-01-01
REQUEST BODY
{
"location": "westus",
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
}
:::
As you can see, this deployment is executed against the resource group scope. In this sample, the template will be ultimately picked-up by the Microsoft.Storage resource provider, which then will be responsible for creating a Storage Account for you.
But of course - nobody wants to construct native REST calls. Instead we focus on the JSON payload and let Azure CLI or PowerShell do the REST communication/transmission part. The JSON document we are about to create is an ARM Template.
A full ARM template is more than just a REST JSON payload. It usually consists of several parts:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [],
"outputs": {}
}
- parameters – Parameters (e.g. a virtual machine's name) that are passed from the outside to the template. Typically, from the command line or your deployment tool (e.g. Terraform, Azure DevOps, Jenkins...)
- variables – variables for internal use. Typically, parameters are “edited”, e.g. names are concatenated strings and stored in variables for later use.
- resources – the actual resources to be created
- outputs – Output parameters that are returned to the caller after the template has been successfully applied. With outputs you can achieve multi-stage deployments by passing outputs to the next ARM template in your deployment chain.
Sticking to our sample from above (Storage Account), let's create a basic template that will create a Storage Account and makes use of a very helpful feature (Template Functions). Here it is:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"metadata": {
"description": "The name of the storage account to be created."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "The location of the storage account to be created."
}
}
},
"variables": {},
"resources": [
{
"name": "[parameters('storageAccountName')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2022-05-01",
"location": "[parameters('location')]",
"tags": {
"displayName": "[parameters('storageAccountName')]"
},
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "StorageV2",
"properties": {}
}
],
"outputs": {
"storageAccountConnectionString": {
"type": "string",
"value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]"
}
}
}
Alternative Bicep visual
@description('The name of the storage account to be created.')
param storageAccountName string
@description('The location of the storage account to be created.')
param location string = resourceGroup().location
resource storageAccount 'Microsoft.Storage/storageAccounts@2015-06-15' = {
name: storageAccountName
location: location
tags: {
displayName: storageAccountName
}
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
kind: 'StorageV2'
properties: {}
}
output storageAccountConnectionString string = storageAccount.listKeys().key1
:::tip
📝 As you can see, we are able to use functions to do dynamic stuff within a template, e.g. reading keys (listKeys()
) or using parameter (parameters()
). There are, of course, other functions e.g. for string manipulation (concatenate, padLeft, split...), numeric functions, comparison functions, conditionals etc. You can find all available template functions and their documentation here: ARM template functions. Please make yourself familiar with the list!
:::
Note: The template is only intended for illustration purposes. Currently, ARM-Outputs (as opposed to inputs) cannot be obscured. This means, the above template would expose the Storage Account key in plain text.
There are several ways to kick off an ARM template deployment:
- Azure CLI
- PowerShell
- Azure Portal -> '+' -> 'Template Deployment'
- ...
We will try each of these to learn its haptic.
-
We use the Azure Portal. You may find it strange to use a web portal that offers wizards and use it to process our template. However, when testing/developing templates errors will occur. In this case the portal offers immediate feedback e.g. for validation errors. So do:
[Azure Portal] -> '+' -> 'Template deployment' -> Create -> 'Build your own template in the editor'
-
Copy and paste the above ARM template into the portal editor window
-
Hit save. This will bring you to the next page.
:::tip 📝 This page contains:
- fixed elements: The deployment scope - where should your resources be deployed (subscription & RG) -
- dynamic elements: The parameters - defined in the template will define the 'look' and hence what the user needs to provide.
:::
-
Enter a unique lowercase value for the storage account and kick off the deployment.
-
When finished take a look at the outputs section:
:::tip 📝 Think of it that way: You can define the outputs of a deployment in the template for later use e.g. if you want to upload something in the next step of your deployment and need the access key for this. :::
You have noticed that you have been asked to enter a value for the parameter storageAccountName
. In a fully automated deployment, this is not really useful.
Using e.g. the Azure CLI you can specify an input
parameter file that goes along with the deployment file.
az deployment group create -g basicarm-rg -n firsttemplate --template-file=./azuredeploy.json [email protected]
In PowerShell, the same command would look like
New-AzResourceGroupDeployment -ResourceGroupName 'basicarm-rg' -TemplateFile './azuredeploy.json' -TemplateParameterFile './azuredeploy.params.json'
Doing so you can set up parameter files for different environments, e.g.:
- azuredeploy.params.DEV.json
- azuredeploy.params.TEST.json
- azuredeploy.params.PROD.json
- ...
:::tip
📝 One template + multiple parameter files. That's how most people do it.
:::
Example for our storage account parameter:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"metadata": {
"description": "The name of the storage account to be created."
}
},
// (...)
}
Alternative Bicep visual
@description('The name of the storage account to be created.')
param storageAccountName string
The corresponding parameter file would look like this.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"value": "adcmyarmsa"
}
}
}
Note: ARM Parameter files can be used with Bicep templates.
Now I want you to create your first ARM Template. A speed course - based on the Tutorial: Create and deploy your first ARM template
You will:
- Open Visual Studio Code and create an empty ARM template file.
- Add a resource to it.
- Use an ARM function
- Create a parameter
- Generate a parameter file
- Deploy it using Azure CLI
and create an empty ARM template file (e.g. pip.json). Use the following code snippet
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": []
}
Put the JSON code snippet that defines a public IP address into the resources section of the template
{
"name": "string",
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2020-06-01",
"location": "string",
"sku": {
"name": "usefulstring"
},
"properties": {
"publicIPAllocationMethod": "usefulstring",
"publicIPAddressVersion": "usefulstring"
}
}
Alternative Bicep visual
resource string 'Microsoft.Network/publicIPAddresses@2020-06-01' = {
name: 'string'
location: 'string'
sku: {
name: 'usefulstring'
}
properties: {
publicIPAllocationMethod: 'usefulstring'
publicIPAddressVersion: 'usefulstring'
}
}
::: tip
Try a Shift-ALT-F to format the code after inserting. (requires ARM extension to be installed)
:::
You need to replace the "usefulstring" with meaningful data. See the following links as reference:
- Microsoft.Network publicIPAddresses
- All Azure resources are listed in the ARM Templates Reference. e.g. Public IP is in the network resource provider.
Concentrate on
"location": "string",
Azure resources go into a resource group. A resource group is located in an azure region. So you may want to use the same region for your public IP Address as it's hosting resource group.
Lucky us. There is a function available - that we can use. Replace "string" with "[resourceGroup().location]
"
Create a parameter so that a user can enter the name of the IP Address artefact. Therefore add a parameter section to the template. Checkout Make template reusable to see how it is done.
As example you can compare your ARM Template with the following code snippet:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"IPName": {
"type": "string",
"metadata": {
"description": "The name of the Azure IP adress artefact"
}
}
},
"resources": [
{
"name": "[parameters('IPName')]",
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2020-06-01",
"location": "[resourceGroup().location]",
"sku": {
"name": "Basic"
},
"properties": {
"publicIPAllocationMethod": "Static",
"publicIPAddressVersion": "IPv4"
}
}
]
}
Alternative Bicep visual
@description('The name of the Azure IP adress artefact')
param IPName string
resource IP 'Microsoft.Network/publicIPAddresses@2020-06-01' = {
name: IPName
location: resourceGroup().location
sku: {
name: 'Basic'
}
properties: {
publicIPAllocationMethod: 'Static'
publicIPAddressVersion: 'IPv4'
}
}
Use VS Code to generate a parameter file for you and enter a value:
As example you can compare your parameters file with the following code snippet:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"IPName": {
"value": "my-PIP"
}
}
}
6. Kick off the deployment using the Azure CLI. To Do this you can upload the template and parameter file to your Azure Cloud Shell:
Create a resource group first by executing e.g.:
az group create -l 'northeurope' -n 'rg-MyFirstARM'
Kick off the deployment by executing e.g.:
az deployment group create -g 'rg-MyFirstARM' --template-file 'pip.json' --parameters '@pip.parameters.json'
Result:
::: tip
There exist two modes how to deploy an ARM template:
- Complete – resources that are not present in the template, but do exist in the resource group, are deleted.
- Incremental (default)– resources that are not present in the template, but exist in the resource group, remain unchanged.
:::
Execute the following commands:
az group delete -n basicarm-rg --no-wait --yes
az group delete -n rg-MyFirstARM