Convert new VM’s dynamic IP address to static with Azure Resource Templates

Posted by Rik Hepworth on Sunday, November 1, 2015

Over the past few posts on this blog I’ve been documenting the templates I have been working on for Black Marble. In a previous sequence I showed how you can use nested deployments to keep your templates simple and still push out complex environments. The problem with those examples is that they are very fixed in what they do. The templates create a number of virtual machines on a virtual network, with static IP addresses for each machine.

This works well for that deployment, where I have complete control. However, one of my aims is to create a series of templates for virtual machines that my developers can combine themselves to create environments that may be very different in makeup to my original. For example, what if the dev needs more servers? What if they only realise after pushing out four web servers that they need a domain controller? If I can’t guarantee the number or sequence of my servers I can’t use static address on creation.

The answer to this problem is actually really simple and uses the same approach as I described previously when reconfiguring a virtual network to alter the DNS address. I deploy a new virtual machine where the virtual nic for that machine requests a dynamic IP address. I then use a nested deployment to reconfigure that same nic, setting the address type to static and specifying the IP address that it was just given as the intended address. This means that I no longer care what order the Azure fabric creates the virtual machines in. That one key change over my previous template approach has halved the deployment time as I can now create all machines in parallel (the bit that takes the most time) and then configure in sequence as needed.

The markup to do this is very straightforward. First we create our nic:

{
    "name": "[concat(parameters('envPrefix'),parameters('vmName'),'nic')]",
    "type": "Microsoft.Network/networkInterfaces",
    "location": "[parameters('VirtualNetwork').Location]",
    "apiVersion": "2015-06-15",
    "dependsOn": [],
    "tags": {
        "displayName": "DomainControllerNic"
    },
    "properties": {
        "ipConfigurations": [
            {
                "name": "ipconfig1",
                "properties": {
                    "privateIPAllocationMethod": "Dynamic",
                    "subnet": {
                        "id": "[concat(parameters('VirtualNetworkId'),'/subnets/',parameters('VirtualNetwork').Subnet1Name)]"
                    }
                }
            }
        ]
    }
}

You can see that I have set privateIPAllocationMethod to Dynamic.

The we call a nested deployment from our template, passing the IP address of the nic as a parameter. That template will redefine the settings of the nic, so it’s important we pass in all the information we need. If I miss something, that setting will be removed from the nic, so it’s important to be careful here. Notice that I use the reference keyword to access the privateIPAddress address property of the nic.

{
    "name": "SetStaticIP",
    "type": "Microsoft.Resources/deployments",
    "apiVersion": "2015-01-01",
    "dependsOn": [
        "[concat(parameters('envPrefix'),parameters('vmName'),'nic')]",
        "[concat(parameters('envPrefix'),parameters('vmName'))]",
        "Microsoft.Insights.VMDiagnosticsSettings"
    ],
    "properties": {
        "mode": "Incremental",
        "templateLink": {
            "uri": "[concat(parameters('_artifactsLocation'), '/SetStaticIP.json', parameters('_artifactsLocationSasToken'))]",
            "contentVersion": "1.0.0.0"
        },
        "parameters": {
            "VirtualNetwork": {
                "value": "[parameters('VirtualNetwork')]"
            },
            "VirtualNetworkId": {
                "value": "[parameters('VirtualNetworkId')]"
            },
            "nicName": {
                "value": "[concat(parameters('envPrefix'),parameters('vmName'),'nic')]"
            },
            "ipAddress": {
                "value": "[reference(concat(parameters('envPrefix'),parameters('vmName'),'nic')).ipConfigurations[0].properties.privateIPAddress]"
            }
        }
    }
}

Within the template called by my nested deployment object I use the incoming parameters to reconfigure the nic. I need to change the privateIPAllocationMethod setting to static and pass in the IP address from my parameters.

{
    "name": "[parameters('nicName')]",
    "type": "Microsoft.Network/networkInterfaces",
    "location": "[parameters('VirtualNetwork').Location]",
    "apiVersion": "2015-05-01-preview",
    "dependsOn": [],
    "tags": {
        "displayName": "DomainControllerNic"
    },
    "properties": {
        "ipConfigurations": [
            {
                "name": "ipconfig1",
                "properties": {
                    "privateIPAllocationMethod": "Static",
                    "privateIPAddress": "[parameters('ipAddress')]",
                    "subnet": {
                        "id": "[concat(parameters('VirtualNetworkId'),'/subnets/',parameters('VirtualNetwork').Subnet1Name)]"
                    }
                }
            }
        ]
    }
}

Finally, in my virtual machine template I pass the IP address back up the chain as an output so I can use it in other templates if needed (for example, to reconfigure the vNet DNS property with the IP address of my domain controller).

{
    "name": "[parameters('nicName')]",
    "type": "Microsoft.Network/networkInterfaces",
    "location": "[parameters('VirtualNetwork').Location]",
    "apiVersion": "2015-05-01-preview",
    "dependsOn": [],
    "tags": {
        "displayName": "DomainControllerNic"
    },
    "properties": {
        "ipConfigurations": [
            {
                "name": "ipconfig1",
                "properties": {
                    "privateIPAllocationMethod": "Static",
                    "privateIPAddress": "[parameters('ipAddress')]",
                    "subnet": {
                        "id": "[concat(parameters('VirtualNetworkId'),'/subnets/',parameters('VirtualNetwork').Subnet1Name)]"
                    }
                }
            }
        ]
    }
}