Terraform - Azure Compute Gallery Deployment and Image Management
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 #
- 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 #
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 #
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 #
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 #
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 #
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.