Deploy a VM add to an existing AD Domain and Network in IaaS with Azure ARM
Deploy a VM add to an existing AD Domain and Network in IaaS with Azure ARM
I’ll preface with a few of the resources I’ve used so far in my Azure study journey
|
|
|
The story so far.
Tools installed and scripts configured! In the post we ran through the step by step creation of an IaaS AD controller, deployed to Azure using ARM and DSC. One of the elements that I didn’t focus in on was the heavy that was done during the IaaS AD controller deployment to create and configure the Virtual Network.
If you care to revisit that code for a moment and delve into the resources section of ‘WindowsVirtualmachine.json‘. You will very quickly see that in the same way the ‘VirtualMachine‘, ‘PublicIPAddress‘ and ‘NetworkInterface‘ resources have been created a resource exists for ‘VirtualNetwork‘.
The resource references the variables to configure the virtual networks and subnets, presented using CIDR notation.
By default this network is going to be created to use Azure domain name services. The problem with this is that Azure DNS, has no idea about your random IaaS domain name – nor does it want to know about it. Therefore before proceeding it is worth re-configuring the created network to point toward the DNS services on the freshly deployed domain controller.
Deploy the next IaaS VM
Start with a Blank Azure Resource Group Project. This is going to provide the three files that are needed for a successful deployment, all be it blank. ‘AzureDeploy.json’, ‘AzureDeployParameters.json’ and ‘Deploy-AzureResourceGroup.ps1’ – our template files and the script to deploy against them.
Cut and paste the following into ‘AzureDeploy.json’;
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "existingVNETName": { "type": "string", "metadata": { "description": "Existing VNET that contains the domain controller" } }, "existingSubnetName": { "type": "string", "metadata": { "description": "Existing subnet that contains the domain controller" } }, "dnsLabelPrefix": { "type": "string", "metadata": { "description": "Unique public DNS prefix for the deployment. The fqdn will look something like '<dnsname>.westus.cloudapp.azure.com'. Up to 62 chars, digits or dashes, lowercase, should start with a letter: must conform to '^[a-z][a-z0-9-]{1,61}[a-z0-9]$'." } }, "vmSize": { "type": "string", "defaultValue": "Standard_A2", "metadata": { "description": "The size of the virtual machines" } }, "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", "defaultValue": "", "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" } }, "domainJoinOptions": { "type": "int", "defaultValue": 3, "metadata": { "description": "Set of bit flags that define the join options. Default value of 3 is a combination of NETSETUP_JOIN_DOMAIN (0x00000001) & NETSETUP_ACCT_CREATE (0x00000002) i.e. will join the domain and create the account on the domain. For more information see https://msdn.microsoft.com/en-us/library/aa392154(v=vs.85).aspx" } }, "vmAdminUsername": { "type": "string", "metadata": { "description": "The name of the administrator of the new VM and the domain. Exclusion list: 'admin','administrator" } }, "vmAdminPassword": { "type": "securestring", "metadata": { "description": "The password for the administrator account of the new VM and the domain" } } }, "variables": { "storageAccountName": "[concat(uniquestring(resourceGroup().id, deployment().name))]", "imagePublisher": "MicrosoftWindowsServer", "imageOffer": "WindowsServer", "windowsOSVersion": "2016-Datacenter", "apiVersion": "2015-06-15", "nicName": "[concat(parameters('dnsLabelPrefix'),'Nic')]", "publicIPName": "[concat(parameters('dnsLabelPrefix'),'Pip')]", "vnetID": "[resourceId(resourceGroup().name, 'Microsoft.Network/virtualNetworks', parameters('existingVNETName'))]", "subnetId": "[concat(variables('vnetID'),'/subnets/', parameters('existingSubnetName'))]" }, "resources": [ { "apiVersion": "[variables('apiVersion')]", "type": "Microsoft.Network/publicIPAddresses", "name": "[variables('publicIPName')]", "location": "[resourceGroup().location]", "properties": { "publicIPAllocationMethod": "Dynamic", "dnsSettings": { "domainNameLabel": "[parameters('dnsLabelPrefix')]" } } }, { "apiVersion": "[variables('apiVersion')]", "type": "Microsoft.Storage/storageAccounts", "name": "[variables('storageAccountName')]", "location": "[resourceGroup().location]", "properties": { "accountType": "Standard_LRS" } }, { "apiVersion": "[variables('apiVersion')]", "type": "Microsoft.Network/networkInterfaces", "name": "[variables('nicName')]", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPName'))]" ], "properties": { "ipConfigurations": [ { "name": "ipconfig", "properties": { "privateIPAllocationMethod": "Dynamic", "publicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPName'))]" }, "subnet": { "id": "[variables('subnetId')]" } } } ] } }, { "apiVersion": "[variables('apiVersion')]", "type": "Microsoft.Compute/virtualMachines", "name": "[parameters('dnsLabelPrefix')]", "location": "[resourceGroup().location]", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts',variables('storageAccountName'))]", "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" ], "properties": { "hardwareProfile": { "vmSize": "[parameters('vmSize')]" }, "osProfile": { "computerName": "[parameters('dnsLabelPrefix')]", "adminUsername": "[parameters('vmAdminUsername')]", "adminPassword": "[parameters('vmAdminPassword')]" }, "storageProfile": { "imageReference": { "publisher": "[variables('imagePublisher')]", "offer": "[variables('imageOffer')]", "sku": "[variables('windowsOSVersion')]", "version": "latest" }, "osDisk": { "name": "osdisk", "vhd": { "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2015-06-15').primaryEndpoints.blob,'vhds/',parameters('dnsLabelPrefix'), 'disk0.vhd')]" }, "caching": "ReadWrite", "createOption": "FromImage" }, "dataDisks": [ { "name": "myvm-data-disk1", "vhd": { "Uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2015-06-15').primaryEndpoints.blob,'vhds/',parameters('dnsLabelPrefix'), 'disk1.vhd')]" }, "caching": "None", "createOption": "Empty", "diskSizeGB": "1000", "lun": 0 } ] }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" } ] }, "diagnosticsProfile": { "bootDiagnostics": { "enabled": "true", "storageUri": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2015-06-15').primaryEndpoints.blob]" } } } }, { "apiVersion": "[variables('apiVersion')]", "type": "Microsoft.Compute/virtualMachines/extensions", "name": "[concat(parameters('dnsLabelPrefix'),'/joindomain')]", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', parameters('dnsLabelPrefix'))]" ], "properties": { "publisher": "Microsoft.Compute", "type": "JsonADDomainExtension", "typeHandlerVersion": "1.3", "autoUpgradeMinorVersion": true, "settings": { "Name": "[parameters('domainToJoin')]", "OUPath": "[parameters('ouPath')]", "User": "[concat(parameters('domainToJoin'), '\\', parameters('domainUsername'))]", "Restart": "true", "Options": "[parameters('domainJoinOptions')]" }, "protectedSettings": { "Password": "[parameters('domainPassword')]" } } } ] }
Ok. Lets explain a little whats going on in the JSON.
Paramaters;
Parameters are by definition “a numerical or other measurable factor forming one of a set that defines a system or sets the conditions of its operation“. In this context we can think of parameters here being the non changing configuration definitions that define the environment being deployed into.
So the parameters that are captured above work for the environment I’m deploying into; be that Development, Testing or Production. Or in this case Lab.
You’ll notice this parameter list is extended from the default list we worked with previously. This should all be fairly initiative, if not there is a description included with each parameter.
Variables
Variables are where you construct values that can be used through a template. So if we continue the train of thinking from above, this is where we are going to store elements that might change from deployment to deployment within the same environment. There are no definitions within the template here, but again these should be intuitive enough.
Resources
In simple terms (probably over simplifying it) resources are where the parameters and variables are applied.
Most of these resources will be familiar. Running from top to bottom we have ‘Microsoft.Network/publicIPAddresses‘, ‘Microsoft.Storage/storageAccounts‘, ‘Microsoft.Network/networkInterfaces‘ and ‘Microsoft.Compute/virtualMachines‘. Each of these resources was discussed in greater or lesser detail in a previous post.
The section added here as part of ‘Microsoft.Compute/virtualMachines/extensions‘ is ‘JsonADDomainExtension‘. This extension can take all the required parameters above to join the VM to an AD domain and then reboot it post join operation. which as luck would have it is exactly what we are trying to achieve here.
{ "apiVersion": "[variables('apiVersion')]", "type": "Microsoft.Compute/virtualMachines/extensions", "name": "[concat(parameters('dnsLabelPrefix'),'/joindomain')]", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', parameters('dnsLabelPrefix'))]" ], "properties": { "publisher": "Microsoft.Compute", "type": "JsonADDomainExtension", "typeHandlerVersion": "1.3", "autoUpgradeMinorVersion": true, "settings": { "Name": "[parameters('domainToJoin')]", "OUPath": "[parameters('ouPath')]", "User": "[concat(parameters('domainToJoin'), '\\', parameters('domainUsername'))]", "Restart": "true", "Options": "[parameters('domainJoinOptions')]" }, "protectedSettings": { "Password": "[parameters('domainPassword')]" }
AzureDeploy.Parameters.json
With the ‘AzureDeploy.json‘ template complete the parameters file needs to be addressed so that the parameters in the deploy file have values.
{ "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "existingVNETName": { "value": "****" }, "existingSubnetName": { "value": "****" }, "vmSize": { "value": "Standard_A2" }, "domainToJoin": { "value": "****" }, "domainUsername": { "value": "****" }, "domainPassword": { "value": "N****" }, "domainJoinOptions": { "value": 3 }, "vmAdminUsername": { "value": "****" }, "vmAdminPassword": { "value": "****" }, "dnsLabelPrefix": { "value": "****" } } }
The syntax and pattern here should be familiar now. Take the parameter name created in the ‘AzureDeploy.json‘ and feed in the required value. All but one of the values within this file are free text the only exception being ‘domainJoinOptions‘ which is configured here with a value of 3. You can read more about these options on the MSDN website for the NetJoinDomain function.
To keep the information on this page Option 3 refers to;
‘lpAccountOU [in]
Optionally specifies the pointer to a constant null-terminated character string that contains the RFC 1779 format name of the organisational unit (OU) for the computer account. If you specify this parameter, the string must contain a full path, for example, OU=testOU,DC=domain,DC=Domain,DC=com. Otherwise, this parameter must be NULL.’
DSC
For the purposes of this demonstration there was no further customisation of the deployed VMs outside of the domain join handled by the extension. Therefore no requirement for DSC.
Validation and Completion
As before the project can be validated and deployed from within Visual Studio.
Next up how to deploy multiple machines and join them to a domain.
Simon