Automatic Static IP Addresses for Azure VMs

Azure Infrastructure

EDIT: Updated the hyperlink to GitHub, this is now published here: https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-automatic-static-ip

During a recent project, I had a need to use only static IP addresses for my virtual machines. However, having to look up the next available IP address seemed counter-intuitive.

What’s the problem anyway

For systems that require a static IP addresses (like Active Directory), or systems that rely on external (non-self-updating) DNS, the default behavior in Azure is problematic because by default a dynamic private IP address is assigned. You can absolutely assign a static IP address by specifying it in your Azure Resource Manager (ARM) template or PowerShell script:

{
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Static",
              "privateIPAddress": "192.168.0.4"

However, there are a couple of issues with this:

1. I need to know the IP address ahead of time. Unlike the ASM model where we had Test-AzureStaticVNetIP cmdlet, we don’t have an ARM equivalent.

2. Azure’s DHCP system isn’t always aware that this IP address is taken.

What’s the solution

I’m going to detail one approach, the one I have used to solve this and would be happy to hear about other approaches to this. What we can do is let the Azure Virtual Network’s DHCP system allocate the IP address and then switch it over to a static IP. This is simple to do from the Azure portal as shown in several articles such as this one. However, we need a more automated approach. By using linked templates, we can create a Network Interface Card (NIC) with a dynamic IP address and then update that NIC with its own IP address, setting it to static.

The ARM template to create the NIC will look as follows:

 

    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables(‘nicName’)]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[concat(‘Microsoft.Network/virtualNetworks/’, variables(‘virtualNetworkName’))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "subnet": {
                "id": "[variables(‘SubnetRef’)]"
              }
            }
          }
        ]
      }
    },
{
      "type": "Microsoft.Resources/deployments",
      "name": "[concat(‘updateip’)]",
      "apiVersion": "2015-01-01",
      "dependsOn": [
        "[concat(‘Microsoft.Network/networkInterfaces/’, variables(‘nicName’))]"
      ],
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "[variables(‘updateip_templateUri’)]",
          "contentVersion": "1.0.0.0"
        },
        "parameters": {
          "nicName": {
            "value": "[variables(‘nicName’)]"
          },
          "SubnetRef": {
            "value": "[variables(‘SubnetRef’)]"
          },
          "privateIp": {
            "value": "[reference(concat(‘Microsoft.Network/networkInterfaces/’, variables(‘nicName’))).ipConfigurations[0].properties.privateIPAddress]"
          }
        }
      }
    }
  ],
  "outputs": {
    "privateIp": {
        "type": "string",
        "value": "[reference(variables(‘nicName’)).ipConfigurations[0].properties.privateIPAddress]"
    }
  }

 

I’m showing two resources. First the NIC and it’s being created as your normally would with a dynamic IP address. The other resource is a deployment (linked template) that is dependent on creation of the NIC. In this resource we are passing a private IP address as the parameter. The value of this parameter is coming from the existing NIC resource. The linked template is simply specifying the creation of the NIC again, but with a static IP. Here’s how that looks:

 

    {
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[parameters(‘nicName’)]",
      "apiVersion": "2015-06-15",
      "location": "[resourceGroup().location]",
      "tags": {
        "Role": "Web Server"
      },
      "dependsOn": [
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Static",
              "privateIPAddress": "[parameters(‘privateIp’)]",
              "subnet": {
                "id": "[parameters(‘SubnetRef’)]"
              }
            }
          }
        ]
      }
    }

Note that we are using “Static” allocation and specifying the parameter for the privateIPAddress. When called, this template is overwriting the original NIC properties and we can include other items such as tags.

Try it out

The complete example is available on GitHub. Please note, you will need to customize the parameters and also update the location of the linked template (it currently points to a non-existent location). For example, you could update it to point directly to my GitHub repository or use your own storage account.

2 comments… add one
  • Jason Masten Feb 10, 2022 Link Reply

    Thanks for sharing this! It worked perfectly for me!

  • anony Jun 6, 2022 Link Reply

    Thanks for the post. This doesn’t seem to work for me when I am deploying multiple vms using copy loops. can you help on that

Leave a Reply