In today's fast-paced digital landscape, efficient and seamless data migration is critical for businesses striving to stay ahead. ShareGate, a leading migration tool, is essential for organizations transitioning to the cloud. However, setting up and configuring ShareGate on an Azure Virtual Machine (VM) can be complex and time-consuming. In this blog post, we will walk you through automating the deployment of an Azure VM and the installation of ShareGate using Terraform. By leveraging Infrastructure as Code (IaC), we aim to simplify the process, enhance efficiency, and ensure a consistent and repeatable setup for your migration needs.
We'll break down each Terraform file involved, providing explanations, customizations, and conclusions for each component. By the end of this guide, you will have a comprehensive understanding of how to use Terraform to set up your Azure environment and automate the installation of ShareGate.
Before proceeding, ensure you have the following:
In the next sections we will dive into the creation of the terraform configuration files, provide a brief explanation, context on how to customize it for your own use, a high-level conclusion and finally the terraform configuration code to deploy the solution.
The provider.tf file defines the Azure provider that Terraform will use to manage your Azure resources. The provider block specifies the required provider and any necessary configurations.
If you are using a different version of the Azure provider or need additional configurations, you can modify the provider block accordingly.
This file is essential as it sets up the connection between Terraform and Azure, enabling the deployment and management of resources in your Azure environment.
#####################################################################
# provider.tf
#####################################################################
# Azure Provider
provider "azurerm" {
features {}
}
The version.tf file specifies the required Terraform version and provider versions. This ensures compatibility and prevents issues arising from version discrepancies.
Adjust the versions according to your requirements or based on the latest compatibility guidelines.
Defining versions ensures a stable and predictable environment for your Terraform configurations.
#####################################################################
# version.tf
#####################################################################
# Provider Version
terraform {
required_version = ">=1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
random = {
source = "hashicorp/random"
version = "~>3.0"
}
}
}
The random.tf file uses the random_id resource to generate unique identifiers, ensuring resource names are unique.
You can modify the byte_length or add other keepers to customize how the IDs are generated.
Using random_id helps avoid name collisions and ensures each resource has a unique identifier.
#####################################################################
# random.tf
#####################################################################
# Generate random text for a unique storage account name
resource "random_id" "random_id" {
keepers = {
# Generate a new ID only when a new resource group is defined
resource_group = azurerm_resource_group.rg.name
}
byte_length = 6
}
The local.tf file defines reusable values like common tags and network security group (NSG) rules using local variables.
You can adjust the tags and NSG rules to match your organization's tagging standards and security requirements.
Local variables enhance maintainability and readability by centralizing commonly used values.
#####################################################################
#local.tf
#####################################################################
# Create common tags and NSG Rules using locals resource
locals {
common_tags = {
Environment = var.environment
Owner = var.owner
ProjectCode = var.projectcode
CostCenter = var.costcenter
}
nsgrules = {
AllowRDP = {
name = "AllowRDP"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefix = "*"
destination_address_prefix = "*"
}
AllowHTTP = {
name = "AllowHTTP"
priority = 120
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
}
AllowHTTPS = {
name = "AllowHTTPS"
priority = 130
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
}
The variables.tf file declares input variables used throughout the Terraform configuration, making the setup flexible and reusable.
Modify the default values and descriptions as per your project requirements.
Variables provide flexibility and allow the same Terraform configuration to be used in different environments with different inputs.
#####################################################################
# variables.tf
#####################################################################
variable "prefix" {
type = string
default = "app"
description = "Prefix of the resource name"
}
variable "location" {
description = "Azure Location"
type = string
}
variable "environment" {
description = "Environment Type - Prod/Stage/Dev"
type = string
}
variable "owner" {
description = "Environment Owner"
type = string
}
variable "projectcode" {
description = "Project Code"
type = string
}
variable "costcenter" {
description = "Cost Center"
type = string
}
variable "vnet_address_space" {
description = "Virtual Network Address Space"
type = list(string)
}
variable "subnet_names" {
description = "Names of the subnets"
type = list(string)
default = []
validation {
condition = length(var.subnet_names) <= 5
error_message = "The number of subnets must be less than 5."
}
}
variable "allocation_method" {
description = "Public IP Allocation Method"
type = string
}
variable "admin_username" {
description = "Windows VM Admin User Name"
type = string
default = "adminuser"
}
variable "admin_password" {
description = "Windows VM Admin Password"
sensitive = true
type = string
}
variable "vm_size" {
description = "Virtual Machine Size"
type = string
default = "Standard_D2S_v3"
}
variable "os_disk_caching" {
description = " VM OS Disk Caching"
type = string
default = "ReadWrite"
}
variable "os_disk_storage_account_type" {
description = "OS Disk Storae Account Type"
type = string
}
variable "src_img_ref_publisher" {
description = "Source Image Reference Pubisher"
type = string
}
variable "src_img_ref_offer" {
description = "Source Image Reference Offer"
type = string
}
variable "src_img_ref_sku" {
description = "Source Image Reference Sku"
type = string
}
variable "src_img_ref_version" {
description = "Source Image Reference Version"
type = string
}
variable "stg_account_tier" {
description = "Storage Account Tier"
}
variable "stg_account_replication_type" {
description = "Storage Account Replication Type"
type = string
}
variable "storage_container_name" {
description = "Storage2 Account Container Name"
type = string
}
variable "container_access_type" {
description = "Container Access Type"
type = string
}
variable "blob_sharegate_msi" {
description = "Blob ShareGate MSI"
type = string
}
variable "blob_type" {
description = "Blob Type"
type = string
}
The terraform.tfvars file provides the values for the input variables defined in variables.tf. This file is used to customize the deployment for a specific environment.
Modify the values in this file to match your environment's requirements, such as location, VM size, and credentials.
The terraform.tfvars file is crucial for defining environment-specific values, allowing the same configuration to be reused across different setups.
#####################################################################
# terraform.tfvars
#####################################################################
# General
prefix = "sgate"
location = "East Us"
# Common Tags
environment = "Dev"
owner = "Roger Taylor"
projectcode = "0123"
costcenter = "C8956"
# Virtual Network
vnet_address_space = ["10.0.0.0/16"]
subnet_names = ["sg-subnet1"]
# Public IP
allocation_method = "Static"
# VM Credentials
admin_username = "adminuser"
admin_password = "Azure@123"
# VM Configuration
vm_size = "Standard_D2S_v3"
os_disk_caching = "ReadWrite"
os_disk_storage_account_type = "Standard_LRS"
src_img_ref_publisher = "MicrosoftWindowsServer"
src_img_ref_offer = "WindowsServer"
src_img_ref_sku = "2022-Datacenter"
src_img_ref_version = "latest"
# VM diagnostic Storage Account
stg_account_tier = "Standard"
stg_account_replication_type = "LRS"
storage_container_name = "software"
container_access_type = "container"
blob_sharegate_msi = "ShareGate.24.6.0.msi"
blob_type = "Block"
The main.tf file is the core of the Terraform configuration, defining the main resources like the Azure resource group.
You can add or remove resources based on your infrastructure needs. Ensure that the tags and resource names align with your naming conventions.
The main.tf file is the backbone of your Terraform setup, defining the primary resources and their configurations.
#####################################################################
# main.tf
#####################################################################
# Create Azure Resource Group
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}-rg"
location = var.location
# Common Tags
tags = local.common_tags
}
The network.tf file defines the virtual network (VNet) and its configurations.
Adjust the address space and tags to match your network design and tagging strategy.
Properly configuring the VNet is crucial for network segmentation and security.
#####################################################################
# network.tf
#####################################################################
# Create Virtual Network
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-vnet"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
address_space = var.vnet_address_space
# Common Tags
tags = local.common_tags
depends_on = [azurerm_resource_group.rg]
}
The subnets.tf file defines the subnets within the VNet.
Modify the subnet names and address prefixes as per your network architecture.
Subnets help in logically dividing the VNet into smaller, manageable segments.
#####################################################################
# subnets.tf
#####################################################################
# Create Subnet
resource "azurerm_subnet" "subnets" {
count = length(var.subnet_names)
name = var.subnet_names[count.index]
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.${count.index + 1}.0/24"]
depends_on = [
azurerm_resource_group.rg,
azurerm_virtual_network.vnet
]
}
The nsg.tf file defines the network security group (NSG), and the nsg-rules.tf file defines the security rules.
Adjust the rules and priorities based on your security requirements.
NSGs and their rules are vital for controlling inbound and outbound traffic to your Azure resources.
#####################################################################
# nsg.tf
#####################################################################
# Create Network Security Group
resource "azurerm_network_security_group" "nsg" {
name = "${var.prefix}-nsg"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
# Common Tags
tags = local.common_tags
depends_on = [azurerm_resource_group.rg]
}
# Associate Network Security Group to Subnet
resource "azurerm_subnet_network_security_group_association" "nsg-link" {
subnet_id = azurerm_subnet.subnets[0].id
network_security_group_id = azurerm_network_security_group.nsg.id
depends_on = [
azurerm_virtual_network.vnet,
azurerm_network_security_group.nsg
]
}
#####################################################################
# nsg-rules.tf
#####################################################################
# Create Network Security Group Rules using for_each
resource "azurerm_network_security_rule" "nsgrules" {
for_each = local.nsgrules
name = each.key
direction = each.value.direction
access = each.value.access
priority = each.value.priority
protocol = each.value.protocol
source_port_range = each.value.source_port_range
destination_port_range = each.value.destination_port_range
source_address_prefix = each.value.source_address_prefix
destination_address_prefix = each.value.destination_address_prefix
resource_group_name = azurerm_resource_group.rg.name
network_security_group_name = azurerm_network_security_group.nsg.name
}
The storage.tf file defines storage accounts for diagnostics and ShareGate software.
Adjust the storage account names, types, and configurations based on your storage needs.
Configuring storage accounts is essential for diagnostics and storing necessary software like ShareGate.
#####################################################################
# storage.tf
#####################################################################
# Create storage account for boot diagnostics
resource "azurerm_storage_account" "stgacct" {
name = "diag${random_id.random_id.hex}"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
account_tier = var.stg_account_tier
account_replication_type = var.stg_account_replication_type
}
# Create storage for ShareGate Software
resource "azurerm_storage_account" "stgacct2" {
name = "soft${random_id.random_id.hex}"
resource_group_name = azurerm_resource_group.rg.name
location = var.location
account_tier = var.stg_account_tier
account_replication_type = var.stg_account_replication_type
}
resource "azurerm_storage_container" "container" {
name = var.storage_container_name
storage_account_name = azurerm_storage_account.stgacct2.name
container_access_type = var.container_access_type
}
resource "azurerm_storage_blob" "sharegate_msi" {
name = var.blob_sharegate_msi
storage_account_name = azurerm_storage_account.stgacct2.name
storage_container_name = azurerm_storage_container.container.name
type = var.blob_type
source = var.blob_sharegate_msi
}
The nic.tf file defines the network interface for the VM.
Ensure the network interface configurations match your network setup.
The network interface connects the VM to the VNet, enabling communication with other resource.
#####################################################################
# nic.tf
#####################################################################
# Create Network Interface
resource "azurerm_network_interface" "nic" {
name = "${var.prefix}-nic"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
# Common Tags
tags = local.common_tags
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.subnets[0].id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.publicip.id
}
depends_on = [
azurerm_subnet.subnets
]
}
The publicip.tf file defines the public IP address for the VM.
Adjust the allocation method and other properties based on your requirements.
A public IP address is necessary for accessing the VM from the internet.
#####################################################################
# publicip.tf
#####################################################################
resource "azurerm_public_ip" "publicip" {
name = "${var.prefix}-pubip"
resource_group_name = azurerm_resource_group.rg.name
location = var.location
allocation_method = var.allocation_method
sku = "Standard"
# Common Tags
tags = local.common_tags
depends_on = [azurerm_resource_group.rg]
}
The windowsvm.tf file defines the Windows virtual machine configuration.
Adjust the VM size, OS disk configurations, and image reference based on your needs.
Properly configuring the VM is essential for ensuring it meets your performance and functional requirements.
#####################################################################
# windowsvm.tf
#####################################################################
# Create Windows Virtual Machine
resource "azurerm_windows_virtual_machine" "vm" {
name = "${var.prefix}-vm"
computer_name = "${var.prefix}-vm"
resource_group_name = azurerm_resource_group.rg.name
location = var.location
size = var.vm_size
admin_username = var.admin_username
admin_password = var.admin_password
# Common Tags
tags = local.common_tags
network_interface_ids = [
azurerm_network_interface.nic.id,
]
os_disk {
caching = var.os_disk_caching
storage_account_type = var.os_disk_storage_account_type
name = "${var.prefix}-OsDisk"
}
source_image_reference {
publisher = var.src_img_ref_publisher
offer = var.src_img_ref_offer
sku = var.src_img_ref_sku
version = var.src_img_ref_version
}
depends_on = [
azurerm_network_interface.nic,
azurerm_resource_group.rg,
azurerm_storage_account.stgacct
]
boot_diagnostics {
storage_account_uri = azurerm_storage_account.stgacct.primary_blob_endpoint
}
}
The custom-ext.tf file defines the custom script extension for the VM to install ShareGate.
Adjust the script URL and command as necessary for your specific installation requirements.
The custom script extension automates the installation of ShareGate, saving time and ensuring consistency.
#####################################################################
# custom-ext.tf
#####################################################################
# Create Custom Extenstion
resource "azurerm_virtual_machine_extension" "vmextension" {
name = "vmextension"
virtual_machine_id = azurerm_windows_virtual_machine.vm.id
publisher = "Microsoft.Compute"
type = "CustomScriptExtension"
type_handler_version = "1.10"
settings = <<SETTINGS
{
"fileUris": ["https://${azurerm_storage_account.stgacct2.name}.blob.core.windows.net/software/ShareGate.24.6.0.msi"],
"commandToExecute": "msiexec /i ShareGate.24.6.0.msi /q SHAREGATEINSTALLSCOPE=PERMACHINE RESTARTEDASADMIN=1 ALLUSERS=1"
}
SETTINGS
}
The backend.tf file configures the remote backend for storing the Terraform state file.
Ensure the resource group name, storage account name, container name, and key match your backend storage setup.
Using a remote backend ensures your Terraform state is stored securely and can be accessed by team members.
#####################################################################
# backend.tf
#####################################################################
# Create Backend in Azure
terraform {
backend "azurerm" {
resource_group_name = "remotestate-rg"
storage_account_name = "azremotebackend"
container_name = "tfstate"
key = "path/terraform.tfstate"
}
}
After configuring all your Terraform files, you need to initialize and deploy your infrastructure using the following Terraform commands.
This command initializes the Terraform configuration, setting up the backend and downloading necessary provider plugins.
Open a PowerShell command window and change the directory to sgwindows folder were the terraform *.tf files are stored
This command formats your Terraform configuration files to ensure they follow standard coding conventions.
This command creates an execution plan, showing what actions Terraform will take to achieve the desired state.
This command applies the execution plan, deploying the resources defined in your Terraform configuration files.
3. Select the sgate-rg resource group.
4. Select the sgate-vnet virtual network resource.
5. In the left panel menu, scroll down to the Monitoring section and select Diagram.
6. The right panel will display a diagram of the solution deployed to Azure.
1. Next go to the virtual machines
2. Select the sgate-vm
3. In the Overview section, select Connect to establish an RDP session with the VM.
4. Log on to the VM and validate that ShareGate is on the desktop.
And there it is the ShareGate software is installed and ready to be activated with a valid license number.
When you no longer need the infrastructure, you can clean up by destroying the deployed resources using the following command.
By following these steps and understanding each Terraform file, you can efficiently deploy an Azure VM and automate the installation of ShareGate. This approach not only saves time but also ensures a consistent and repeatable setup, crucial for successful data migrations. Using Terraform's commands to initialize, format, plan, apply, and eventually destroy your infrastructure ensures you maintain control over your environment throughout its lifecycle.
Thanks for coming along with me on this terraform journey.