Known configurations

Known configurations, on the other hand, are specific pre-determined configurations for deploying an environment using ARM templates. These pre-determined configurations are known as T-shirt sizing configuration. The T-shirt sizing acts as an analogy. Similar to the way a T-shirt is available in a pre-determined configuration such as small, medium, and large, ARM templates can be pre-configured to deploy a small, medium, or large environment depending on the need. This means users cannot determine any custom size for the environment, but they can choose from various options, and ARM templates execution during runtime will ensure that an appropriate configuration of the environment is provisioned.

So the first step in creating a modular ARM template is deciding on the known configurations for an environment.

As an example, here is the configuration of a data center deployment on Azure:

T-shirt size

ARM template configuration

Small

4 virtual machines with 7 GB of memory along with 4 CPU cores

Medium

8 virtual machines with 14 GB of memory along with 8 CPU cores

Large

16 virtual machines with 28 GB of memory along with 8 CPU cores

 

Now that we know the configurations, we can create modular ARM templates.

There are two ways to write modular ARM templates:

ARM templates can be divided into modular templates based on the following:

An ideal way to decide on the modular method to author an ARM template is as follows:

The preceding steps for creating a modular designed template can be easily be understood by means of an diagram, as shown next:

Now, let's implement a part of the functionality shown in the previous diagram. In this implementation, we will provision a virtual machine with a script extension using a modular approach. The custom script extension deploys Docker binaries and prepares a container environment on Windows Server 2016 virtual machine.

Now, we are going to create a solution using ARM templates using a modular approach. As mentioned before, the first step is to create individual resource templates.  These individual resource templates will be used to compose additional templates capable of creating an environment. These templates would be needed to create a virtual machine. All arm templates shown here are available in accompanied chapter code. The names and code of these templates are shown next:

First, let's look at the code for the Storage.json template. This template provisions a storage account that every virtual machine needs for storing its OS and data disk files:


{
"$schema": "https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"minLength": 1
},
"storageType": {
"type": "string",
"minLength": 1
},
...
"outputs": {
"resourceDetails": {
"type": "object",
"value": "[reference(parameters('storageAccountName'))]"
}
}
}

Next, let's look at the code for the public IP address template. A virtual machine that should be accessible over the internet needs a public IP address resource assigned to its network interface card. Although exposing a virtual machine to the internet is optional, this resource optionally might get used for creating a virtual machine. The code shown next is available in PublicIPAddress.json file:

{
"$schema": "https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"publicIPAddressName": {
"type": "string",
"minLength": 1
},
"publicIPAddressType": {
"type": "string",
"minLength": 1
...
}
}
],
"outputs": {
"resourceDetails": {
"type": "object",
"value": "[reference(parameters('publicIPAddressName'))]"
}
}
}

Next, let's look at the code for the virtual network. Virtual machines on Azure need a virtual network for communication. This template will be used to create a virtual network on Azure with pre-defined address range and subnets.  The code shown next is available in virtualNetwork.json file:

{
"$schema": "https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"virtualNetworkName": {
"type": "string",
"minLength": 1
...
},
"subnetPrefix": {
"type": "string",
"minLength": 1
},
"resourceLocation": {
"type": "string",
"minLength": 1
}
...
"subnets": [
{
"name": "[parameters('subnetName')]",
"properties": {
"addressPrefix": "[parameters('subnetPrefix')]"
}
}
]
}
}
],
"outputs": {
"resourceDetails": {
"type": "object",
"value": "[reference(parameters('virtualNetworkName'))]"
}
}
}

Next, let's look at the code for the Network Interface Card. A virtual network card is needed by virtual machine to connected to a virtual network and to accept and send requests from and to the internet. The code shown next is available in NIC.json file:

{
"$schema": "https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"nicName": {
"type": "string",
"minLength": 1
},
"publicIpReference": {
"type": "string",
"minLength": 1
...
[resourceId(subscription().subscriptionId,resourceGroup().name, 'Microsoft.Network/publicIPAddresses', parameters('publicIpReference'))]",
"vnetRef": "[resourceId(subscription().subscriptionId,resourceGroup().name, 'Microsoft.Network/virtualNetworks', parameters('virtualNetworkReference'))]",
"subnet1Ref": "[concat(variables('vnetRef'),'/subnets/', parameters('subnetReference'))]"
},
...
"id": "[variables('subnet1Ref')]"
}
}
}
]
}
}
],
"outputs": {
"resourceDetails": {
"type": "object",
"value": "[reference(parameters('nicName'))]"
}
}
}

Next, let's look at the code for creating a virtual machine. Each virtual machine is a resource in Azure, and note that this template has no reference to storage, network, public IP address, or other resources created earlier. This reference and composition will happen later in this section using another template. The code shown next is available in VirtualMachine.json file:

