Azure continuous VM deployment

Table Of Contents

Intro

One of the questions I have gotten from customers is, what happens if I run my deployment script of a VM multiple times? For instance, if the customer has a script that runs an ARM or Bicep template with multiple virtual machines, what will happen to the already running machine if they add a new one? Let us have a look at that in this blog post.

I have used the Microsoft documentation as a guideline for this blog post. You can find all the information on this Microsoft site.

https://docs.microsoft.com/en-us/azure/architecture/solution-ideas/articles/cicd-for-azure-vms

The concern I heard

The main concern I heard about having a continuous deployment of virtual machines in Azure is that the machine will be overwritten or reset, thereby losing any software installation done on the machine. I understand that fear since most IT admins deploy VMs and then install the software manually or with tools like config manager.

How can we approach the scenario

First, let us address the concern about using the continuous deployment with VMs. It is straightforward to remove the worry about overwriting or losing any changes done to a deployed VM. Deploying an ARM/Bicep template multiple times won’t overwrite anything on the VM unless you change the deployment template for that specific VM. If we go a step deeper, this is what happens. Azure will evaluate the deployment when submitted and compare it to what is already deployed. If the deployment hasn’t changed, Azure will report that all is OK. Azure will update the VM to reflect these changes if there are changes.

Let me show you an example. Code is from the Microsoft docs: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/quick-create-bicep?tabs=CLI

@description('Username for the Virtual Machine.')
param adminUsername string

@description('Password for the Virtual Machine.')
@minLength(12)
@secure()
param adminPassword string

@description('Unique DNS Name for the Public IP used to access the Virtual Machine.')
param dnsLabelPrefix string = toLower('${vmName}-${uniqueString(resourceGroup().id, vmName)}')

@description('Name for the Public IP used to access the Virtual Machine.')
param publicIpName string = 'myPublicIP'

@description('Allocation method for the Public IP used to access the Virtual Machine.')
@allowed([
  'Dynamic'
  'Static'
])
param publicIPAllocationMethod string = 'Dynamic'

@description('SKU for the Public IP used to access the Virtual Machine.')
@allowed([
  'Basic'
  'Standard'
])
param publicIpSku string = 'Basic'

@description('The Windows version for the VM. This will pick a fully patched Gen2 image of this given Windows version.')
@allowed([
 '2019-datacenter-gensecond'
 '2019-datacenter-core-gensecond'
 '2019-datacenter-core-smalldisk-gensecond'
 '2019-datacenter-core-with-containers-gensecond'
 '2019-datacenter-core-with-containers-smalldisk-g2'
 '2019-datacenter-smalldisk-gensecond'
 '2019-datacenter-with-containers-gensecond'
 '2019-datacenter-with-containers-smalldisk-g2'
 '2016-datacenter-gensecond'
])
param OSVersion string = '2019-datacenter-gensecond'

@description('Size of the virtual machine.')
param vmSize string = 'Standard_D2s_v3'

@description('Location for all resources.')
param location string = resourceGroup().location

@description('Name of the virtual machine.')
param vmName string = 'simple-vm'

var storageAccountName = 'bootdiags${uniqueString(resourceGroup().id)}'
var nicName = 'myVMNic'
var addressPrefix = '10.0.0.0/16'
var subnetName = 'Subnet'
var subnetPrefix = '10.0.0.0/24'
var virtualNetworkName = 'MyVNET'
var networkSecurityGroupName = 'default-NSG'

resource stg 'Microsoft.Storage/storageAccounts@2021-04-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}

resource pip 'Microsoft.Network/publicIPAddresses@2021-02-01' = {
  name: publicIpName
  location: location
  sku: {
    name: publicIpSku
  }
  properties: {
    publicIPAllocationMethod: publicIPAllocationMethod
    dnsSettings: {
      domainNameLabel: dnsLabelPrefix
    }
  }
}

resource securityGroup 'Microsoft.Network/networkSecurityGroups@2021-02-01' = {
  name: networkSecurityGroupName
  location: location
  properties: {
    securityRules: [
      {
        name: 'default-allow-3389'
        properties: {
          priority: 1000
          access: 'Allow'
          direction: 'Inbound'
          destinationPortRange: '3389'
          protocol: 'Tcp'
          sourcePortRange: '*'
          sourceAddressPrefix: '*'
          destinationAddressPrefix: '*'
        }
      }
    ]
  }
}

resource vn 'Microsoft.Network/virtualNetworks@2021-02-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefix
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetPrefix
          networkSecurityGroup: {
            id: securityGroup.id
          }
        }
      }
    ]
  }
}

resource nic 'Microsoft.Network/networkInterfaces@2021-02-01' = {
  name: nicName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: pip.id
          }
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', vn.name, subnetName)
          }
        }
      }
    ]
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2021-03-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPassword
    }
    storageProfile: {
      imageReference: {
        publisher: 'MicrosoftWindowsServer'
        offer: 'WindowsServer'
        sku: OSVersion
        version: 'latest'
      }
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
      }
      dataDisks: [
        {
          diskSizeGB: 1023
          lun: 0
          createOption: 'Empty'
        }
      ]
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
        }
      ]
    }
    diagnosticsProfile: {
      bootDiagnostics: {
        enabled: true
        storageUri: stg.properties.primaryEndpoints.blob
      }
    }
  }
}

output hostname string = pip.properties.dnsSettings.fqdn

If I change the VM size in the code above and rerun the deployment, the VM will be updated with the new size, but nothing else will change. Some settings can’t be changed to an already deployed VM. For instance, changing the vNet is not possible without complete redeployment.

Is the above always true?

No. As with most things in IT, there are always exceptions. The standard way to deploy a template in Azure is to do an incremental deployment, and then the above is true. If you, for some reason, deploy with the option “-Mode Complete,” it will remove anything in the resource group if it is a resource group deployment and rebuild what is in the code.

Example:

New-AzResourceGroupDeployment -Name Demo -ResourceGroupName "rg-demo-001" -Mode Complete -TemplateParameterFile Demo.bicep

Advice

My advice is to always use incremental deployments unless you have specific use cases where you need to remove everything each time you deploy the virtual machines.

Another piece of advice is to remember to include software and settings in your continuous deployments so that if you do need to remove and start over, you can do this safely and efficiently since everything is defined as code or other ways of automatic deployments.

There are multiple ways to ensure that settings are as you expect them to be, like using Azure DSC, Ansible or Chef. You can also install software with these tools, and there are tools like configuration manager, Microsoft Deployment Toolkit, PDQ Deploy, and more to help with software.

Summary

When working with infrastructure and DevOps, it is vital to think about the whole solution and not just the VM as a single item. It is also essential to have a good dev/test environment where you can test all changes and see what it does. Good help is also to use the “WhatIf” option on your deployments. This will show you what changes will be performed when you deploy your updated templates.

If you want some more examples or maybe a YouTube video to explain more on this topic, let me know, and I will try and make that happy.

Thanks for reading, and reach out if you have any comments or suggestions.

Comments