Recently I need to move few production Azure VMs between two virtual networks. Right now is not to simple because we need to remove current VM and redeploy into new vNET with old configuration. When our VM is deployed in Azure Resource Manager (ARM) model, we must edit JSON template.
Below you will find my way to edit the JSON template.
Test environment
To map the approximate production environment I prepared a test environment.
It consists of four Resource Groups which contain the following resources:
- 1 VM with configuration (network card, public IP address. NSG rule)
- 2 virtual networks
- 1 storage account
Resource groups
I created four Resource Groups divided into individual functions:
NewNetwork and OldNetwork contains vNETs with names respectively NewNetwork and OldNetwork.
Storage – contains storage account named ewteststorage where VM VHD disks are stored.
In VirtualMachines is a virtual machine running Windows Server 2012 R2 named testvm.
Virtual Machine configuration
Virtual Machine has been deployed from standard Windows Server 2012 R2 template in DS2 v2 size.
Disks
To the VM I joined 4 additional Data Disks named testvm-disk(01,02,03,04). And then I configured one virtual disk with StorageSpaces.
Network
Virtual Machine contains one network card attached to the OldNetwork.
Target configuration
Transfer a virtual machine (network card) to the network NewNetwork maintaining the current configuration.
Export current configuration
The first step that we need to do is to export the configuration of the virtual machine to a JSON. Form Azure Portal (portal.azure.com) you can do this in few steps shown below:
- Choose correct Resource Group
- Then click on Virtual Machine icon
3. In the Settings blade scroll down and click Automation script.
4. A the end click on Download and download zip archive to your favorite location.
Template preparation
Before we can re-use the template we have to adequately prepare JSON file.
Template construction
JSON template describing the configuration of Azure Resource Manager consists of six components: $schema, contentVersion arameters, Variables, Resources Outputs.
- $schema – required. Location of the JSON schema file that describes the version of the template language. You should use the URL shown above.
- conventVersion – required. Version of the template (such as 1.0.0.0). You can provide any value for this element.
- Parameters – not required. Values that are provided when deployment is executed to customize resource deployment.
- Variables – not required. Values that are used as JSON fragments in the template to simplify template language expressions.
- Resources – required. Resource types that are deployed or updated in a resource group.
- Outputs – not required. Values that are returned after deployment.
ZIP archive and programs that you can use to edit templates
After downloading and unpack the zip file in the directory you will find six files. You need only file named template.json.
JSON templates you can edit easily with Visual Studio. For example you can use free Community version.
Another interesting program is a Visual Studio Code with armsnippet extension.
Edit templat
Now when we know our environment and know what we want we can start edit our ARM template.
Main steps are:
- Remove unnecessary parameters and appeals to them.
- Conversion Parameters to the Variables.
- Changing the appeals of the Parameters to the Variables.
- Changing the target network.
- Changing the configuration of the virtual machine.
- Changing the network interface configuration.
Remove unnecessary parameters and appeals to them
My ARM template looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "virtualMachines_testvm_adminPassword": { "defaultValue": null, "type": "SecureString" }, "virtualMachines_testvm_primary": { "defaultValue": null, "type": "SecureString" }, "virtualMachines_testvm_name": { "defaultValue": "testvm", "type": "String" }, "networkInterfaces_testvm33_name": { "defaultValue": "testvm33", "type": "String" }, "networkSecurityGroups_testvm_name": { "defaultValue": "testvm", "type": "String" }, "publicIPAddresses_testvm_name": { "defaultValue": "testvm", "type": "String" }, "networkInterfaces_testvm33_id": { "defaultValue": "/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/OldNetwork/providers/Microsoft.Network/virtualNetworks/OldNetwork/subnets/default", "type": "String" } }, "variables": {}, "resources": [ { "comments": "Generalized from resource: '/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/VirtualMachines/providers/Microsoft.Compute/virtualMachines/testvm'.", "type": "Microsoft.Compute/virtualMachines", "name": "[parameters('virtualMachines_testvm_name')]", "apiVersion": "2015-06-15", "location": "westeurope", "tags": {}, "properties": { "hardwareProfile": { "vmSize": "Standard_DS2_v2" }, "storageProfile": { "imageReference": { "publisher": "MicrosoftWindowsServer", "offer": "WindowsServer", "sku": "2012-R2-Datacenter", "version": "latest" }, "osDisk": { "name": "[parameters('virtualMachines_testvm_name')]", "createOption": "FromImage", "vhd": { "uri": "[concat('https://ewteststorage.blob.core.windows.net/vhds/', parameters('virtualMachines_testvm_name'),'2016624204843.vhd')]" }, "caching": "ReadWrite" }, "dataDisks": [ { "lun": 0, "name": "[concat(parameters('virtualMachines_testvm_name'),'-disk01')]", "createOption": "Empty", "vhd": { "uri": "[concat('https://ewteststorage.blob.core.windows.net/vhds/', parameters('virtualMachines_testvm_name'),'-disk01.vhd')]" }, "caching": "ReadOnly", "diskSizeGB": 100 }, { "lun": 1, "name": "[concat(parameters('virtualMachines_testvm_name'),'-disk02')]", "createOption": "Empty", "vhd": { "uri": "[concat('https://ewteststorage.blob.core.windows.net/vhds/', parameters('virtualMachines_testvm_name'),'-disk02.vhd')]" }, "caching": "ReadOnly", "diskSizeGB": 100 }, { "lun": 2, "name": "[concat(parameters('virtualMachines_testvm_name'),'-disk03')]", "createOption": "Empty", "vhd": { "uri": "[concat('https://ewteststorage.blob.core.windows.net/vhds/', parameters('virtualMachines_testvm_name'),'-disk03.vhd')]" }, "caching": "ReadOnly", "diskSizeGB": 100 }, { "lun": 3, "name": "[concat(parameters('virtualMachines_testvm_name'),'-disk04')]", "createOption": "Attach", "vhd": { "uri": "[concat('https://ewteststorage.blob.core.windows.net/vhds/', parameters('virtualMachines_testvm_name'),'-disk04.vhd')]" }, "caching": "ReadOnly" } ] }, "osProfile": { "computerName": "[parameters('virtualMachines_testvm_name')]", "adminUsername": "eklime", "windowsConfiguration": { "provisionVMAgent": true, "enableAutomaticUpdates": true }, "secrets": [], "adminPassword": "[parameters('virtualMachines_testvm_adminPassword')]" }, "networkProfile": { "networkInterfaces": [ { "properties": { "primary": "[parameters('virtualMachines_testvm_primary')]" }, "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_testvm33_name'))]" } ] } }, "dependsOn": [ "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_testvm33_name'))]" ] }, { "comments": "Generalized from resource: '/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/VirtualMachines/providers/Microsoft.Network/networkInterfaces/testvm33'.", "type": "Microsoft.Network/networkInterfaces", "name": "[parameters('networkInterfaces_testvm33_name')]", "apiVersion": "2016-03-30", "location": "westeurope", "properties": { "ipConfigurations": [ { "name": "ipconfig1", "properties": { "privateIPAddress": "10.10.0.4", "privateIPAllocationMethod": "Dynamic", "publicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_testvm_name'))]" }, "subnet": { "id": "[parameters('networkInterfaces_testvm33_id')]" } } } ], "dnsSettings": { "dnsServers": [] }, "enableIPForwarding": false, "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_testvm_name'))]" } }, "dependsOn": [ "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_testvm_name'))]", "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_testvm_name'))]" ] }, { "comments": "Generalized from resource: '/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/VirtualMachines/providers/Microsoft.Network/networkSecurityGroups/testvm'.", "type": "Microsoft.Network/networkSecurityGroups", "name": "[parameters('networkSecurityGroups_testvm_name')]", "apiVersion": "2016-03-30", "location": "westeurope", "properties": { "securityRules": [ { "name": "default-allow-rdp", "properties": { "protocol": "*", "sourcePortRange": "*", "destinationPortRange": "3389", "sourceAddressPrefix": "*", "destinationAddressPrefix": "*", "access": "Allow", "priority": 1000, "direction": "Inbound" } } ] }, "dependsOn": [] }, { "comments": "Generalized from resource: '/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/VirtualMachines/providers/Microsoft.Network/publicIPAddresses/testvm'.", "type": "Microsoft.Network/publicIPAddresses", "name": "[parameters('publicIPAddresses_testvm_name')]", "apiVersion": "2016-03-30", "location": "westeurope", "properties": { "publicIPAllocationMethod": "Dynamic", "idleTimeoutInMinutes": 4 }, "dependsOn": [] } ] } |
We will deploy our virtual machine from existing VHD disk, so we don’t need to define all parameters, e.g. admin password (of course if we want to use existing one). So we can safely remove virtualMachines_testvm_adminPassword parameter.
We also can remove virtualMachines_testvm_primary – it’s not required when we defining network interface parameters.
When we remove these parameters above, we will be following:
„virtualMachines_testvm_name”
„networkInterfaces_testvm33_name”
„networkSecurityGroups_testvm_name”
„publicIPAddresses_testvm_name”
„networkInterfaces_testvm33_id”
Conversion Parameters to the Variables
After the initial cleaning, we can proceed to the next step. Now we need to convert Parameters to Variables. Cut of all parameters definitions and paste them to variables. Then correct phrase as shown below:
Original:
1 2 3 4 5 6 |
"variables": { "virtualMachines_testvm_name": { "defaultValue": "testvm", "type": "String" }, } |
After:
1 2 3 |
"variables": { "virtualMachines_testvm_name": "testvm", } |
Correct rest of variables.
Changing the appeals of the Parameters to the Variables
Originally, our resources have a references to parameters e.g. in network card:
1 2 |
"type": "Microsoft.Network/networkInterfaces", "name": "[parameters('networkInterfaces_testvm33_name')]", |
That’s mean that the network card name will be generated based on networkInterfaces_testvm33_name parameter. We need to change parameters word to variables. You can use „replace all” (Ctrl+h).
„replace all” replace also first Parameters word which are a definition of parameters block. We need correct that.
Changing the target network
Now is the time when we finally can change our target network to the new one. If you look at my JSON template you can find networkInterfaces_testvm33_id variable (after correction) which definiens vNET configuration. We need to replace OldNetwork word to NewNetwork. In my test environment these two networks belongs to different Resource Groups, so we need also change target RG.
Original:
1 |
"networkInterfaces_testvm33_id": "/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/OldNetwork/providers/Microsoft.Network/virtualNetworks/OldNetwork/subnets/default" |
After:
1 |
"networkInterfaces_testvm33_id": "/subscriptions/Azure-SUBSCRIPTION-ID/resourceGroups/NewNetwork/providers/Microsoft.Network/virtualNetworks/NewNetwork/subnets/default" |
And it’s almost over. Now we need correct some VM configuration.
Changing the configuration of the virtual machine
At the firs step we need to correct disks parameters.
We don’t need imageReference (1) because we will deploy VM from existing disk. Remove all imageReference block. Next, replace in points 2 and 3 creationOption to Attach. In point 4 remove information about size of disk. Our data disk already exists with correct size.
Additionally we need to declare which OS is installed on OS disk. We can do this with „osType”: „Windows”, in osDisk block:
The last step is to remove the osProfile block and existing references to non existing variables (which we removed earlier):
Before:
After:
Changing the network interface configuration
Last step in template preparing stage is a correction of network interface configuration. When we export our ARM template, virtual machine private IP also was exported. In new virtual network we will have different address space. So we need to remove or change this configuration. I chose second option.
Before:
After:
Uff… that is the end of template preparation. Now is time to deploy new VM.
Removing existing VM
Before we can deploy VM into new network, we need to remove existing virtual machine. Look at the steps below:
New VM creation
Finally! Now we can deploy our ARM template and create VM into the new vNET.
Let’s do that:
- Click New.
- In the search box write Template Deployment and press ENTER.
- Select the appropriate position.
- And then click Create.
In an open blade click Edit template (1) and in the editable area paste our prepared ARM template (2). Click Save (3).
Next select existing (1) and appropriate (2) Resource Group. Accept Legal terms (3,4) and finally click Create.
After few minutes our new Virtual Machine will be ready to use in a new vNET.
My new VM:
As you can see StorageSpaces configuration has been preserved. Only the private and public IP address has been changed.
And that’s it. If you have any question feel free to comment below.