CR: Nonprod Resource Group Restructure
Jira: HM-8556 Status: In Progress Scheduled: TBD — phases 0–4 can proceed without a maintenance window; phase 5 requires one Owner: Leon Geldenhuys Risk: MEDIUM (phases 0–4 are LOW; phase 5 is HIGH) | Duration: ~7–9 working days | Rollback: varies per phase
Summary
rg-eshars-nonprod has grown to 222 resources across 41 resource types and 5 Azure regions — a “junk drawer” that mixes VMs, SQL servers, networking, app services, key vaults, storage, monitoring, and unrelated sandbox resources. This makes RBAC too broad, cost reporting opaque, and deployments risky (any ARM deployment targets the whole group).
This change splits rg-eshars-nonprod into purpose-built resource groups following the Microsoft Cloud Adoption Framework pattern of grouping by workload tier + environment. Target structure aligns with the Landing Zone Architecture — shared networking lives in the hub RG (rg-eshars-core-np); there is no per-environment -network RG. Prod is out of scope for now; ad-hoc RGs (rg-eshars-nonprod-centralUS, etc.) are left in place.
Target Structure
| New Resource Group | Contents | ~Count |
|---|---|---|
rg-eshars-nonprod-data-cus | SQL servers + databases, elastic pools, SQL private endpoints, Data Factory | ~34 |
rg-eshars-nonprod-compute-cus | VMs, NICs, disks, VM extensions (application compute only) | ~62 |
rg-eshars-nonprod-ops-cus | Automation accounts, WAC gateway VMs (vm-ops-nonprod), hybrid workers, ops tooling | ~4 |
rg-eshars-nonprod-web-cus | App Service plans, web apps + slots, Function Apps + slots, App Insights, Log Analytics | ~39 |
rg-eshars-nonprod-security-cus | Key Vaults, managed identities, certificates, KV private endpoints | ~12 |
rg-eshars-nonprod-storage-cus | Storage accounts, blob private endpoints | ~12 |
rg-eshars-nonprod-monitoring-cus | Metric alerts, action groups, dashboards, autoscale settings | ~15 |
rg-eshars-nonprod-backup-cus | Recovery Services vaults | ~2 |
Scope
| Resource Group | Change | Phase |
|---|---|---|
rg-eshars-nonprod | Source — resources migrated out over phases 0–6 | All |
rg-eshars-nonprod-data-cus | New — receives SQL + storage-adjacent resources | 3 |
rg-eshars-nonprod-compute-cus | New — receives application VMs + disks | 6 |
rg-eshars-nonprod-ops-cus | New — receives automation accounts, WAC gateway VM, ops tooling | 1 |
rg-eshars-nonprod-web-cus | New — receives App Services + Functions + App Insights | 4 |
rg-eshars-nonprod-security-cus | New — receives Key Vaults + managed identities | 1 |
rg-eshars-nonprod-storage-cus | New — receives storage accounts | 2 |
rg-eshars-nonprod-monitoring-cus | New — receives alerts + dashboards | 1 |
rg-eshars-nonprod-backup-cus | New — receives Recovery Services vaults | 6 |
rg-eshars-nonprod-network-cus | Retire — migrate DNS zones to rg-eshars-core-np, then delete | 5 |
Resources requiring team decision before proceeding:
| Resource | Issue |
|---|---|
db-eshars-nonprod/db-nursing-prod, /ep-eshars-prod | Prod databases on a nonprod SQL server — clarify intent before Phase 3 |
eshars-prod-central-sp01, ASP-eshars-prod-CentralUs | Prod App Service plan in nonprod RG — move to rg-eshars-prod |
vm-tmhp-prod-2-nsg, vm-tmhp-prod-2151 | Prod VM resources in nonprod RG — move to rg-eshars-prod |
devops24, DevOps-24 | SQL + VM — confirm still in use or delete |
interviewapiacr, authentica-interview, wa-hellowrld-test, kv-flask-app-np | Non-eshars/sandbox resources — move to rg-authentica-sandbox or delete |
WindowsServer*UpgradeDisk*, vm-sftp-nonprod-001-temp-nic, stale snapshots | Orphaned — confirm and delete |
Prerequisites
- RBAC audit: who has role assignments scoped to
rg-eshars-nonprod? (These must be re-scoped to new RGs) - Terraform state strategy agreed:
terraform state mvvs. re-import after each phase - Team decisions on prod-in-nonprod resources (see table above)
- Maintenance window agreed for phases 5 and 7 (Bastion, VPN GW, App GW)
- All Azure DevOps pipeline service connections scoped to
rg-eshars-nonprodidentified - Azure portal access and
azCLI authenticated to correct subscription
Procedure
Pre-checks (all phases)
# Confirm current resource count before startingaz resource list --resource-group rg-eshars-nonprod \ --subscription 9639039c-86c6-4859-b014-d28688863cd5 \ --query "length(@)" -o tsv# Run prerequisite audit.\eshars-ops\develop\scripts\setup-az-prerequisites.ps1 -AuditPhase 0 — Cleanup (1–2 days, zero downtime)
Goal: Remove orphaned and misplaced resources. No moves, no file changes.
- Audit orphaned resources (temp NICs, upgrade disks, old snapshots):
Terminal window az resource list --resource-group rg-eshars-nonprod \--subscription 9639039c-86c6-4859-b014-d28688863cd5 \--query "[?contains(name,'temp') || contains(name,'Upgrade') || contains(name,'Snapshot')].{name:name,type:type}" -o table - Get team confirmation — review the list, mark resources safe to delete
- Delete confirmed orphans:
Terminal window az resource delete --ids <resource-id> --subscription 9639039c-86c6-4859-b014-d28688863cd5 - Move sandbox resources (
devops24,interviewapiacr,authentica-interview,wa-hellowrld-test,kv-flask-app-np) torg-authentica-sandboxor delete - Move confirmed prod resources (
vm-tmhp-prod-2*,eshars-prod-central-sp01,ASP-eshars-prod-CentralUs) torg-eshars-prod
Verification: Re-run resource count — number should be meaningfully lower.
Rollback: N/A — deletions of confirmed orphans are intentional. Prod-resource moves can be reversed with another az resource move.
Phase 1 — Monitoring & Security (1 day, zero downtime)
Goal: Move loosely coupled resources first — alerts and Key Vaults don’t affect runtime traffic.
-
Create target resource groups:
Terminal window az group create --name rg-eshars-nonprod-monitoring --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5az group create --name rg-eshars-nonprod-security --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5 -
Move metric alerts, smart detection rules, action groups, dashboards, autoscale settings:
Terminal window az resource move --destination-group rg-eshars-nonprod-monitoring \--ids <space-separated resource IDs> \--destination-subscription-id 9639039c-86c6-4859-b014-d28688863cd5 -
Move Key Vaults and managed identities to
rg-eshars-nonprod-security:⚠️ Managed identity resource IDs change — managed identities get a new resource ID when moved. Any App Service with this managed identity configured as its identity must be updated — go to App Service → Identity → User-assigned and re-select the identity after the move.
-
Verify KV URIs are unchanged (apps reference KV by DNS name, not RG):
Terminal window az keyvault show --name kv-eshars-mobile-keys --query "properties.vaultUri" -o tsv
Files to update: eshars-ops/develop/infrastructure/core/envs/nonprod.tfvars if KV resource groups are parameterized.
Verification:
- Alert rules appear in
rg-eshars-nonprod-monitoring - Key Vaults appear in
rg-eshars-nonprod-security - App Services can still read secrets from moved Key Vaults (check App Service logs / App Insights for 401/403)
- Managed identity still assigned on App Services that use it
Rollback: Move resources back to rg-eshars-nonprod. KV URI is DNS-based — no app config changes needed.
Phase 1b — Ops Tooling (0.5 day, zero downtime)
Goal: Create rg-eshars-nonprod-ops and move automation accounts + WAC gateway VM into it.
Run alongside or immediately after Phase 1 (same risk tier — no runtime traffic impact).
Note:
vm-ops-nonprodis moved here (not torg-eshars-nonprod-compute) because it is management-plane infrastructure (WAC gateway), not application compute.
-
Create target resource group:
Terminal window az group create --name rg-eshars-nonprod-ops --location centralus \--subscription 9639039c-86c6-4859-b014-d28688863cd5 -
Create
aa-eshars-nonprodautomation account in the new RG:Terminal window az automation account create \--name aa-eshars-nonprod \--resource-group rg-eshars-nonprod-ops \--location centralus \--assign-identity SystemAssigned -
Create nonprod variants of the cleanup automation accounts:
JobLogsCleanupandPeriodicCleanupcurrently live inrg-eshars-prodand contain both_PRODand_QA/_Regression/_Training/_UATrunbooks. We split them rather than move — prod runbooks stay in the prod accounts, nonprod runbooks go to new accounts.Terminal window az automation account create \--name JobLogsCleanup-Nonprod \--resource-group rg-eshars-nonprod-ops \--location centralus \--assign-identity SystemAssignedaz automation account create \--name PeriodicCleanup-Nonprod \--resource-group rg-eshars-nonprod-ops \--location centralus \--assign-identity SystemAssignedThen upload nonprod runbooks from git (the
deploy-runbooks.ymlpipeline handles this automatically on push — directoriesJobLogsCleanup-Nonprod/andPeriodicCleanup-Nonprod/map to the new accounts inrg-eshars-nonprod-ops):Terminal window # Or manually:for rb in CleanRecords_QA CleanRecords_Regression CleanRecords_Training CleanRecords_UAT; dofor account in JobLogsCleanup-Nonprod PeriodicCleanup-Nonprod; dosrc="JobLogsCleanup-Nonprod"[[ "$account" == "PeriodicCleanup-Nonprod" ]] && src="PeriodicCleanup-Nonprod"az automation runbook create -g rg-eshars-nonprod-ops \--automation-account-name "$account" --name "$rb" --type PowerShell72az automation runbook replace-content -g rg-eshars-nonprod-ops \--automation-account-name "$account" --name "$rb" \--content @"scripts/azure-runbooks/$src/$rb.ps1"az automation runbook publish -g rg-eshars-nonprod-ops \--automation-account-name "$account" --name "$rb"donedoneFinally, remove the nonprod runbooks from the prod accounts (they now live in the nonprod accounts):
Terminal window for rb in CleanRecords_QA CleanRecords_Regression CleanRecords_Training CleanRecords_UAT; doaz automation runbook delete -g rg-eshars-prod --automation-account-name JobLogsCleanup --name "$rb" -yaz automation runbook delete -g rg-eshars-prod --automation-account-name PeriodicCleanup --name "$rb" -ydone -
Move
vm-ops-nonprod(WAC gateway VM) along with its NIC and OS disk:Terminal window az vm show --name vm-ops-nonprod --resource-group rg-eshars-nonprod \--query "{vmId:id, osDiskId:storageProfile.osDisk.managedDisk.id, nicIds:networkProfile.networkInterfaces[].id}" -o jsonaz resource move --destination-group rg-eshars-nonprod-ops \--ids <vm-id> <nic-id> <os-disk-id> \--destination-subscription-id 9639039c-86c6-4859-b014-d28688863cd5 -
Update
deploy-runbooks.ymlACCOUNT_RG_MAP to pointJobLogsCleanupandPeriodicCleanupatrg-eshars-nonprod-ops(see Files to update below).
Files to update:
.github/workflows/deploy-runbooks.yml— add per-account RG map, addaa-eshars-nonprodentryscripts/azure-runbooks/docs/jit-vm-access.md— update RG references
Verification:
-
az automation account show -g rg-eshars-nonprod-ops -n JobLogsCleanupreturns account -
az automation account show -g rg-eshars-nonprod-ops -n PeriodicCleanupreturns account -
az automation account show -g rg-eshars-nonprod-ops -n aa-eshars-nonprodreturns account -
vm-ops-nonprodaccessible via Bastion after move - Existing runbook schedules still firing (check job history next day)
-
deploy-runbooks.ymlpipeline passes on a test push
Rollback: Move accounts back to rg-eshars-nonprod with another az resource move.
Phase 2 — Storage (0.5 day, zero downtime)
Goal: Move storage accounts. Connection strings don’t include RG names so no app reconfiguration needed.
-
Create target resource group:
Terminal window az group create --name rg-eshars-nonprod-storage --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5 -
Move storage accounts + blob private endpoints:
Terminal window az resource move --destination-group rg-eshars-nonprod-storage \--ids <storage account resource IDs> \--destination-subscription-id 9639039c-86c6-4859-b014-d28688863cd5
Verification:
- Storage accounts appear in
rg-eshars-nonprod-storage - Function App storage bindings still work (
eshars-functions-nonprod) - Diagnostic settings still flow to diagnostic storage accounts
- No storage-related errors in App Insights
Rollback: Move storage accounts back to rg-eshars-nonprod.
Phase 3 — Data Tier (1 day, ~30s read-only lock per SQL server)
Goal: Move SQL servers and databases. Apps connect by FQDN — no connection string changes needed.
-
Create target resource group:
Terminal window az group create --name rg-eshars-nonprod-data --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5 -
Move one SQL server at a time (moving a server moves all its databases automatically):
Terminal window # Get the SQL server resource IDaz sql server show --name db-eshars-nonprod --resource-group rg-eshars-nonprod \--subscription 9639039c-86c6-4859-b014-d28688863cd5 --query id -o tsvaz resource move --destination-group rg-eshars-nonprod-data \--ids <sql-server-resource-id> \--destination-subscription-id 9639039c-86c6-4859-b014-d28688863cd5 -
Repeat for remaining SQL servers (
db-eshars-nonprod-west,db-eshars-developeraccess,sqlvaxaodxhnlxdnju) -
Move SQL private endpoints to
rg-eshars-nonprod-data -
Move Data Factory (
qaesharsdatafactory) -
Update Azure Runbooks — each runbook references
rg-eshars-nonprodfor SQL operations:eshars-ops/develop/scripts/azure-runbooks/aa-eshars-prod/— 12+ files — updateResourceGroupNameparameters
-
Update
eshars-ops/develop/pipelines/environments/*.yml— update deployment target resource group -
Update
eshars-ops/develop/infrastructure/core/envs/nonprod.tfvars
Verification:
- SQL servers appear in
rg-eshars-nonprod-data - All apps connect to databases (dev, qa, uat, trn, regression)
- Azure Automation Runbooks execute successfully (test a DB copy job)
- Azure DevOps pipeline deploys to at least one environment
Rollback: Move SQL servers back to rg-eshars-nonprod. Revert runbook and pipeline file changes.
Phase 4 — Web Tier (1 day, zero downtime)
Goal: Move App Services and Functions. Apps keep running during the move.
-
Create target resource group:
Terminal window az group create --name rg-eshars-nonprod-web --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5 -
Move App Service plans, web apps + slots, Function Apps + slots, App Insights:
Terminal window az resource move --destination-group rg-eshars-nonprod-web \--ids <space-separated resource IDs> \--destination-subscription-id 9639039c-86c6-4859-b014-d28688863cd5Note: move the App Service plan first — web apps in a plan must move with it.
-
Update deployment pipelines:
eshars-ops/develop/pipelines/environments/dev.ymleshars-ops/develop/pipelines/environments/qa.ymleshars-ops/develop/pipelines/environments/uat.ymleshars-ops/develop/pipelines/environments/training.ymleshars-ops/develop/pipelines/environments/regression.ymleshars-ops/develop/pipelines/hisd-release.yml
Verification:
- All environment URLs respond: dev, qa, uat, trn, regression
- App Insights telemetry flowing for all environments
- Azure DevOps pipeline deploys to all environments successfully
Rollback: Move App Service plans + apps back to rg-eshars-nonprod. Revert pipeline file changes.
Phase 5 — Networking cleanup (1 day, maintenance window for Bastion deploy)
Goal: Retire rg-eshars-nonprod-network-cus (DNS-only legacy RG), link the hub’s SQL DNS zone, delete orphaned VNet, and deploy Bastion in the hub.
Step 1 — Fix orphaned SQL DNS zone (zero downtime)
The hub’s privatelink.database.windows.net zone has records but no VNet links — nothing resolves via it today.
# Link to hub VNetaz network private-dns link vnet create \ --resource-group rg-eshars-core-np \ --zone-name privatelink.database.windows.net \ --name link-to-hub-vnet \ --virtual-network vnet-eshars-core-np \ --registration-enabled false
# Link to legacy spoke VNet (so spoke resources also resolve via hub zone)az network private-dns link vnet create \ --resource-group rg-eshars-core-np \ --zone-name privatelink.database.windows.net \ --name link-to-np-spoke \ --virtual-network /subscriptions/9639039c-86c6-4859-b014-d28688863cd5/resourceGroups/rg-eshars-nonprod/providers/Microsoft.Network/virtualNetworks/rg-eshars-nonprod \ --registration-enabled falseStep 2 — Migrate DNS records from legacy RG (zero downtime)
rg-eshars-nonprod-network-cus holds 2 zones linked to legacy eastus2 VNets. Migrate their A records to the hub zones, then delete.
# List records to migrateaz network private-dns record-set a list \ --resource-group rg-eshars-nonprod-network-cus \ --zone-name privatelink.database.windows.net \ --query "[].{name:name, ip:aRecords[0].ipv4Address}" -o table
# For each record, add it to the hub zone (verify no duplicate first)az network private-dns record-set a add-record \ --resource-group rg-eshars-core-np \ --zone-name privatelink.database.windows.net \ --record-set-name <name> \ --ipv4-address <ip>
# After verifying all records migrated:az network private-dns zone delete \ --resource-group rg-eshars-nonprod-network-cus \ --name privatelink.database.windows.net --yes
# Repeat for privatelink.blob.core.windows.net (create zone in hub RG first if needed)
# Delete the now-empty RGaz group delete --name rg-eshars-nonprod-network-cus --yesStep 3 — Delete orphaned VNet (zero downtime)
rg-eshars-nonprod-vnet (10.5.0.0/24, southcentralus) is in the wrong region, has no peerings, no resources, no delegation. Safe to delete.
az network vnet delete \ --resource-group rg-eshars-nonprod \ --name rg-eshars-nonprod-vnetStep 4 — Deploy Bastion in hub (maintenance window: ~5 min)
The hub’s AzureBastionSubnet is already sized (10.20.5.0/26). Deploy a Basic or Standard Bastion host there. Decommission the legacy Bastion per 2026-04-17 Nonprod Bastion Decommission.
# Create public IP for Bastionaz network public-ip create \ --resource-group rg-eshars-core-np \ --name pip-bastion-eshars-core-np \ --sku Standard \ --location centralus
# Deploy Bastion hostaz network bastion create \ --resource-group rg-eshars-core-np \ --name bastion-eshars-core-np \ --vnet-name vnet-eshars-core-np \ --public-ip-address pip-bastion-eshars-core-np \ --location centralus \ --sku StandardUpdate scripts to reference new Bastion RG:
eshars-ops/develop/scripts/bastion/bastion-rdp-nonprod.ps1—$BastionRG = "rg-eshars-core-np",$BastionName = "bastion-eshars-core-np"eshars-ops/develop/scripts/bastion/bastion-rdp-nonprod.sheshars-ops/develop/scripts/setup-az-prerequisites.ps1—$BastionRG
Verification:
-
nslookup sql-eshars-nonprod-cus.database.windows.netfrom within VNet resolves to private IP (not public) -
rg-eshars-nonprod-network-cusRG no longer exists -
rg-eshars-nonprod-vnetVNet no longer exists -
bastion-rdp-nonprod.ps1 -Listshows all VMs - RDP tunnel to
vm-ops-nonprodsucceeds via hub Bastion -
setup-az-prerequisites.ps1 -Auditpasses all checks
Rollback: DNS zone migration — re-link the old rg-eshars-nonprod-network-cus zones (recreate VNet links). Bastion — if hub Bastion fails, deploy a Basic SKU in the legacy RG temporarily.
Phase 6 — Compute (1 day, zero downtime)
Goal: Move VMs. Azure supports live cross-RG VM moves — VMs keep running.
-
Create target resource groups:
Terminal window az group create --name rg-eshars-nonprod-compute --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5az group create --name rg-eshars-nonprod-backup --location centralus --subscription 9639039c-86c6-4859-b014-d28688863cd5 -
Move each VM along with its NIC, OS disk, data disks, and extensions (Azure requires all associated resources in the same move operation):
Terminal window # Get VM ID, OS disk ID, managed data disk IDs, and NIC IDsaz vm show --name vm-ops-nonprod --resource-group rg-eshars-nonprod \--subscription 9639039c-86c6-4859-b014-d28688863cd5 \--query "{vmId:id, osDiskId:storageProfile.osDisk.managedDisk.id, dataDiskIds:storageProfile.dataDisks[].managedDisk.id, nicIds:networkProfile.networkInterfaces[].id}" -o json# Get VM extension resource IDs (must be included in the same move)az vm extension list --vm-name vm-ops-nonprod --resource-group rg-eshars-nonprod \--subscription 9639039c-86c6-4859-b014-d28688863cd5 \--query "[].id" -o jsonaz resource move --destination-group rg-eshars-nonprod-compute \--ids <vm-id> <nic-ids> <os-disk-id> <data-disk-ids> <extension-ids> \--destination-subscription-id 9639039c-86c6-4859-b014-d28688863cd5 -
Repeat for each application VM:
vm-sql16-np,vm-cleanup-jobs,vm-jobscheduler-np-001,vm-jobscheduler,vm-db-api,vm-successed-nonprodNote:vm-ops-nonprodwas already moved torg-eshars-nonprod-opsin Phase 1b. -
Move Recovery Services vaults to
rg-eshars-nonprod-backup -
Update VM RG values in bastion scripts:
eshars-ops/develop/scripts/bastion/bastion-rdp-nonprod.ps1— update eachRG =value in the$VMshash to"rg-eshars-nonprod-compute"eshars-ops/develop/scripts/bastion/bastion-rdp-nonprod.sheshars-ops/develop/scripts/setup-az-prerequisites.ps1— updatenonprodVMsRG values
Verification:
- VMs running and accessible via Bastion (
bastion-rdp-nonprod.ps1 vm-ops-nonprod) -
setup-az-prerequisites.ps1 -AuditVNet alignment checks all pass - Backup status shows healthy in Recovery Services vaults
- Azure Monitor agents (AMA, Dependency Agent) still reporting
Rollback: Move VMs back to rg-eshars-nonprod with another az resource move. Revert bastion script changes.
Files Updated Across All Phases
| File | Phases | Change |
|---|---|---|
eshars-ops/develop/infrastructure/core/envs/nonprod.tfvars | 1–6 | RG name references |
eshars-ops/develop/pipelines/environments/dev.yml | 3, 4 | Deployment target RG |
eshars-ops/develop/pipelines/environments/qa.yml | 3, 4 | Deployment target RG |
eshars-ops/develop/pipelines/environments/uat.yml | 3, 4 | Deployment target RG |
eshars-ops/develop/pipelines/environments/training.yml | 3, 4 | Deployment target RG |
eshars-ops/develop/pipelines/environments/regression.yml | 3, 4 | Deployment target RG |
eshars-ops/develop/pipelines/hisd-release.yml | 4 | Deployment target RG |
eshars-ops/develop/scripts/bastion/bastion-rdp-nonprod.ps1 | 5, 6 | $BastionRG = "rg-eshars-core-np" + VM $RG values |
eshars-ops/develop/scripts/bastion/bastion-rdp-nonprod.sh | 5, 6 | Bastion RG + VM RG values |
eshars-ops/develop/scripts/setup-az-prerequisites.ps1 | 5, 6 | $BastionRG + nonprodVMs RG values |
eshars-ops/develop/scripts/azure-runbooks/ (12+ files) | 3 | SQL server RG references |
eshars-docs/develop/ops/docs/platform/infrastructure/architecture-shared-core.md | 5 | Landing Zone Architecture — updated to reflect end-state |
Verification (End-to-End)
Run after all phases are complete:
-
az resource list --resource-group rg-eshars-nonprod— should be near-empty (only unclassified leftovers) -
az group show --name rg-eshars-nonprod-network-cus— should return 404 (deleted) - All eshars environments respond: dev, qa, uat, trn, regression
-
nslookup sql-eshars-nonprod-cus.database.windows.netfrom within VNet resolves to private IP -
bastion-rdp-nonprod.ps1 -Listshows all VMs with correct ports -
setup-az-prerequisites.ps1 -Auditpasses all checks - Azure DevOps pipelines deploy to all environments
- Azure Automation Runbooks execute (DB copy/scrub test)
-
terraform planshows no unexpected drift (update state as needed) - Azure Cost Management shows resources attributed to new RGs
Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Hub Bastion deploy fails | Low | Low — fall back to legacy Bastion temporarily | Deploy Basic SKU first; Standard SKU only after verifying |
| DNS zone migration causes resolution gap | Low | Medium — brief private endpoint downtime during cutover | Keep old zone VNet links until hub zone is verified working; remove old zone only after |
| VPN Gateway transit broken during peering change | Low | Medium — VPN tunnel down | VPN GW and peering are already configured; Phase 5 only adds DNS links, not peering changes |
| SQL read-only lock causes app errors | Low | Low — ~30s transient | Off-hours scheduling; brief blip, not an outage |
| Managed identity re-assignment missed | Low | Medium — KV access broken | Verify each App Service identity binding after Phase 1 |
| Azure DevOps pipeline deploys to wrong RG | Medium | Low — deployment fails, no data loss | Update pipeline files before first post-Phase-4 pipeline run |
| Terraform drift after moves | High | Low — plan/apply mismatch | Agree on state strategy (mv vs. import) before starting |
Outcome
Phase 0 Completed: — Phase 1 Completed: — Phase 2 Completed: — Phase 3 Completed: — Phase 4 Completed: — Phase 5 Completed: — Phase 6 Completed: — Phase 7 Completed: — Final State: — Notes: —