Skip to main content
Jorge Bernhardt Jorge Bernhardt
  1. Posts/

Bicep - Easy deployment of Azure Virtual Machine Scale Sets

·1155 words·6 mins· 100 views · 5 likes ·
Bicep Microsoft Azure IaC Azure CLI

Hey there, folks! In today’s blog post, we’ll discuss the Azure Virtual Machine Scale Sets, also known as VMSS. If you have experience with Azure, you might already know that VMSS are crucial for managing cloud services that can be scaled up and down based on demand. However, it’s understandable if you find it a bit overwhelming to get started with them.

To make this easier, we’re using Bicep. Bicep is Azure’s language, designed to simplify how we handle ARM templates. It’s all about making code clearer, more readable, and more straightforward in defining and deploying Azure resources.

In this post, I’ll walk you through optimizing VMSS deployment using Bicep. Let’s explore ways to improve your Azure VMSS approach. Are you ready? Let’s get started!

Prerequisites>

Prerequisites #

Before you start, you’ll need the following to deploy and manage resources with Bicep:

  • You need Azure CLI version 2.20.0 or later to deploy Bicep files on your local machine.
  • A text editor or IDE of your choice (Visual Studio Code with Bicep extension is my recommendation)
Create the Bicep file>

Create the Bicep file #

The first step in deploying a Bicep template is to create the Bicep file that defines your resources. Create a new file named vmss.bicep. This file will contain the code necessary to define and configure your Log Analytics Workspaces.

@description('Specifies the location for all resources.')
@allowed([
  'westeurope'
  'northeurope'
])
param location string

@description('The SKU of the VM.')
param vmSku string

@description('The number of VM instances.')
param instanceCount int

@description('The prefix for the VMSS name.')
param nameSubfix string

@description('The ID of the subnet where the VMSS will be located.')
param subnetId string

@description('The operating system type.')
@allowed([
  'Windows'
  'Ubuntu'
])
param osType string

@description('The admin username for the VM.')
param adminUsername string

@secure()
@description('The admin password for the VM.')
param adminPassword string

@description('The tags to be associated with the VMSS.')
param tags object = {
  bicep: 'true'
  environment: 'jorgebernhatdt.com'
}

var osProfile = {
  computerNamePrefix: nameSubfix
  adminUsername: adminUsername
  adminPassword: adminPassword
  windowsConfiguration: osType == 'Windows' ? {
    enableAutomaticUpdates: true
  } : null
  linuxConfiguration: osType != 'Windows' ? {
    disablePasswordAuthentication: false
  } : null
}

var imageReference = {
  publisher: osType == 'Windows' ? 'MicrosoftWindowsServer' : 'Canonical'
  offer: osType == 'Windows' ? 'WindowsServer' : 'UbuntuServer'
  sku: osType == 'Windows' ? '2022-Datacenter' : '18_04-LTS-GEN2'
  version: 'latest'
}

var networkConfig = {
  networkInterfaceConfigurations: [
    {
      name: 'nic'
      properties: {
        primary: true
        ipConfigurations: [
          {
            name: 'ipconfig'
            properties: {
              subnet: {
                id: subnetId
              }
            }
          }
        ]
      }
    }
  ]
}

var osDiskConfig = {
  caching: 'ReadWrite'
  managedDisk: {
    storageAccountType: 'Standard_LRS'
  }
  createOption: 'FromImage'
}

var storageProfile = {
  imageReference: imageReference
  osDisk: osDiskConfig
}

resource vmss 'Microsoft.Compute/virtualMachineScaleSets@2023-07-01' = {
  name: 'vmss-${nameSubfix}'
  location: location
  tags: tags
  sku: {
    name: vmSku
    tier: 'Standard'
    capacity: instanceCount
  }
  properties: {
    overprovision: true
    upgradePolicy: {
      mode: 'Manual'
    }
    virtualMachineProfile: {
      osProfile: osProfile
      storageProfile: storageProfile
      networkProfile: networkConfig
    }
  }
}

output vmssId string = vmss.id
output vmssName string = vmss.name
output vmssLocation string = vmss.location

