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

Terraform - Azure Compute Gallery Deployment and Image Management

·1865 words·9 mins· 100 views · 5 likes ·
Azure CLI Azure Cloud Shell Microsoft Microsoft Azure

Hi everyone! Today, in this blog post, we will explore how to deploy and manage Azure Shared Image Galleries using Terraform. As you know, Shared Image Galleries in Azure provide a centralized way to store and manage custom images for your virtual machines, enabling consistent and efficient deployments across multiple regions.

In this guide, I will detail the steps necessary to configure and deploy Azure Shared Image Galleries efficiently and effectively with Terraform, maximizing the benefits of automation and reproducibility that infrastructure as code provides. This guide covers all the basic and essential aspects, from setting up a resource group to defining shared images and managing their versions.

Let’s get started!

Prerequisites>

Prerequisites #

  • You need Terraform CLI on your local machine, if you’re new to using Terraform to deploy Microsoft Azure resources, then I recommend you check out this link.
  • A text editor or IDE of your choice (Visual Studio Code with terraform extension is my recommendation)
Declare Azure Provider in Terraform>

Declare Azure Provider in Terraform #

The provider.tf file in Terraform is used to specify and configure the providers used in your Terraform configuration. A provider is a service or platform where the resources will be managed. This could be a cloud provider like Microsoft Azure, AWS, Google Cloud, etc.

This file is important because it tells Terraform which provider’s API to use when creating, updating, and deleting resources. Without it, Terraform wouldn’t know where to manage your resources.

provider "azurerm" {
  features {}
}
Deploy Azure Resources Using Terraform>

Deploy Azure Resources Using Terraform #

In the case of Azure Shared Image Gallery deployment, the main.tf file contains the following key components:

  • azurerm_resource_group: This block sets up the Azure Resource Group where all other resources will be deployed.
  • data “azurerm_image”: This block retrieves information about an existing image in Azure. It requires the name of the image and the name of the resource group where the image is located.
  • azurerm_shared_image_gallery: This block defines a shared image gallery, which is used to store and manage images. It includes the name, location, and description of the gallery, as well as sharing permissions and optional community gallery settings.
  • azurerm_shared_image: This block defines a shared image within the gallery. It includes the name, OS type, and identifier details such as publisher, offer, and SKU. Optional fields like description, architecture, and recommended vCPU and memory configurations can also be specified.
  • azurerm_shared_image_version: This block defines versions of the shared image. Each version includes the name, managed image ID, and target regions where the image will be replicated. Optional fields like end-of-life date and replication mode can also be specified.
// Define the resource group
resource "azurerm_resource_group" "rg" {
  name     = var.resource_group.name
  location = var.resource_group.location
  tags     = var.tags
}

// Get the existing Azure image
data "azurerm_image" "img" {
  name                = var.existing_image.name
  resource_group_name = var.existing_image.resource_group_name
}

// Define the shared image gallery
resource "azurerm_shared_image_gallery" "sig" {
  name                = var.shared_image_gallery.name
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  description         = var.shared_image_gallery.description
  tags                = var.tags

  sharing {
    permission = var.shared_image_gallery.sharing_permission

    community_gallery {
      eula            = var.shared_image_gallery.eula
      prefix          = var.shared_image_gallery.prefix
      publisher_email = var.shared_image_gallery.publisher_email
      publisher_uri   = var.shared_image_gallery.publisher_uri
    }
  }
}

// Define the shared image
resource "azurerm_shared_image" "si" {
  name                = var.shared_image.name
  gallery_name        = azurerm_shared_image_gallery.sig.name
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  os_type             = var.shared_image.os_type

  identifier {
    publisher = var.shared_image.publisher
    offer     = var.shared_image.offer
    sku       = var.shared_image.sku
  }

 
  description                  = var.shared_image.description
  specialized                  = var.shared_image.specialized
  architecture                 = var.shared_image.architecture
  hyper_v_generation           = var.shared_image.hyper_v_generation
  max_recommended_vcpu_count   = var.shared_image.max_recommended_vcpu_count
  min_recommended_vcpu_count   = var.shared_image.min_recommended_vcpu_count
  max_recommended_memory_in_gb = var.shared_image.max_recommended_memory_in_gb
  min_recommended_memory_in_gb = var.shared_image.min_recommended_memory_in_gb
  end_of_life_date             = var.shared_image.end_of_life_date
  tags                         = var.tags

  depends_on = [
    azurerm_shared_image_gallery.sig,
  ]
}

