Migrating from an Older Platform Version¶
This page is for a platform team that takes over a tenant which runs an older generation of the platform, or that has spokes outside the platform. It covers three migrations:
| Migration | Use it when | Section |
|---|---|---|
| Bicep-based spoke to the Terraform spoke canvas | A workload subscription was built with an earlier, bicep-based landing zone and must become a spoke of this platform, without rebuilding it | Bicep spoke to Terraform |
| Mono-repository hub to the layered hub | The hub is from before the layered deployment (before v4.3.0) | Mono-repository hub to layers |
| Older platform version to the current one | The hub is layered but runs an older release or the older pipelines | Older version to the current version |
For an ordinary upgrade between current versions, use What to do when a new version is released. To move a workload to a spoke, see Migrate a workload to the platform.
Principles for every migration¶
- Make the configuration describe what is in Azure, not what you would like. The aim of the first run is a plan that imports what exists and changes as little as possible.
- Read every plan. Look for destroys and forced replacements that should not happen, and for resources that Terraform wants to create although they already exist (they must be imported).
- Tell the owner first. Agree a time slot with the contact person of the affected workload or hub before you apply. Some migrations cause a short disruption (for example reconnecting a network).
- Work on a branch and in a copy of the state where you can. Keep the old definition until the new one has been applied and checked, then remove the old one in a second pull request.
- Have a second person review the pull request and the plan.
- Return to the normal pin. Migrations often use a temporary code version (an import tag or branch). After the apply, move the entry pipeline back to the release you want to run.
Bicep spoke to Terraform spoke¶
Before you start¶
- The correct subscription ID of the spoke.
- A contact person (the workload's product owner) who gets a heads-up before you apply.
- The spoke's resource group, region and environment, and the name of its Virtual WAN connection (search for the virtual network's name in the connection list of the virtual hub).
1. Prepare the migration file¶
The documentation set contains a helper PowerShell script, prepareMigrationFile.ps1, which reads the existing spoke
with the Azure CLI and writes a spoke JSON file in the format of the spoke canvas. Sign in to the subscription that holds
the spoke first. It reads, among others, the subscription, the virtual network with its address space, DNS servers and
subnets, the route tables and network security groups, the Log Analytics workspace, the Key Vault and the tags, and takes
these inputs:
| Parameter | Meaning |
|---|---|
subscriptionId |
The subscription that holds the spoke |
resourceGroupName |
The resource group of the existing resources |
region |
The short region name, for example weu |
environment |
The spoke environment, for example nonprod |
outputFileName |
The name of the JSON file to write |
workloadShortName |
The workload short name for the spoke canvas |
virtualWanConnectionName |
The name of the existing Virtual WAN connection |
The result contains most parameters. You still fill in the tags and adInfo blocks by hand, and you check everything
against Azure. Treat the script as an accelerator, not as the source of truth, and check its output against the
spoke JSON reference. If your team does not have the script, create the file by
hand from the portal and the bicep parameters.
2. Create the spoke file¶
- Create a branch in the configuration repository.
- Create the spoke file in
<env-folder>/spokes/<team>/, starting from an existing spoke file or from the prepared one. - Make the file match the actual situation in Azure: names, address ranges, subnets, delegations, route tables and NSG rules.
- Point the pipeline at the code version that contains the import definitions for the spoke canvas (a dedicated import branch or tag in older versions, and the release that contains them in newer ones). Ask the product team if you are unsure which one.
3. Run the plan¶
Run the spoke pipeline up to the plan (spokeGlob set to this one file; Plan mode) and read it:
- Resources that would be destroyed or replaced that should not be.
- Resources that would be created although they exist, and so must be imported.
Terraform links existing resources to its configuration with import blocks in the spoke canvas code. They look like
this (the spoke canvas loops over the virtual networks):
import {
for_each = local.allVnets
to = module.virtualnetwork["${each.key}"].azurerm_virtual_network.resVirtualNetwork[0]
id = "/subscriptions/${local.jsonfile.azureSubscriptionId}/resourceGroups/${local.namingConventionSet.ResourceGroup}/providers/Microsoft.Network/virtualNetworks/${each.key}"
}
If the plan is wrong, find out whether the import block (a change for the product team) or your spoke file is wrong. Fix it, plan again, and repeat until the plan is what you expect.
4. Apply¶
- Give the contact person a heads-up.
- Run the apply phase. If it fails, troubleshoot with the log and ask a colleague or the product team when the cause is not clear.
- Run the pipeline once more and check that it plans no changes.
- Tell the contact person that the spoke is migrated.
- Put the pipeline back on the release you normally pin, and open a pull request for a second person to review.
5. Remove the old definition¶
In the repository of the old landing zone, remove the spoke definition files that you migrated (pull request again), so that the old pipelines do not manage the same spoke.
Example: moving existing Terraform state¶
In one migration the spoke already had Terraform state from an earlier generation of the spoke repository. If your situation is similar, these steps are a starting point, not a recipe:
- Create the new spoke file from the old one.
- Copy the state file to the spoke state container and rename it to the pattern of the new layout
(
spoke_<workload>_<environment>.tfstate, see Naming and state). A wrong name means the plan shows that no resources exist. - Run the plan to find the resource addresses you need.
- Add
movedblocks (andimportblocks where needed) for resources that changed address, and disassociate network security groups that the new canvas replaces. - Run the pipeline and read the changes. Key Vault, storage account, virtual network and subnets must not be replaced. Expect some role assignments, NSGs, IP group associations and Entra groups of the old layout to be replaced or removed. A storage account network rule may show as a change on every run. Check whether that is still the case in your version.
- Run again until the plan is clean. Then comment out or remove the
movedandimportblocks, but only after the last migration that needs them. - Disable the old spoke file (for example by renaming it with a
.disabledsuffix) and open the pull requests.
Mono-repository hub to layers¶
Hubs from before v4.3.0 were deployed from a single repository without layers. Releases around v4.4 introduced an import
tag that imports the existing resources into the layered state when you move over. At the time of writing the tag was
v4.4.1-import; confirm the current name in the upgrade guide of your target version.
- Create a branch in the customer configuration repository.
- In the entry pipeline, change the code reference to the import tag.
- Follow the upgrade guide of the first layered version (the v4.4.0 guide): it names configuration files to remove from the configuration repository and changes to make.
-
Remove the existing custom policy definitions for alerts at the platform management group, so they are recreated by the new layers. In PowerShell, with the platform management group as scope:
Get-AzPolicyDefinition -ManagementGroupName "<platform management group>" -Custom | Where-Object { $_.Name -like "Deploy_*_Alert" } | Remove-AzPolicyDefinition Get-AzPolicyDefinition -ManagementGroupName "<platform management group>" -Custom | Where-Object { $_.Name -like "Deploy_activitylog*" } | Remove-AzPolicyDefinition Get-AzPolicyDefinition -ManagementGroupName "<platform management group>" -Custom | Where-Object { $_.Name -eq "Deploy_AlertProcessing_Rule" } | Remove-AzPolicyDefinition -
Run the pipeline for the import tag, layer by layer, in this order, with a plan before every apply, and read the plan:
- management
- firewall
- identity
- postdeployment
- foundation
- connectivity (a short disruption is expected: agree a time slot with the customer)
- Change the code reference to the first normal release of the layered generation (for example
v4.4.1), run it for the environment and check that everything still works. - Continue to the current version with the cumulative upgrade guides (next section).
Older version to the current version¶
Moving from an older layered release to the current one is a normal upgrade, with a few structural differences that the upgrade guides cover one release at a time. Plan time for these:
| Change over the generations | What to do |
|---|---|
One main.yaml pipeline, layers run one at a time, with Environment, Region and Layer parameters |
Replace with the entry pipelines hub, spoke, infrastructure, container and their destroy counterparts (Pipelines) |
| Settings spread over pipeline variables, a variable group and secrets | Put them in mcp.json (Config) |
| Four repositories (platform and spoke, code and config) | Two repositories (Repo setup) |
| Agents on a virtual machine scale set, built from an image gallery | Managed DevOps Pool and Container Apps jobs, deployed by infrastructure (Prepare agents) |
The ghrunners layer for GitHub |
Runners from infrastructure, registered by a GitHub App (GitHub variant) |
Spoke files in spokes/tfspokes/<workload>/ of a separate spoke repository |
<env-folder>/spokes/<team>/ in the configuration repository (Spoke config) |
Procedure:
- Record the current version and read every upgrade guide up to the target version, oldest first (Upgrade steps).
- Do the version-specific clean-up steps of each guide. Some require removing old policy definitions or assignments in the portal first.
- Do the structural changes in the table on a branch: create
mcp.json, create the new entry pipelines, runinfrastructureandcontainer, move the spoke files. - Plan the hub in canary and read it. Moving the configuration must plan no destroys for existing hub resources. If state keys or paths differ, stop and ask the product team before you apply.
- Apply to canary, then to prod, as in Deploy the platform.
- Plan one spoke per kind of spoke and read the result (Reading the spoke plans).
- Decommission what the new generation replaces: old agents, old pipelines, old repositories.
Tell the workload teams
A migration can touch the network (connectivity layer) and the identity objects of spokes. Announce it like any other maintenance (Maintenance calendar).