Important: The imageReference variable dynamically determines the appropriate operating system for the VMSS based on the specified osType. Using ternary operators simplifies the selection between Windows and Ubuntu server images, enhancing the script’s efficiency and reducing the likelihood of manual errors. This approach streamlines the deployment process and exemplifies Bicep’s capability to manage complex decisions within a concise and readable format.

Deployment scope>

Deployment scope #

You can target your deployment to a resource group, subscription, management group, or tenant. In this case, you will need an Azure resource group to house all the necessary resources.

Important: By default, when deploying a Bicep template, the scope where the resource should be deployed is a resource group.

You can use an existing Resource Group, or you can create a new Resource Group. If you want to know how to create a Resource Group using Azure CLI, check out this link.

Parameters>

Parameters #

Personalization is key to making your template reusable. With parameters, you can easily tailor the template to your specific needs. You can use either inline parameters or a parameter file to pass parameter values. In my case, I’ll use a file to pass the parameters; here’s an example.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "location": {
        "value": "northeurope"
      },
      "vmSku": {
        "value": "Standard_DS1_v2"
      },
      "instanceCount": {
        "value": 2
      },
      "nameSubfix": {
        "value": "bicep"
      },
      "subnetId": {
        "value": "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Network/virtualNetworks/{vnet-name}/subnets/{subnet-name}"
      },
      "osType": {
        "value": "Ubuntu"
      },
      "adminUsername": {
        "value": "azadmin"
      },
      "tags": {
        "value": {
          "bicep": "true",
          "environment": "PROD"
        }
      }
    }
}

Important: Please note that the parameter file stores parameter values in plain text format. If you need to include a parameter with sensitive data, it’s recommended to store the value in a secure key vault.

Secure Handling of Sensitive Data>

Secure Handling of Sensitive Data #

The first critical step in our secure deployment process involves retrieving the admin password from Azure Key Vault. This ensures the password is handled securely and not exposed in the script.

adminPassword=$(az keyvault secret show --name <secret-name> --vault-name <keyvault-name> --query value -o tsv)

Next, we use the retrieved password in deploying our Bicep template. This step integrates our secure practice into the deployment process.

Preview changes>

Preview changes #

Before deploying your Bicep file, it’s a best practice to preview the changes. This helps you understand the impact of your deployment on the existing Azure resources. Use Azure’s what-if operation. This doesn’t make any actual changes but provides a detailed output, including color-coded results, which show potential changes. Here’s the command to preview your deployment:

az deployment group what-if \
--resource-group <resource-group-name> \
--template-file <filename>.bicep \
--parameters @<filename>.parameters.json \
--parameters adminPassword=$adminPassword 
Deploy the Azure resource>

Deploy the Azure resource #

After reviewing and confirming the changes, you may proceed with deploying your Azure resources by using the following command:

az deployment group create \
--resource-group <resource-group-name> \
--template-file <filename>.bicep \
--parameters @<filename>.parameters.json \
--parameters adminPassword=$adminPassword 
Validate the deployment>

Validate the deployment #

To verify that the budget resource was created correctly, you can either use the Azure Portal or the Azure CLI to check the created resources and their configurations. For Azure CLI, use the following command. The following command provides a JSON output with the basic properties of your VMSS, confirming its existence and high-level configuration.

az vmss show \
  --name <vmss-name> \
  --resource-group <resource-group-name> \
  --query "{ \
    vmssName:name, \
    location:location, \
    sku:sku \
  }" \
  --output json

Instead, this provides detailed information about each instance, including its size, operating system type, and provisioning status, allowing you to confirm that each instance is configured and running as expected.

az vmss list-instances \
  --name <vmss-name> \
  --resource-group <resource-group-name> \
  --query "[].{ \
    vmId:instanceId, \
    vmSize:hardwareProfile.vmSize, \
    osType:storageProfile.osDisk.osType, \
    provisioningState:provisioningState \
  }" \
  --output json

References and useful links #

Thank you for taking the time to read my post. I sincerely hope that you find it helpful.