{
"$schema": "https://schema.management.azure.com/schemas/2015-01
01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"minLength": 1
...
},
"imageOffer": {
"type": "string",
"minLength": 1
},
"windowsOSVersion": {
"type": "string",
"minLength": 1
},
...
"outputs": {
"resourceDetails": {
"type": "object",
"value": "[reference(parameters('vmName'))]"
}
}
}

Next, let's look at the code for creating a custom script extension. This resource executes a PowerShell script on a virtual machine after it is provisioned. This resource provides an opportunity to execute post-provisioning tasks in Azure virtual machines. The code shown next is available in CustomScriptExtension.json file:

{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"VMName": {
"type": "string",
"defaultValue": "sqldock",
"metadata": {
...
"commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -file docker.ps1')]"
},
"protectedSettings": {
}
}
}
],
"outputs": {
}
}

Next, we'll look at the custom script extension PowerShell code that prepares the Docker environment. Please note that a virtual machine reboot might happen while executing the PowerShell script, depending on whether the containers Windows feature is already installed or not. The following script installs the NuGet package, the DockerMsftProvider provider, and Docker executable. docker.ps1 file is available with the accompanied chapter code.

#
# docker.ps1
#
Install-PackageProvider -Name Nuget -Force -ForceBootstrap -Confirm:$false
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force -Confirm:$false -verbose
Install-Package -Name docker -ProviderName DockerMsftProvider -Force -ForceBootstrap -Confirm:$false

All the linked templates shown before should be uploaded to a container within an Azure Blob Storage account. This container can have private access policy applied as we saw in the previous chapter however for this example, we will set the access policy as container. This means these linked templates can be accessed without the need of any SAS token. 

Finally, let's focus on writing the master template. Within Master template, all the linked templates are composed together to create a solution—to deploy a virtual machine and execute a script within it. The same approach can be used for creating other solutions like provisioning a data center consisting of multiple inter-connected virtual machines.  The code shown next is available in Master.json file.

{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"minLength": 1
...
},
"subnetName": {
"type": "string",
"minLength": 1
},
"subnetPrefix": {
"type": "string",
"minLength": 1
...
"windowsOSVersion": {
"type": "string",
"minLength": 1
},
"vhdStorageName": {
"type": "string",
"minLength": 1
},
"vhdStorageContainerName": {
"type": "string",
"minLength": 1
...[concat('https://',parameters('storageAccountName'),'armtfiles.blob.core.windows.net/',variables('containerName'),'/Storage.json')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"storageAccountName": {
"value": "[parameters('storageAccountName')]"
},
"storageType": {
"value": "[parameters('storageType')]"
},
"resourceLocation": {
"value": "[resourceGroup().location]"
...
"outputs": {
"resourceDetails": {
"type": "object",
"value": "[reference('GetVM').outputs.resourceDetails.value]"
}
}
}

The master templates invoke the external templates and also co-ordinates inter-dependencies among them.

The external templates should be available at a well-known location so that the master template can access and invoke them. In this example, the external templates are stored in Azure Blob Storage container and this information was passed to ARM template by means of parameters.

The external templates in Azure Blob Storage could be access protected by setting up access policies.  The command used to deploy the master template is shown next. It might look like a complex command but a majority of values are used as parameters.  Readers are advised to change the value of these parameters before running it. The linked templates have been uploaded to a storage account named st02gvwldcxm5suwe within armtemplates container. The resource group should be created if it does not exists. The first command is used to create a new Resource Group in West Europe region:

New-AzureRmResourceGroup -Name "testvmrg" -Location "West Europe" -Verbose

Rest of the parameter values are needed for configuring each resource. The storage account name and dnsNameForPublicIP  value should be unique within Azure:

New-AzureRmResourceGroupDeployment -Name "testdeploy1" -ResourceGroupName testvmrg -Mode Incremental -TemplateFile "C:\chapter 05\Master.json" -storageAccountName "st02gvwldcxm5suwe" -storageType "Standard_LRS" -publicIPAddressName "uniipaddname" -publicIPAddressType "Dynamic" -dnsNameForPublicIP "azureforarchitectsbook" -virtualNetworkName vnetwork01 -addressPrefix "10.0.1.0/16" -subnetName "subnet01" -subnetPrefix "10.0.1.0/24" -nicName nic02 -vmSize "Standard_DS1" -adminUsername "sysadmin" -adminPassword $(ConvertTo-SecureString -String sysadmin@123 -AsPlainText -Force) -vhdStorageName oddnewuniqueacc -vhdStorageContainerName vhds -OSDiskName mynewvm -vmName vm10 -windowsOSVersion 2012-R2-Datacenter -imagePublisher MicrosoftWindowsServer -imageOffer WindowsServer -containerName armtemplates -Verbose