Azure Lab Services – Azure Compute Gallery Integration, and Custom Images using Packer

As a follow on from my last post, I wanted to explore using Packer to create images for Azure Lab Services in more detail. Specifically, how images created using Packer could be imported into Azure Lab Services. If you’re new to Azure Lab Services, I’d recommend reading my previous post on it.


Why use Packer with Azure Lab Services?

This is the key question for this post… why would you actually want to use Packer with Azure Lab Services?

In my view, the key here is in how Packer allows us to create images programmatically, and when combined with Azure Lab Services and tooling like Terraform, we can spin up whole lab environments, along with custom images and applications very easily, repeatably, and quickly. It also means we can manage the creation, ongoing changes, and removal of Azure Lab environments, with our custom images, using DevOps toolsets like Azure DevOps, GitHub and more.

✅ In this post I’ll run through the creation of a Packer Image, the storage of that image in an Azure Compute Gallery, and then how we can integrate the Compute Gallery to Azure Lab Services, and therefore use our Custom image.


Step 1 – Creating A Compute Gallery and Packer Image

To start this process, we need to create the basic elements that Packer needs to run in Azure, namely Resource Groups, and an Azure Compute Gallery to store our image. To do this, I’ll use the Azure CLI:

az group create -l uksouth -n rg-uks-packer-build
az group create -l uksouth -n rg-uks-packer-images
az group create -l uksouth -n rg-uks-AzureLabsImages
az sig create --resource-group rg-uks-AzureLabsImages --gallery-name AzureLabsImages
az sig image-definition create --resource-group rg-uks-AzureLabsImages --gallery-name AzureLabsImages --gallery-image-definition win11-22h2-pro --publisher AzureLabsImages --offer Labs-Win11-22h2-Pro --sku Labs-Win11 --os-type windows --os-state Generalized --hyper-v-generation V2

This will create the following items:

  • 3 Resource Groups, 1 for Packer build items, 1 to store the created image, and 1 for the Azure Compute Gallery.
  • 1 Azure Compute Gallery
  • 1 Image Definition within the Azure Compute Gallery

It’s worth noting, for this test, I am creating a Windows 11 22h2 Pro Image – if you wish to create something different to this, you’ll need to make changes to the code/templates shared within this post.

Next, we need to setup a Service Principal for Packer – to do this, use the CLI command below (remember to add your Subscription ID):

az ad sp create-for-rbac --name packer --role contributor --scopes /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx --query "{ client_id: appId, client_secret: password, tenant_id: tenant }"

This will output Service Principal details:

Creating a Service Principal for Packer
Creating a Service Principal for Packer

Note: These details give Contributor access to the Subscription, and should be treated as sensitive!

Next, we need to add these to our Packer template. Here’s the template file I am using – you’ll need to add your Service Principal details to the below Packer template. (Anything you need to replace is marked with “__replace this__“):

source "azure-arm" "autogenerated_1" {
  azure_tags = {
    environment = "packer"
  }
  build_resource_group_name         = "rg-uks-packer-build"
  client_id                         = "__replace this__"
  client_secret                     = "__replace this__"
  communicator                      = "winrm"
  image_offer                       = "Windows-11"
  image_publisher                   = "MicrosoftWindowsDesktop"
  image_sku                         = "win11-22h2-pro"
  managed_image_name                = "packer-win11-22h2-pro"
  managed_image_resource_group_name = "rg-uks-packer-images"
  os_type                           = "Windows"
  shared_image_gallery_destination {
    gallery_name        = "AzureLabsImages"
    image_name          = "win11-22h2-pro"
    image_version       = "1.0.0"
    replication_regions = ["uksouth"]
    resource_group      = "rg-uks-AzureLabsImages"
  }
  subscription_id = "__replace this__"
  vm_size         = "Standard_D4as_v5"
  winrm_insecure  = true
  winrm_timeout   = "5m"
  winrm_use_ssl   = true
  winrm_username  = "packer"
}