// Define the shared image version
resource "azurerm_shared_image_version" "siv" {
  for_each = var.shared_image_versions

  name                = each.key
  gallery_name        = azurerm_shared_image_gallery.sig.name
  image_name          = azurerm_shared_image.si.name
  resource_group_name = azurerm_resource_group.rg.name
  location            = var.resource_group.location
  managed_image_id    = data.azurerm_image.img.id

  target_region {
    name                   = var.resource_group.location
    regional_replica_count = each.value.regional_replica_count
    storage_account_type   = each.value.storage_account_type
  }

  target_region {
    name                   = each.value.target_region_name
    regional_replica_count = each.value.regional_replica_count
    storage_account_type   = each.value.storage_account_type
  }


  end_of_life_date                         = each.value.end_of_life_date
  exclude_from_latest                      = each.value.exclude_from_latest
  deletion_of_replicated_locations_enabled = each.value.deletion_of_replicated_locations_enabled
  replication_mode                         = each.value.replication_mode
  tags                                     = var.tags

  depends_on = [
    azurerm_shared_image.si, // Versions depend on the image
  ]
}
Declaration of input variables>

Declaration of input variables #

The variables.tf file in Terraform defines the variables I will use in the main.tf file. These variables allow for more flexibility and reusability in the code.

In this example, the variables defined in the variables.tf include:

  • resource_group: This block declares a variable named resource_group, which is an object. It contains the properties name and location, used to define the Azure Resource Group where all resources will be deployed.
  • existing_image: This block declares a variable named existing_image, which is an object. It contains the properties name and resource_group_name, used to reference an existing image in Azure.
  • shared_image_gallery: This block declares a variable named shared_image_gallery, which is an object. It contains properties like name, description, sharing_permission, eula, prefix, publisher_email, and publisher_uri, used to define a shared image gallery and its settings.
  • shared_image: This block declares a variable named shared_image, which is an object. It includes properties such as name, os_type, publisher, offer, sku, and optional fields like description, specialized, architecture, hyper_v_generation, max_recommended_vcpu_count, min_recommended_vcpu_count, max_recommended_memory_in_gb, min_recommended_memory_in_gb, and end_of_life_date.
  • shared_image_versions: This block declares a variable named shared_image_versions, which is a map of objects. Each object contains properties such as regional_replica_count, storage_account_type, target_region_name, and optional fields like end_of_life_date, exclude_from_latest, deletion_of_replicated_locations_enabled, and replication_mode.
  • tags: This block declares a variable named tags, which is a map of strings. It is used to assign tags to the Azure resources being created. For example, you can use a key-value pair such as Terraform = true to indicate that the resource was deployed with Terraform.
// Define the resource group details
variable "resource_group" {
  type = object({
    name     = string
    location = string
  })
}

// Define the existing image details
variable "existing_image" {
  type = object({
    name                = string
    resource_group_name = string
  })
}

// Define the shared image gallery details
variable "shared_image_gallery" {
  description = "Shared image gallery details"
  type = object({
    name               = string
    description        = optional(string)
    sharing_permission = optional(string)
    eula               = optional(string)
    prefix             = optional(string)
    publisher_email    = optional(string)
    publisher_uri      = optional(string)
  })

  validation {
    condition     = contains(["Community", "Groups", "Private"], var.shared_image_gallery.sharing_permission)
    error_message = "sharing_permission must be one of 'Community', 'Groups', or 'Private'."
  }
}

// Define the shared image details
variable "shared_image" {
  description = "Shared image details"
  type = object({
    name                         = string
    os_type                      = string
    publisher                    = string
    offer                        = string
    sku                          = string
    description                  = optional(string)
    specialized                  = optional(bool)
    architecture                 = optional(string)
    hyper_v_generation           = optional(string)
    max_recommended_vcpu_count   = optional(number)
    min_recommended_vcpu_count   = optional(number)
    max_recommended_memory_in_gb = optional(number)
    min_recommended_memory_in_gb = optional(number)
    end_of_life_date             = optional(string)
  })

  validation {
    condition     = contains(["Linux", "Windows"], var.shared_image.os_type)
    error_message = "os_type must be either 'Linux' or 'Windows'."
  }

  validation {
    condition     = contains(["x64", "Arm64"], var.shared_image.architecture)
    error_message = "architecture must be either 'x64' or 'Arm64'."
  }

  validation {
    condition     = contains(["V1", "V2"], var.shared_image.hyper_v_generation)
    error_message = "hyper_v_generation must be either 'V1' or 'V2'."
  }
}

