Reputation: 28
I have developed and ARM Template that will build multiple VMs in parallel. Each machine has an OS Disk, and a vNIC. I can use PowerShell to deploy the ARM Template or I can upload the ARM Template and deploy it using the "Deploy Custom Template" feature of the Azure Portal.
However I cannot quite seem to get the Syntax correct to add in the JsonADDomainExtension with a copy loop so that all machines get added to my domain in the same template.
At the moment all my resources are in the one Resource Group. I have pre-deployed a vNET with a Subnet, and a storage account.
Below is the ARM Template that works deploying multiple machines at once.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters":
{
"adminUsername":
{
"type": "string",
"metadata": {"description": "Administrator username for the Virtual Machine."}
},
"adminPassword":
{
"type": "securestring",
"metadata": {"description": "Password for the Virtual Machine.Should contain any 3 of: 1 Lower Case, 1 Upper Case, 1 Number and 1 Special character."}
},
"numberOfInstances":
{
"type": "int",
"defaultValue": 3,
"minValue": 2,
"maxValue": 5,
"metadata": {"description": "Number of VMs to deploy, limit 5 since this sample is using a single storage account."}
},
"StartOfCount":
{
"type": "int",
"defaultValue": 1,
"metadata": {"description": "Number that you want to start the machine name numbering from, EG to create 5 VMs with the name starting at 045 (W2016SRV-045-vm) you would enter 45 here, or to start at 1 just enter 1 here, this wil give you a machine name like 'W2016SRV-001-vm'."}
},
"vmNamePrefix":
{
"type": "string",
"defaultValue": "W2016SRV-",
"maxLength": 9,
"metadata": {"description": "The VM name prefix maximum 9 characters. This allows for three digits in the name and trailing '-vm'. EG: 'W2016SRV-045-vm'."}
},
"vmSize":
{
"type": "string",
"defaultValue": "Standard_B2ms",
"metadata":
{
"description": "The size(T-shirt) for the VM. (Standard_D8s_v3)",
"SNC::Parameter::Metadata": {"referenceType": "Microsoft.Compute/virtualMachines/vmSize"}
}
},
"vmLocation":
{
"type": "string",
"defaultValue": "AustraliaSoutheast",
"metadata":
{
"description": "Location or datacenter where the VM will be placed.",
"SNC::Parameter::Metadata": {"referenceType": "Microsoft.Azure/region"}
}
},
"domainToJoin":
{
"type": "string",
"metadata": {"description": "The FQDN of the AD domain"}
},
"domainUsername":
{
"type": "string",
"metadata": {"description": "Username of the account on the domain"}
},
"domainPassword":
{
"type": "securestring",
"metadata": {"description": "Password of the account on the domain"}
},
"ouPath":
{
"type": "string",
"metadata": {"description": "Specifies an organizational unit (OU) for the domain account. Enter the full distinguished name of the OU in quotation marks. Example: 'OU=testOU; DC=domain; DC=Domain; DC=com"}
},
"sizeOfDiskInGB":
{
"type": "int",
"defaultValue": 200,
"metadata": {"description": "The disk size for the OS drive in the VM, in GBs. Default Value is 500"}
},
"existingBootDiagStorageResourceGroup":
{
"type": "string",
"metadata": {"description": "Storage account resource group name where boot diagnistics will be stored"}
},
"existingBootDiagStorageName":
{
"type": "string",
"metadata":
{
"description": "Storage account name where boot diagnistics will be stored. It should be at the same location as the VM.",
"SNC::Parameter::Metadata": {"referenceType": "Microsoft.Storage/storageAccounts"}
}
},
"existingvNetResourceGroup":
{
"type": "string",
"metadata":
{
"description": "Resource Group of the Existing Virtual Network.",
"SNC::Parameter::Metadata": {"referenceType": "Microsoft.Resources/resourceGroups"}
}
},
"existingvNetName":
{
"type": "string",
"metadata":
{
"description": "Existing Virtual Network to connect to Network Interface to.It should be at the same location as the VM.",
"SNC::Parameter::Metadata": {"referenceType": "Microsoft.Network/virtualNetworks"}
}
},
"subnetName":
{
"type": "string",
"metadata":
{
"description": "The Subnet for the VM.",
"SNC::Parameter::Metadata": {"referenceType": "Microsoft.Network/subNets"}
}
}
},
"variables":
{
"storageAccountType": "Standard_LRS",
"vnetID": "[resourceId(parameters('existingvNetResourceGroup'), 'Microsoft.Network/virtualNetworks', parameters('existingvNetName'))]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/', parameters('subnetName'))]",
"StartOfCountString": "[String(Parameters('StartOfCount'))]"
},
"resources":
[
{
"apiVersion": "[providers('Microsoft.Network','networkInterfaces').apiVersions[0]]",
"type": "Microsoft.Network/networkInterfaces",
"name": "[concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm-vNic')]",
"location": "[parameters('vmLocation')]",
"copy": {
"name": "nicLoop",
"count": "[parameters('numberOfInstances')]"
},
"properties":
{
"ipConfigurations":
[
{
"name": "Prod",
"properties":
{
"privateIPAllocationMethod": "Dynamic",
"subnet": {"id": "[variables('subnetRef')]"}
}
}
]
}
},
{
"apiVersion": "[providers('Microsoft.Compute','virtualMachines').apiVersions[0]]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm')]",
"location": "[parameters('vmLocation')]",
"copy": {
"name": "vmLoop",
"count": "[parameters('numberOfInstances')]"
},
"dependsOn": ["nicLoop"],
"tags":
{
"Project": "***"
},
"properties":
{
"hardwareProfile": {"vmSize": "[parameters('vmSize')]"},
"osProfile":
{
"computerName": "[concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"windowsConfiguration": {"enableAutomaticUpdates": false}
},
"storageProfile":
{
"imageReference": {
"id": "/subscriptions/***/resourceGroups/***/providers/Microsoft.Compute/images/***"
},
"osDisk":
{
"name": "[concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm-osDisk')]",
"createOption": "FromImage",
"diskSizeGB": "[parameters('sizeOfDiskInGB')]",
"caching": "ReadWrite",
"managedDisk": {"storageAccountType": "[variables('storageAccountType')]"}
}
},
"networkProfile": {"networkInterfaces": [{"id": "[resourceId('Microsoft.Network/networkInterfaces',concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm-vNic'))]"}]},
"diagnosticsProfile":
{
"bootDiagnostics":
{
"enabled": true,
"storageUri": "[reference(resourceId(parameters('existingBootDiagStorageResourceGroup'), 'Microsoft.Storage/storageAccounts', parameters('existingBootDiagStorageName')), '2015-06-15').primaryEndpoints['blob']]"
}
}
}
}
]
}
However, If I add in this to the resources bit at the bottom then it doesn't work.
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmNamePrefix'), padLeft(copyIndex(variables('StartOfCountString')),3, '0'), '/JoinDomain')]",
"location": "[resourceGroup().location]",
"copy": {
"name": "DomainJoinLoop",
"count": "[parameters('numberOfInstances')]"
},
"dependsOn": "[concat('Microsoft.Compute/virtualMachines/', concat(parameters('vmNamePrefix'), padLeft(copyIndex(variables('StartOfCountString')),3, '0')))]",
"properties": {
"publisher": "Microsoft.Compute",
"type": "JsonADDomainExtension",
"typeHandlerVersion": "1.3",
"autoUpgradeMinorVersion": true,
"settings": {
"Name": "[parameters('domainToJoin')]",
"User": "[concat(parameters('domainToJoin'), '\\', parameters('domainUsername'))]",
"OUPath": "[parameters('ouPath')]",
"Restart": "true",
"Options": "3"
},
"protectedsettings": {
"Password": "[parameters('domainPassword')]"
}
}
}
In the copyIndex portions of the code I've tried both variables and parameters to set the offset for the copyIndex. I've also tried hard coding it. I keep getting an error like this.
{"telemetryId":"649e0a7f-2c22-41f9-bede-c13618f5053a", "bladeInstanceId":"Blade_b45c7efadff74340820345aa2e9d76c1_26_0", "galleryItemId":"Microsoft.Template","createBlade": "DeployToAzure", "code":"InvalidRequestContent", "message":"The request content was invalid and could not be deserialized: 'Error converting value \"[concat('Microsoft.Compute/virtualMachines/', concat(parameters('vmNamePrefix'), padLeft(copyIndex(variables('StartOfCountString')),3, '0')))]\" to type 'System.String[]'. Path 'properties.template.resources[1].resources[0].dependsOn', line 259, position 171.'."}
The reason that I want to set an offset for the copy index is so that I can build say ten machines like W2016SRV-001, 002, ... 010, and then later on build more machines starting at 011, 012, 013, etc.
There are other ways around it but I really want to be able to do this in an ARM Template and I'm not yet aware after all my extensive googling why it shouldn't be possible.
...............................................................................
OK so after editing the template as suggested by @4c74356b41 I'm now getting a completely new and different error when I try and deploy the template through the portal.
{"telemetryId":"649e0a7f-2c22-41f9-bede-c13618f5053a",
"bladeInstanceId":"Blade_b45c7efadff74340820345aa2e9d76c1_26_0",
"galleryItemId":"Microsoft.Template","createBlade":"DeployToAzure",
"code":"InvalidTemplate",
"message":"Deployment template validation failed: 'The template resource
'[concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'),
'-vm/JoinDomain')]'
at line '250' column '13' is not valid. Copying nested resources is not supported.
Please see https://aka.ms/arm-copy/#looping-on-a-nested-resource for usage details.'."}
So following the link in the error message and reading some more ... what I needed to do was drop the Domain Join Extension out as a child of the VM and have it as a top level resource. Woot woot it worked.
So now the end of my ARM Template looks like this.
*** VM bits are here ***
}
}
}, <-- end of the VM bits.
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm/JoinDomain')]",
"location": "[resourceGroup().location]",
"copy": {
"name": "DomainJoinLoop",
"count": "[parameters('numberOfInstances')]"
},
"dependsOn": [ "vmLoop" ],
"properties":
{
"publisher": "Microsoft.Compute",
"type": "JsonADDomainExtension",
"typeHandlerVersion": "1.3",
"autoUpgradeMinorVersion": true,
"settings":
{
"Name": "[parameters('domainToJoin')]",
"User": "[concat(parameters('domainToJoin'), '\\', parameters('domainUsername'))]",
"OUPath": "[parameters('ouPath')]",
"Restart": "true",
"Options": "3"
},
"protectedsettings": {"Password": "[parameters('domainPassword')]"}
}
}
],
"outputs":
{}
}
Multiple VMs deployed in parallel and all joined to the domain without issue.
Sooo my lessons learned are these and I hope that they help someone else in the future.
Upvotes: 0
Views: 1153
Reputation: 72171
Compare your vm name and extension name:
concat(parameters('vmNamePrefix'), padLeft(copyIndex(parameters('StartOfCount')),3, '0'), '-vm')
concat(parameters('vmNamePrefix'), padLeft(copyIndex(variables('StartOfCountString')),3, '0'), '/JoinDomain')
besides the fact you are using an unneeded variable you are missing -vm
. The extension name has to be in the format parent_name/extension_name
else it wouldn't know to which parent resource this extension belongs (it must always belong to some vm).
Your dependsOn
is also wrong, you can simplify it to this:
"dependsOn": [ "vmLoop" ]
Upvotes: 0