⏩ How to Speed Up Existing Azure Infrastructure Migration to Terraform? Discover our Time-Efficient Solution - Bid Farewell to Manual Configuration! 🚀
Terraform supports importing infrastructure into its state out of the box, but it’s up to the user to provide the proper configuration code for each resource that should be managed. Translating an existing Azure infrastructure into Terraform configuration can be challenging and laborious, not only for a beginner in Azure Cloud Services – sometimes it’s over a hundred resources that need to be imported!
Luckily, Azure provides a tool facilitating that effort – aztfexport – which significantly accelerated the import process in our case. Though there were some limitations of the tool to overcome, eventually the migration was completed with success 🎉
Legacy Infrastructure
The following diagram pictures the infrastructure we dealt with. It consists of several high-level Azure resources: 4 Function Apps, an App Service, a Service bus, a Cosmos DB, an Application Insights and a Key Vault.
As it turned out later, that infrastructure is represented by 102 Terraform resources, which is a significant number to process with the help of aztfexport tool, not to speak of approaching it manually.
Aztfexport limitations
The aztfexport tool generates configuration code along with a Terraform state file that reflects the prevailing state of the infrastructure so, in theory, it can be managed by Terraform right away. At the same time, it doesn’t aim at the reproducibility of the infrastructure. Reaching that reproducibility required additional adjustments to the outputted code.
The snippet below represents code generated for the Application Insights along with its alert rule, configured to track anomalies. It pictures some of the encountered issues that needed to be resolved. For the sake of the example, some sensitive values were replaced with dummy ones.
resource "azurerm_resource_group" "res-0" {
location = "northeurope"
name = "resource-group-name"
}
resource "azurerm_monitor_smart_detector_alert_rule" "res-219" {
detector_type = "FailureAnomaliesDetector"
frequency = "PT1M"
name = "alert-rule"
resource_group_name = "resource-group-name"
scope_resource_ids = ["id-of-res-220-application-insights-in-plain-text"]
severity = "Sev3"
action_group {
ids = ["action-group-resource-id"]
}
depends_on = [
azurerm_resource_group.res-0,
]
}
resource "azurerm_application_insights" "res-220" {
application_type = "web"
location = "northeurope"
name = "application-insights"
resource_group_name = "resource-group-name"
sampling_percentage = 0
workspace_id = "some-resource-id"
depends_on = [
azurerm_resource_group.res-0,
]
}
Adopted approach
- Generate a configuration for the desired resource group. It should be outputted into a separate directory and not pushed into a remote repository right away, as the output contains sensitive data and secrets.
- Recreate necessary dependencies and remove sensitive data. Pick a configuration of a high-level resource you want to track (like Function App), and then include the configuration of all resources it depends on (e.g. Storage Account, Service Plan). In the meantime, hide exposed sensitive data by using Terraform variables. This step often involves checking the Azure Portal to determine which resource property is being referenced. For example, given a connection string as plain text, decide whether it’s a database’s primary or secondary connection string.
- Organise connected resources into modules. For example, group resources of a single Function App into a module. Rename resources Within a separate module, the resource naming could be more straightforward and shorter compared to everything gathered in a single file, where you need to differentiate resources of several Function Apps.
- Manually import each resource into Terraform state with the terraform import command. This was the most laborious step. Luckily, aztfexport outputs a mapping of generated resource names and their ids, which speeds up the import process – while you need to figure out the new name, the id is already provided.
resource "azurerm_resource_group" "this" {
location = "northeurope"
name = "resource-group-name"
}
resource "azurerm_application_insights" "this" {
name = "application-insights"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
application_type = "web"
sampling_percentage = 0
workspace_id = "some-resource-id"
}
resource "azurerm_monitor_smart_detector_alert_rule" "this" {
name = "alert-rule"
resource_group_name = azurerm_resource_group.this.name
severity = "Sev3"
scope_resource_ids = [azurerm_application_insights.this.id]
frequency = "PT1M"
detector_type = "FailureAnomaliesDetector"
action_group {
ids = ["action-group-resource-id"]
}
}