// Define the shared image versions details
variable "shared_image_versions" {
  description = "Shared image versions details"
  type = map(object({
    regional_replica_count                   = number
    storage_account_type                     = string
    target_region_name                       = string
    end_of_life_date                         = optional(string)
    exclude_from_latest                      = optional(bool)
    deletion_of_replicated_locations_enabled = optional(bool)
    replication_mode                         = optional(string)
  }))

  validation {
    condition     = alltrue([for v in var.shared_image_versions : contains(["Standard_LRS", "Premium_LRS", "Standard_ZRS"], v.storage_account_type)])
    error_message = "storage_account_type must be one of 'Standard_LRS', 'Premium_LRS', or 'Standard_ZRS'."
  }

  validation {
    condition     = alltrue([for v in var.shared_image_versions : contains(["Full", "Shallow"], v.replication_mode)])
    error_message = "replication_mode must be either 'Full' or 'Shallow'."
  }
}

// Define common tags for all resources
variable "tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default = {
    Environment = "www.jorgebernhardt.com"
    Terraform   = "true"
  }
}
Declaration of output values>

Declaration of output values #

The output.tf file in Terraform extracts and displays information about the resources created or managed by your Terraform configuration. These outputs are defined using the output keyword and can be used to return information that can be useful for the user, for other Terraform configurations, or for programmatically using the information in scripts or other tools.

In this example, the output.tf file returns information about the resource group, shared image gallery, shared images, and shared image versions that were created.

  • resource_group: This output block provides information about the resource group, including its name, location, and ID.
  • shared_image_gallery: This output block provides information about the shared image gallery, including its name, location, and ID.
  • shared_image: This output block provides information about the shared image, including its name, location, ID, OS type, publisher, offer, and SKU.
  • shared_image_versions: This output block provides information about the shared image versions, including their names, locations, IDs, end-of-life dates, whether they are excluded from the latest filter, if the deletion of replicated locations is enabled, replication modes, and target regions.

Once Terraform has finished applying your configuration, it will display the defined outputs.

// Output for the resource group
output "resource_group" {
  description = "Information about the resource group"
  value = {
    name     = azurerm_resource_group.rg.name
    location = azurerm_resource_group.rg.location
    id       = azurerm_resource_group.rg.id
  }
}

// Output for the shared image gallery
output "shared_image_gallery" {
  description = "Information about the shared image gallery"
  value = {
    name        = azurerm_shared_image_gallery.sig.name
    location    = azurerm_shared_image_gallery.sig.location
    id          = azurerm_shared_image_gallery.sig.id
  }
}

// Output for the shared image
output "shared_image" {
  description = "Information about the shared image"
  value = {
    name        = azurerm_shared_image.si.name
    location    = azurerm_shared_image.si.location
    id          = azurerm_shared_image.si.id
    os_type     = azurerm_shared_image.si.os_type
    publisher   = azurerm_shared_image.si.identifier[0].publisher
    offer       = azurerm_shared_image.si.identifier[0].offer
    sku         = azurerm_shared_image.si.identifier[0].sku
  }
}

// Output for the shared image versions
output "shared_image_versions" {
  description = "Information about the shared image versions"
  value = {
    for k, v in azurerm_shared_image_version.siv : k => {
      name                                     = v.name
      location                                 = v.location
      id                                       = v.id
      end_of_life_date                         = v.end_of_life_date
      exclude_from_latest                      = v.exclude_from_latest
      deletion_of_replicated_locations_enabled = v.deletion_of_replicated_locations_enabled
      replication_mode                         = v.replication_mode
      target_regions = [
        for tr in v.target_region : {
          name                   = tr.name
          regional_replica_count = tr.regional_replica_count
          storage_account_type   = tr.storage_account_type
        }
      ]
    }
  }
}
Executing the Terraform Deployment>

Executing the Terraform Deployment #

Now that you’ve declared the resources correctly, it’s time to take the following steps to deploy them in your Azure environment.

  • Initialization: To begin, execute the terraform init command. This will initialize your working directory that holds the .tf files and download the provider specified in the provider.tf file, and configure the Terraform backend. If you want to know how, check this link.

  • Planning: Next, execute the terraform plan. This command creates an execution plan and shows Terraform’s actions to achieve the desired state defined in your .tf files. This gives you a chance to review the changes before applying them.

  • Apply: When you’re satisfied with the plan, execute the terraform apply command. This will implement the required modifications to attain the intended infrastructure state. Before making any changes, you will be asked to confirm your decision.

  • Inspection: After applying the changes, you can use terraform show command to see the current state of your infrastructure.

  • Destroy (optional): when a project is no longer needed or when resources have become outdated. You can use the terraform destroy command. This will remove all the resources that Terraform has created.

References and useful links #

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