build {
  sources = ["source.azure-arm.autogenerated_1"]

  provisioner "powershell" {
    inline = ["Set-ExecutionPolicy Bypass -Scope Process -Force", "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12", "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))", "choco install 7zip -y --force --force-dependencies", "choco install vscode -y --force --force-dependencies", "choco install firefox -y --force --force-dependencies", "choco install terraform -y --force --force-dependencies"]
  }

  provisioner "windows-restart" {
  }

  provisioner "powershell" {
    inline = ["while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm", "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }"]
  }

}

To run the code above, save the file as filename.pkr.hcl and then run “packer build .\filename.pkr.hcl“.

Once the Packer build has been completed, we will see our image imported into the Compute Gallery:

Our Image is now ready and stored in our Compute Gallery
Our Image is now ready and stored in our Compute Gallery

✅ We can now move on to using this image within a Lab!


Step 2 – Enabling our Custom Images in Azure Lab Services

For the next part of this post, you will need a Lab Service Plan already created – if you need guidance on doing this, please refer to my previous post here. Once your Lab Service plan is in place, browse to it in the Portal, and click on “Azure compute gallery“:

Lab Service Plan - Azure Compute Gallery
Lab Service Plan – Azure Compute Gallery

Note, if you are attaching an existing Gallery, there are Permissions that must be assigned first – see hereWithin the next screen, we can link our Gallery (created earlier in Step 1) to this Lab Service Plan:

Attaching our Compute Gallery
Attaching our Compute Gallery

Select the previously created Gallery:

Attaching our Compute Gallery
Attaching our Compute Gallery

The Gallery will then be attached:

Compute Gallery attached!
Compute Gallery attached!

Once attached, you’ll see the Gallery and it’s images displayed:

Compute Gallery attached and our Packer created image is shown.
Compute Gallery attached and our Packer created image is shown.

Before we can use our Windows 11 Image (shown above, in our Gallery, as “win11-22h2-pro“), we need to enable this image. To do this, tick the image and select “Enable Image” and then click on “Apply” at the bottom of the screen:

Enabling our Image
Enabling our Image

✅ Now that our Image is enabled, we can use this image when creating a new Lab Environment.


Step 3 – Using Custom Images within Azure Lab Services

Now that we have integrated the Compute Gallery and enabled our Custom Image, we can use this Image within Azure Lab Services. Firstly, let’s look at doing this within the Portal. We start by visiting labs.azure.com to create our lab. To start, click on “+ New Lab“:

New Lab
New Lab

Once selected, the New lab screen will appear. In order to see our custom image, click on “Virtual machine image” as shown in the screenshot below:

Creating a New Lab - Selecting Images
Creating a New Lab – Selecting Images

In this dropdown, you should then see the custom image that has been created:

Selecting our Custom Image
Selecting our Custom Image

Once our custom image has been selected, we can follow the rest of the Lab creation steps, firstly, with the local administrator details:

Setting Lab VM Credentials
Setting Lab VM Credentials

Next, it’s the Lab Policies:

Setting Lab Policies
Setting Lab Policies

Finally, we can select “Use virtual machine image without customization”, as we have already setup this image using Packer:

Deploying the VM Image without Customising
Deploying the VM Image without Customising

The Lab will now be deployed using our custom image:

Creating Lab
Creating Lab

Once this has been done, we need to publish the image – for my test I will just select 1 VM to test with:

Image Publishing
Image Publishing

This image will then show as Publishing:

Image Publishing
Image Publishing

Once the image is published, we can add users and access the lab. If you need a guide on this, please refer to my previous post on Azure Lab Services here. When we access the Lab, we need to power on the VM and wait for it to start – this process is outlined below:

Starting our Lab VM
Starting our Lab VM

Once the VM is started and we have clicked on the connect symbol, we are provided with an RDP file we can use to connect to our VM. Once connected, we can see our VM is running and it has the applications installed using Chocolatey within Packer on it:

