Recently, I needed to create an Azure Resource Manager (ARM) template for a virtual network (Vnet) containing numerous configuration items. Because this Vnet is designed to peer to a virtual network gateway, one of the subnets must have the fixed name “GatewaySubnet.”
Microsoft’s ARM template documentation lacks a comprehensive set of basic coding style recommendations about when to use what capability. While the doc for what ARM coding can accomplish is (slowly) getting better, how to do what you might want is limited to a few samples on Github and Google searches. These, naturally, vary in quality.
One confusing template concept is the “child” resource and when and how it can and should be used in a template. The doc on ARM child resources only provides a glancing blow description of the child resources, largely focused on the naming requirements. It’s fine, as far as it goes — which isn’t far.
But it’s no help telling you when you might want to actually use a child resource. In my case, using a child resource solved a big problem: how to automate creating “regular” subnets and create a subnet with a fixed name. The template is invoked by a PowerShell script that allocates the IP address space. Using that allocation, the script calculates the regular subnets’ addresses and names and passes in an array of hashtables. In turn, the ARM template uses a copy:
statement to create the regular subnets in the assigned IP space. Here’s an example hashtable containing two subnets to be created. The script invokes the ARM template via New-AzResourceGroupDeployment
and passes the hashtable as a dynamic parameter:
@(@{Name = "Subnet1"; addressPrefix = "10.172.12.64/26" },@{Name = "Subnet2"; addressPrefix = "10.172.12.128/26" })
The ARM template also creates and assigns a network security group (NSG) to those subnets. But NSGs aren’t permitted in GatewaySubnet So, the question is, how do you create that “oddball” subnet — the one that is not like the others in the array of subnets?
This is an ideal use for a child resource. Simply embed a child resources:
object array inside the Vnet’s resources:
array and make sure to just use the type
name as the resource you wish to create. That is, do not pass the complete resource ID to the Resource Manager.
In this case "type": "subnet"
and "name": "GatewaySubnet"
are enough for the Resource Manager to figure out which Vnet to place the subnet in. Interestingly, if you do not specify dependsOn
with the resource ID of the Vnet you will get an error telling you to add one. IMHO, if the Resource Manager knows enough to be inform you of the exact missing dependsOn
in an error message and the subnet
resource is being created as a child resource, the dependsOn
shouldn’t be necessary. Oh, well… To complete the child subnet definition, a parameter named gatewaySubnetAddressPrefix
is used to pass the addressPrefix
of the GatewaySubnet to be created.
So, after all that, we now have a coding style guideline for ARM template child resources: use a child resource when you must implement a special case in a group of resources you are creating. In this template, I created subnets and assigned an NSG to them in a copy:
loop. However, since one cannot apply an NSG to the GatewaySubnet, a child resource makes it possible to handle the special case easily.
Here’s the template file. The template is (c) 2019 Air11 Technology LLC and is licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0
I hope you find this useful.
{ "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", "contentVersion": "1.0.0.0", "parameters": { "vnetName": { "type": "string", "metadata": { "description": "Name of the new Vnet" } }, "vnetAddressSpace": { "type": "string", "metadata": { "description": "Vnet CIDR address space" } }, "region": { "type": "string", "metadata": { "description": "Lowercase region into which the Vnet should be defined" } }, "gatewaySubnetAddressPrefix": { "type": "string", "metadata": { "description": "String containing the gateway subnet address" } }, "subnets": { "type": "array", "metadata": { "description": "Array containing subnet values for the new Vnet" } }, "NsgExpressRouteName": { "type": "string", "defaultValue": "[concat('NsgExpressRoute', parameters('vnetName'))]", "metadata": { "description": "Example: NsgExpressRouteEastUS" } }, "location": { "type": "string", "defaultValue": "[resourceGroup().location]", "metadata": { "description": "Location for all resources." } } }, "variables": { }, "resources": [ { "name": "[parameters('NsgExpressRouteName')]", "type": "Microsoft.Network/networkSecurityGroups", "location": "[parameters('region')]", "apiVersion": "2019-04-01", "properties": { "securityRules": [ { "name": "ExpressRoute", "properties": { "access": "Allow", "description": "Required to permit inbound traffic from Express Route NAT IPs", "destinationAddressPrefix": "*", "destinationPortRange": "*", "direction": "Inbound", "priority": 100, "protocol": "*", "sourceAddressPrefixes": [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], "sourcePortRange": "*" } } ] } }, { "name": "[parameters('vnetName')]", "type": "Microsoft.Network/virtualNetworks", "location": "[parameters('region')]", "apiVersion": "2019-04-01", "dependsOn": [ "[resourceId('Microsoft.Network/networkSecurityGroups/',parameters('NsgExpressRouteName'))]" ], "properties": { "addressSpace": { "addressPrefixes": [ "[parameters('vnetAddressSpace')]" ] }, "dhcpOptions": { "dnsServers": [ "172.16.1.1", "172.16.1.2", "172.16.1.3", "172.16.1.4" ] }, "copy": [ { "name": "subnets", "count": "[length(parameters('subnets'))]", "input": { "name": "[parameters('subnets')[copyIndex('subnets')].name]", "properties": { "addressPrefix": "[parameters('subnets')[copyIndex('subnets')].addressprefix]", "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups',parameters('NsgExpressRouteName'))]" }, "serviceEndpoints": [ { "service": "Microsoft.Sql" }, { "service": "Microsoft.KeyVault" }, { "service": "Microsoft.Storage" }, { "service": "Microsoft.AzureActiveDirectory" } ] } } } ] }, "resources": [ { "name": "GatewaySubnet", "type": "subnets", "location": "[parameters('region')]", "apiVersion": "2019-04-01", "dependsOn": [ "[resourceId('Microsoft.Network/virtualNetworks/',parameters('vnetName'))]" ], "properties": { "addressPrefix": "[parameters('gatewaySubnetAddressPrefix')]" } } ] } ], "outputs": { "NewVnetResourceId": { "type": "string", "value": "[resourceId('Microsoft.Network/virtualNetworks',parameters('vnetName'))]" } } }
Leave a Reply