Checking our Custom Image deployed into Azure Lab Services
Checking our Custom Image deployed into Azure Lab Services

✅ We’ve now completed the deployment and testing of Azure Lab Services, using a custom Packer created image with Azure Compute Gallery!


What about Terraform?

Fortunately, adding our Packer created image into a Lab Services deployment using Terraform is a very simple process. If you need a refresher on deploying Azure Lab Services using Terraform, please see my previous post here. To use our Custom Image, we just need to add two things to the deployment; The Compute Gallery to the Lab Service Plan, and our Image into the Lab deployment. Note, if you are attaching an existing Gallery, there are Permissions that must be assigned first – see here

This is done within Terraform as follows:

Lab Service Plan:

This requires the Compute Gallery Resource ID adding to the Resource Block:

# Lab Service Plan
resource "azurerm_lab_service_plan" "plan1" {
  name                = "lsp-${var.labname}-${var.region}"
  resource_group_name = azurerm_resource_group.rg.name
  location            = var.region
  allowed_regions     = [var.region]
  shared_gallery_id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-uks-AzureLabsImages/providers/Microsoft.Compute/galleries/AzureLabsImages"

  support {
    email        = "jake@jakewalsh.co.uk"
    instructions = "Welcome to your Azure Lab, please raise a support request if you encounter any issues"
    url          = "https://jakewalsh.co.uk"
  }
  tags = {
    Environment = var.labname
    Function    = "AzureLabServices"
  }
}
Enabling our custom image – AzAPI to the Rescue!

I had a slight issue with my Lab whereby I couldn’t enable the Image once the Compute Gallery had been attached to the Lab (using the above code), and despite searching I could not find any way of doing this within the AzureRM provider (at the time of writing). The problem I found was that even though the Gallery had been attached, the Lab creation would fail as the image was not enabled:

Thankfully, the AzAPI Terraform provider was able to let me work around this using the below code:

# AzAPI to enable the Image in the Gallery
resource "azapi_resource_action" "enable-image" {
  type        = "Microsoft.LabServices/labPlans/images@2022-08-01"
  resource_id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-lab1-uksouth/providers/Microsoft.LabServices/labPlans/lsp-lab1-uksouth/images/win11-22h2-pro"
  action      = ""
  method      = "PUT"
  body = jsonencode({
    "properties" : {
      "enabledState" : "Enabled"
    }
  })
  depends_on = [
    azurerm_lab_service_plan.plan1
  ]
}

With the above code set, and a depends_on for this set in the Lab code below, the image is enabled:

Our custom image has been enabled using the AzAPI Provider
Our custom image has been enabled using the AzAPI Provider

With this image enabled, the Lab is created without issue.

Lab

Within the Lab Resource we can specific the ID of the image:

# Lab
resource "azurerm_lab_service_lab" "lab1" {
  name                = "lab-${var.labname}-${var.region}"
  resource_group_name = azurerm_resource_group.rg.name
  location            = var.region
  title               = "Lab 1"
  description         = "Virtual Machine for Lab Environment"
  lab_plan_id         = azurerm_lab_service_plan.plan1.id
  depends_on = [
    azapi_resource_action.enable-image
  ]
  connection_setting {
    client_rdp_access = "Public"
  }

  security {
    open_access_enabled = false
  }

  virtual_machine {
    shared_password_enabled = true

    admin_user {
      username = var.labusername
      password = azurerm_key_vault_secret.vmpassword.value
    }

    image_reference {
      id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-uks-AzureLabsImages/providers/Microsoft.Compute/galleries/AzureLabsImages/images/win11-22h2-pro"
    }

    sku {
      name     = "Classic_Fsv2_8_16GB_128_S_SSD"
      capacity = 1
    }
  }
  tags = {
    Environment = var.labname
    Function    = "AzureLabServices"
  }
}

✅ This concludes this post – I hope the above has been useful in demonstrating how to use Azure Compute Gallery integration, and Custom Images using Packer within Azure Lab Services.

Skip to content