- Jul 26
- 29 min read
Azure Site Recovery Deep Dive
Replicate Azure VMs and Perform a Regional Failover

Azure Site Recovery provides managed replication, recovery-point management, test failover, regional failover, commit, reverse replication, and failback orchestration for protected workloads.
In this workshop, you will build an Azure-to-Azure disaster recovery environment using PowerShell 7 and the Az PowerShell modules.
You will:
create source and recovery networks in two Azure regions;
deploy a Linux workload VM with an operating-system disk and a data disk;
create a Recovery Services vault in the recovery region;
configure Azure-to-Azure replication;
create replication policies and mappings;
monitor initial synchronization;
run an isolated test failover;
validate the recovered application and replicated data;
clean up the test failover;
perform a real regional failover;
commit the failover;
prepare reverse replication and failback;
remove all workshop resources.
For Azure-to-Azure replication, the Recovery Services vault should be separate from the source VM region. Placing the vault in the intended recovery region keeps Site Recovery management operations available during a source-region outage.
Cost warningThis workshop creates virtual machines, managed disks, storage accounts, public IP addresses, replicated disks, recovery points, and a Recovery Services vault.Initial replication, reprotection, failover, and cleanup can take a significant amount of time. Delete the complete environment when the workshop is finished.
Workshop overview
Item | Configuration |
Level | Intermediate to advanced |
Estimated duration | 2–4 hours |
Deployment interface | PowerShell 7 |
Source region | West Europe |
Recovery region | North Europe |
Source workload | Ubuntu Linux VM |
Protected disks | OS disk and data disk |
Replication | Azure-to-Azure |
Recovery-point retention | 24 hours |
App-consistent snapshots | Every 4 hours |
Test failover | Isolated recovery network |
Regional failover | West Europe to North Europe |
Failback | Reprotection and reverse-failover planning |
What you will learn
By completing this workshop, you will learn how to:
Design a two-region Site Recovery topology.
Place the Recovery Services vault correctly.
Create source, recovery, and isolated test networks.
Deploy a stateful test workload.
Create Site Recovery fabrics.
Create protection containers.
Create a replication policy.
Create forward and reverse protection mappings.
Create forward and reverse network mappings.
Configure replication for managed disks.
Monitor initial replication.
Validate replication health.
Run a test failover without interrupting production.
Detect and validate the test VM.
Verify replicated application and data-disk content.
Clean up a test failover.
Select a recovery point.
Perform a regional failover.
Validate the recovered workload.
Commit the failover.
Plan reverse replication and failback.
Troubleshoot replication, networking, and failover.
Delete the complete environment.
Architecture
Azure Site Recovery
Recovery Services Vault
North Europe
│
Replication policy and mappings
│
┌────────────────────┴────────────────────┐
│ │
│ │
┌─────────▼────────────────┐ ┌───────────▼────────────────┐
│ Source region │ │ Recovery region │
│ West Europe │ │ North Europe │
│ │ │ │
│ Source resource group │ │ Recovery resource group │
│ │ │ │
│ VNet │ │ Recovery VNet │
│ 10.10.0.0/16 │ │ 10.20.0.0/16 │
│ │ │ │
│ snet-workload │ │ snet-workload │
│ 10.10.1.0/24 │ │ 10.20.1.0/24 │
│ │ │ │
│ ┌─────────────────────┐ │ │ Replicated VM is created │
│ │ Source VM │ │ │ here during real failover │
│ │ vm-asr-workload │ │ │ │
│ │ │ │ │ ┌─────────────────────┐ │
│ │ OS disk │─┼───────────►│ │ Recovered VM │ │
│ │ Data disk │─┼───────────►│ │ OS disk replica │ │
│ │ Nginx :8080 │ │ │ │ Data disk replica │ │
│ └─────────────────────┘ │ │ └─────────────────────┘ │
│ │ │ │
│ Source cache storage │ │ Recovery cache storage │
└─────────────────────────┘ └───────────────────────────┘
│
│ Test failover
▼
┌────────────────────────────┐
│ Isolated test VNet │
│ North Europe │
│ │
│ 10.20.0.0/16 │
│ snet-workload │
│ 10.20.1.0/24 │
│ │
│ Temporary test VM │
│ Deleted during cleanup │
└────────────────────────────┘
Address plan
Component | Address range |
Source VNet | 10.10.0.0/16 |
Source workload subnet | 10.10.1.0/24 |
Source VM private IP | 10.10.1.4 |
Recovery VNet | 10.20.0.0/16 |
Recovery workload subnet | 10.20.1.0/24 |
Isolated test VNet | 10.20.0.0/16 |
Isolated test subnet | 10.20.1.0/24 |
The recovery and test VNets deliberately use the same address space.
This is valid because the test VNet remains isolated and is never peered with the recovery VNet.
A test-failover network should be isolated from production and should use matching subnet names. Site Recovery attempts to place the test VM into a subnet with the same name and preserve its configured IP address when that address is available. Test failover does not interrupt ongoing replication or the source production VM.
Recovery workflow
Deploy source VM
│
▼
Enable replication
│
▼
Initial replication
│
▼
ProtectionState = Protected
│
▼
Run isolated test failover
│
▼
Validate application and data
│
▼
Clean up test failover
│
▼
Stop source VM
│
▼
Run regional failover
│
▼
Validate recovery VM
│
▼
Commit failover
│
▼
Reprotect toward source region
│
▼
Plan or execute failback
Important operational distinctions
Test failover
A test failover:
creates a temporary VM;
uses an isolated test network;
does not stop production replication;
does not change the active replication direction;
is removed through test-failover cleanup.
Regional failover
A real failover:
stops normal source-to-target replication;
creates or activates the recovery VM;
moves workload responsibility to the recovery region;
must be validated before it is committed.
Commit
A commit:
accepts the failover result;
removes the previous recovery points;
prevents selecting another recovery point for that failover;
is required before reverse replication can begin.
Reprotect
Reprotection:
reverses the replication direction;
makes the recovery-region VM the new replication source;
prepares the workload for failback to the original region.
Site Recovery requires a cache storage account in the current source region. Each managed disk that will be replicated must be represented in the PowerShell disk-replication configuration.
1. Verify PowerShell
The workshop requires PowerShell 7.
$PSVersionTable
Confirm the major version:
if ($PSVersionTable.PSVersion.Major -lt 7) {
throw "PowerShell 7 or later is required."
}
2. Install the Az PowerShell modules
Check whether the required Site Recovery module is installed:
Get-Module `
-ListAvailable `
-Name Az.RecoveryServices |
Sort-Object Version -Descending |
Select-Object -First 1 `
Name,
Version,
Path
Install the Az module when required:
if (
-not (
Get-Module `
-ListAvailable `
-Name Az.RecoveryServices
)
) {
Install-Module `
-Name Az `
-Scope CurrentUser `
-Repository PSGallery `
-Force `
-AllowClobber
}
Import the required modules:
$RequiredModules = @(
"Az.Accounts"
"Az.Resources"
"Az.Network"
"Az.Compute"
"Az.Storage"
"Az.RecoveryServices"
)
foreach ($ModuleName in $RequiredModules) {
Import-Module `
-Name $ModuleName `
-Force `
-ErrorAction Stop
}
Display the loaded versions:
Get-Module $RequiredModules |
Select-Object `
Name,
Version |
Sort-Object Name
3. Sign in to Azure
Connect-AzAccount
List the available subscriptions:
Get-AzSubscription |
Select-Object `
Name,
Id,
TenantId,
State |
Format-Table
Select the subscription:
$SubscriptionId = "<your-subscription-id>"
Set-AzContext `
-SubscriptionId $SubscriptionId
Verify the active context:
Get-AzContext |
Select-Object `
Name,
Subscription,
Tenant,
Account
4. Define the workshop variables
Keep the same PowerShell session open throughout the workshop.
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$SubscriptionId = (
Get-AzContext
).Subscription.Id
if ([string]::IsNullOrWhiteSpace($SubscriptionId)) {
throw "No active Azure subscription was found."
}
$SourceLocation = "westeurope"
$RecoveryLocation = "northeurope"
# Resource groups
$SourceResourceGroup = "rg-asr-source-weu"
$RecoveryResourceGroup = "rg-asr-recovery-neu"
$VaultResourceGroup = "rg-asr-vault-neu"
$TestResourceGroup = "rg-asr-test-neu"
# Source network
$SourceVnetName = "vnet-asr-source-weu"
$SourceVnetPrefix = "10.10.0.0/16"
$WorkloadSubnetName = "snet-workload"
$SourceSubnetPrefix = "10.10.1.0/24"
$SourceNsgName = "nsg-asr-source-weu"
# Recovery network
$RecoveryVnetName = "vnet-asr-recovery-neu"
$RecoveryVnetPrefix = "10.20.0.0/16"
$RecoverySubnetPrefix = "10.20.1.0/24"
$RecoveryNsgName = "nsg-asr-recovery-neu"
# Test-failover network
$TestVnetName = "vnet-asr-test-neu"
$TestVnetPrefix = "10.20.0.0/16"
$TestSubnetPrefix = "10.20.1.0/24"
$TestNsgName = "nsg-asr-test-neu"
# Source workload VM
$SourceVmName = "vm-asr-workload"
$SourceVmNicName = "nic-asr-workload"
$SourceVmPublicIp = "pip-asr-workload-weu"
$SourceVmPrivateIp = "10.10.1.4"
$SourceOsDiskName = "osdisk-asr-workload"
$SourceDataDiskName = "datadisk-asr-workload-01"
$VmAdminUsername = "azureuser"
$VmSize = "Standard_B2s"
# Temporary and recovery public IPs
$TestFailoverPublicIp = "pip-asr-testfailover-neu"
$FailoverPublicIp = "pip-asr-recovered-neu"
# Site Recovery vault
$VaultName = "rsv-asr-deepdive-neu"
# ASR fabrics
$SourceFabricName = "asr-fabric-westeurope"
$RecoveryFabricName = "asr-fabric-northeurope"
# ASR protection containers
$SourceContainerName = "asr-container-westeurope"
$RecoveryContainerName = "asr-container-northeurope"
# Replication policy
$ReplicationPolicyName = "asr-policy-24hour"
# Protection-container mappings
$SourceToRecoveryMappingName = "asr-map-weu-to-neu"
$RecoveryToSourceMappingName = "asr-map-neu-to-weu"
# Network mappings
$SourceToRecoveryNetworkMappingName = "asr-netmap-weu-to-neu"
$RecoveryToSourceNetworkMappingName = "asr-netmap-neu-to-weu"
# Globally unique storage-account names
$UniqueSuffix = Get-Random `
-Minimum 100000 `
-Maximum 999999
$SourceCacheStorageName = (
"stasrweu$UniqueSuffix"
).ToLowerInvariant()
$RecoveryCacheStorageName = (
"stasrneu$UniqueSuffix"
).ToLowerInvariant()
Set-AzContext `
-SubscriptionId $SubscriptionId |
Out-Null
Review the configuration:
[pscustomobject]@{
SubscriptionId = $SubscriptionId
SourceLocation = $SourceLocation
RecoveryLocation = $RecoveryLocation
SourceResourceGroup = $SourceResourceGroup
RecoveryResourceGroup = $RecoveryResourceGroup
VaultResourceGroup = $VaultResourceGroup
TestResourceGroup = $TestResourceGroup
SourceVm = $SourceVmName
SourceCacheStorage = $SourceCacheStorageName
RecoveryCacheStorage = $RecoveryCacheStorageName
}
5. Check for existing workshop resources
$WorkshopResourceGroups = @(
$SourceResourceGroup
$RecoveryResourceGroup
$VaultResourceGroup
$TestResourceGroup
)
$ExistingResourceGroups = @(
foreach ($ResourceGroupName in $WorkshopResourceGroups) {
Get-AzResourceGroup `
-Name $ResourceGroupName `
-ErrorAction SilentlyContinue
}
)
if ($ExistingResourceGroups) {
$ExistingResourceGroups |
Select-Object `
ResourceGroupName,
Location |
Format-Table
throw @"
One or more workshop resource groups already exist.
Delete the previous deployment or change the resource-group names.
"@
}
6. Create the regional resource groups
Create the source resource group:
$SourceResourceGroupObject = New-AzResourceGroup `
-Name $SourceResourceGroup `
-Location $SourceLocation `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "source"
region = $SourceLocation
}
Create the recovery resource group:
$RecoveryResourceGroupObject = New-AzResourceGroup `
-Name $RecoveryResourceGroup `
-Location $RecoveryLocation `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "recovery"
region = $RecoveryLocation
}
Create the vault resource group:
$VaultResourceGroupObject = New-AzResourceGroup `
-Name $VaultResourceGroup `
-Location $RecoveryLocation `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "vault"
region = $RecoveryLocation
}
Create the test resource group:
$TestResourceGroupObject = New-AzResourceGroup `
-Name $TestResourceGroup `
-Location $RecoveryLocation `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "test-failover"
region = $RecoveryLocation
}
Display them:
Get-AzResourceGroup |
Where-Object {
$_.ResourceGroupName -in $WorkshopResourceGroups
} |
Select-Object `
ResourceGroupName,
Location,
ProvisioningState |
Sort-Object ResourceGroupName |
Format-Table
7. Create the regional NSGs
The lab permits inbound HTTP on port 8080 so the source, test, and recovered application can be validated from the local workstation.
In production, restrict the source prefix or use a private validation path.
Define an NSG helper function:
function New-AsrWorkshopNsg {
param(
[Parameter(Mandatory)]
[string]$Name,
[Parameter(Mandatory)]
[string]$ResourceGroupName,
[Parameter(Mandatory)]
[string]$Location
)
$HttpRule = New-AzNetworkSecurityRuleConfig `
-Name "Allow-HTTP-8080" `
-Description "Allow HTTP validation for the Site Recovery workshop." `
-Access Allow `
-Protocol Tcp `
-Direction Inbound `
-Priority 100 `
-SourceAddressPrefix Internet `
-SourcePortRange "*" `
-DestinationAddressPrefix "*" `
-DestinationPortRange 8080
New-AzNetworkSecurityGroup `
-Name $Name `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-SecurityRules $HttpRule `
-Tag @{
workshop = "site-recovery-deep-dive"
}
}
Create the source NSG:
$SourceNsg = New-AsrWorkshopNsg `
-Name $SourceNsgName `
-ResourceGroupName $SourceResourceGroup `
-Location $SourceLocation
Create the recovery NSG:
$RecoveryNsg = New-AsrWorkshopNsg `
-Name $RecoveryNsgName `
-ResourceGroupName $RecoveryResourceGroup `
-Location $RecoveryLocation
Create the isolated test NSG:
$TestNsg = New-AsrWorkshopNsg `
-Name $TestNsgName `
-ResourceGroupName $TestResourceGroup `
-Location $RecoveryLocation
8. Create the source VNet
Create the source subnet configuration:
$SourceSubnetConfiguration = New-AzVirtualNetworkSubnetConfig `
-Name $WorkloadSubnetName `
-AddressPrefix $SourceSubnetPrefix `
-NetworkSecurityGroup $SourceNsg
Create the source VNet:
$SourceVnet = New-AzVirtualNetwork `
-Name $SourceVnetName `
-ResourceGroupName $SourceResourceGroup `
-Location $SourceLocation `
-AddressPrefix $SourceVnetPrefix `
-Subnet $SourceSubnetConfiguration `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "source-network"
}
9. Create the recovery VNet
$RecoverySubnetConfiguration = New-AzVirtualNetworkSubnetConfig `
-Name $WorkloadSubnetName `
-AddressPrefix $RecoverySubnetPrefix `
-NetworkSecurityGroup $RecoveryNsg
$RecoveryVnet = New-AzVirtualNetwork `
-Name $RecoveryVnetName `
-ResourceGroupName $RecoveryResourceGroup `
-Location $RecoveryLocation `
-AddressPrefix $RecoveryVnetPrefix `
-Subnet $RecoverySubnetConfiguration `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "recovery-network"
}
10. Create the isolated test-failover VNet
$TestSubnetConfiguration = New-AzVirtualNetworkSubnetConfig `
-Name $WorkloadSubnetName `
-AddressPrefix $TestSubnetPrefix `
-NetworkSecurityGroup $TestNsg
$TestVnet = New-AzVirtualNetwork `
-Name $TestVnetName `
-ResourceGroupName $TestResourceGroup `
-Location $RecoveryLocation `
-AddressPrefix $TestVnetPrefix `
-Subnet $TestSubnetConfiguration `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "isolated-test-network"
}
Display all three VNets:
@(
$SourceVnet
$RecoveryVnet
$TestVnet
) |
Select-Object `
Name,
Location,
ResourceGroupName,
AddressSpace |
Format-Table
Do not peer the test VNet with any other network.
11. Create the source VM public IP
$SourcePublicIp = New-AzPublicIpAddress `
-Name $SourceVmPublicIp `
-ResourceGroupName $SourceResourceGroup `
-Location $SourceLocation `
-Sku Standard `
-AllocationMethod Static `
-IpAddressVersion IPv4 `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "source-validation"
}
12. Create the source VM NIC
Resolve the source subnet:
$SourceSubnet = Get-AzVirtualNetworkSubnetConfig `
-Name $WorkloadSubnetName `
-VirtualNetwork $SourceVnet
Create the NIC:
$SourceVmNic = New-AzNetworkInterface `
-Name $SourceVmNicName `
-ResourceGroupName $SourceResourceGroup `
-Location $SourceLocation `
-SubnetId $SourceSubnet.Id `
-PrivateIpAddress $SourceVmPrivateIp `
-PublicIpAddressId $SourcePublicIp.Id `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "source-workload"
}
13. Create the source workload VM
Enter a password for the Linux administrator account:
$VmCredential = Get-Credential `
-UserName $VmAdminUsername `
-Message "Enter the password for the Site Recovery workshop VM."
Create the VM configuration:
$SourceVmConfig = New-AzVMConfig `
-VMName $SourceVmName `
-VMSize $VmSize
Configure Linux:
$SourceVmConfig = Set-AzVMOperatingSystem `
-VM $SourceVmConfig `
-Linux `
-ComputerName $SourceVmName `
-Credential $VmCredential
Configure the image:
$SourceVmConfig = Set-AzVMSourceImage `
-VM $SourceVmConfig `
-PublisherName "Canonical" `
-Offer "0001-com-ubuntu-server-jammy" `
-Skus "22_04-lts-gen2" `
-Version "latest"
Configure the OS disk:
$SourceVmConfig = Set-AzVMOSDisk `
-VM $SourceVmConfig `
-Name $SourceOsDiskName `
-CreateOption FromImage `
-StorageAccountType Standard_LRS
Attach the NIC:
$SourceVmConfig = Add-AzVMNetworkInterface `
-VM $SourceVmConfig `
-Id $SourceVmNic.Id `
-Primary
Add a managed data disk:
$SourceVmConfig = Add-AzVMDataDisk `
-VM $SourceVmConfig `
-Name $SourceDataDiskName `
-DiskSizeInGB 8 `
-Lun 0 `
-CreateOption Empty `
-StorageAccountType Standard_LRS
Deploy the VM:
New-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Location $SourceLocation `
-VM $SourceVmConfig `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "protected-workload"
}
Verify its state:
Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-Status |
Select-Object `
Name,
Location,
PowerState,
ProvisioningState
14. Configure the application and data disk
The configuration creates:
an ext4 filesystem on the data disk;
a persistent mount at /srv/asrdata;
a replicated marker file;
an Nginx service on port 8080;
a page that displays the marker stored on the data disk.
$WorkloadConfigurationScript = @'
#!/usr/bin/env bash
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y nginx curl
DATA_DISK="/dev/disk/azure/scsi1/lun0"
MOUNT_PATH="/srv/asrdata"
for attempt in $(seq 1 60); do
if [ -e "$DATA_DISK" ]; then
break
fi
sleep 5
done
if [ ! -e "$DATA_DISK" ]; then
echo "The Azure data-disk path was not found."
exit 1
fi
mkdir -p "$MOUNT_PATH"
if ! blkid "$DATA_DISK" >/dev/null 2>&1; then
mkfs.ext4 -F "$DATA_DISK"
fi
DISK_UUID="$(blkid -s UUID -o value "$DATA_DISK")"
if ! grep -q "$DISK_UUID" /etc/fstab; then
echo "UUID=$DISK_UUID $MOUNT_PATH ext4 defaults,nofail 0 2" >> /etc/fstab
fi
mount -a
MARKER_FILE="$MOUNT_PATH/site-recovery-marker.txt"
cat >"$MARKER_FILE" <<MARKER
Azure Site Recovery Deep Dive
Source region: West Europe
Protected VM: vm-asr-workload
Created: $(date --utc --iso-8601=seconds)
Data disk: /srv/asrdata
MARKER
cat >/etc/nginx/sites-available/default <<'NGINX'
server {
listen 8080 default_server;
listen [::]:8080 default_server;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
NGINX
MARKER_CONTENT="$(cat "$MARKER_FILE")"
cat >/var/www/html/index.html <<HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Azure Site Recovery Deep Dive</title>
<style>
body {
margin: 4rem;
background: #07111f;
color: #80eaff;
font-family: Arial, sans-serif;
}
.card {
max-width: 760px;
padding: 2rem;
border: 2px solid #ff4bd8;
border-radius: 14px;
box-shadow: 0 0 28px #ff4bd855;
}
h1 {
color: #ff59dc;
}
pre {
padding: 1rem;
background: #020811;
color: #ffffff;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="card">
<h1>Azure Site Recovery Deep Dive</h1>
<p>The workload is online.</p>
<p>The following data was loaded from the managed data disk:</p>
<pre>$MARKER_CONTENT</pre>
</div>
</body>
</html>
HTML
nginx -t
systemctl enable nginx
systemctl restart nginx
echo
echo "=== Data-disk mount ==="
findmnt "$MOUNT_PATH"
echo
echo "=== Replication marker ==="
cat "$MARKER_FILE"
echo
echo "=== Nginx listener ==="
ss -lntp | grep ':8080'
echo
echo "=== Local application test ==="
curl -fsS http://127.0.0.1:8080
'@
Run the configuration:
$SourceConfigurationResult = Invoke-AzVMRunCommand `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-CommandId "RunShellScript" `
-ScriptString $WorkloadConfigurationScript
Display the result:
$SourceConfigurationResult.Value.Message
15. Validate the source application
Get the source public IP:
$SourcePublicIp = Get-AzPublicIpAddress `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmPublicIp
Display the URL:
$SourceApplicationUrl = "http://$($SourcePublicIp.IpAddress):8080"
$SourceApplicationUrl
Test the application:
$SourceApplicationResponse = Invoke-WebRequest `
-Uri $SourceApplicationUrl `
-TimeoutSec 30
Validate the response:
if (
$SourceApplicationResponse.Content -notmatch
"Azure Site Recovery Deep Dive"
) {
throw "The source application did not return the expected content."
}
if (
$SourceApplicationResponse.Content -notmatch
"Data disk"
) {
throw "The source application did not return the data-disk marker."
}
Write-Host `
"PASS: The source application and data disk are available." `
-ForegroundColor Green
16. Create the cache storage accounts
Create the source-region cache storage account:
$SourceCacheStorageAccount = New-AzStorageAccount `
-Name $SourceCacheStorageName `
-ResourceGroupName $SourceResourceGroup `
-Location $SourceLocation `
-SkuName Standard_LRS `
-Kind StorageV2 `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "source-replication-cache"
}
Create the recovery-region cache storage account for later reprotection:
$RecoveryCacheStorageAccount = New-AzStorageAccount `
-Name $RecoveryCacheStorageName `
-ResourceGroupName $RecoveryResourceGroup `
-Location $RecoveryLocation `
-SkuName Standard_LRS `
-Kind StorageV2 `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "reverse-replication-cache"
}
Display both accounts:
@(
$SourceCacheStorageAccount
$RecoveryCacheStorageAccount
) |
Select-Object `
StorageAccountName,
Location,
Kind,
Sku |
Format-Table
17. Create the Recovery Services vault
$Vault = New-AzRecoveryServicesVault `
-Name $VaultName `
-ResourceGroupName $VaultResourceGroup `
-Location $RecoveryLocation
Display it:
$Vault |
Select-Object `
Name,
ResourceGroupName,
Location,
ID
Set the Site Recovery vault context:
Set-AzRecoveryServicesAsrVaultContext `
-Vault $Vault
All subsequent Site Recovery cmdlets in this session operate against this vault.
18. Create an ASR job-waiting function
Most Site Recovery configuration operations are asynchronous.
function Wait-AsrJob {
param(
[Parameter(Mandatory)]
$Job,
[Parameter(Mandatory)]
[string]$Operation,
[int]$TimeoutMinutes = 120,
[int]$PollSeconds = 15
)
$Deadline = (
Get-Date
).AddMinutes($TimeoutMinutes)
do {
$CurrentJob = Get-AzRecoveryServicesAsrJob `
-Job $Job
Write-Host (
"{0:u} {1}: {2} - {3}" -f `
(Get-Date),
$Operation,
$CurrentJob.State,
$CurrentJob.StateDescription
)
if ($CurrentJob.State -eq "Succeeded") {
return $CurrentJob
}
if (
$CurrentJob.State -in @(
"Failed"
"Cancelled"
"Suspended"
)
) {
$ErrorText = (
$CurrentJob.Errors |
Format-List * |
Out-String
)
throw @"
Site Recovery operation failed.
Operation:
$Operation
State:
$($CurrentJob.State)
Errors:
$ErrorText
"@
}
Start-Sleep `
-Seconds $PollSeconds
}
while ((Get-Date) -lt $Deadline)
throw @"
Site Recovery operation timed out.
Operation:
$Operation
Timeout:
$TimeoutMinutes minutes
"@
}
19. Resolve the Azure region display names
Site Recovery fabric creation uses Azure location display names.
$SourceLocationObject = Get-AzLocation |
Where-Object {
$_.Location -eq $SourceLocation
} |
Select-Object -First 1
$RecoveryLocationObject = Get-AzLocation |
Where-Object {
$_.Location -eq $RecoveryLocation
} |
Select-Object -First 1
if (-not $SourceLocationObject) {
throw "The source Azure location was not found."
}
if (-not $RecoveryLocationObject) {
throw "The recovery Azure location was not found."
}
$SourceLocationDisplayName = $SourceLocationObject.DisplayName
$RecoveryLocationDisplayName = $RecoveryLocationObject.DisplayName
Display them:
[pscustomobject]@{
SourceCode = $SourceLocation
SourceDisplayName = $SourceLocationDisplayName
RecoveryCode = $RecoveryLocation
RecoveryDisplayName = $RecoveryLocationDisplayName
}
20. Create the source Site Recovery fabric
$SourceFabricJob = New-AzRecoveryServicesAsrFabric `
-Azure `
-Location $SourceLocationDisplayName `
-Name $SourceFabricName
Wait for completion:
Wait-AsrJob `
-Job $SourceFabricJob `
-Operation "Create source Site Recovery fabric" `
-TimeoutMinutes 60
Retrieve the source fabric:
$SourceFabric = Get-AzRecoveryServicesAsrFabric `
-Name $SourceFabricName
21. Create the recovery Site Recovery fabric
$RecoveryFabricJob = New-AzRecoveryServicesAsrFabric `
-Azure `
-Location $RecoveryLocationDisplayName `
-Name $RecoveryFabricName
Wait for completion:
Wait-AsrJob `
-Job $RecoveryFabricJob `
-Operation "Create recovery Site Recovery fabric" `
-TimeoutMinutes 60
Retrieve the recovery fabric:
$RecoveryFabric = Get-AzRecoveryServicesAsrFabric `
-Name $RecoveryFabricName
Display both fabrics:
@(
$SourceFabric
$RecoveryFabric
) |
Select-Object `
FriendlyName,
FabricType,
Location,
FabricSpecificDetails |
Format-List
22. Create the protection containers
Create the source protection container:
$SourceContainerJob = New-AzRecoveryServicesAsrProtectionContainer `
-InputObject $SourceFabric `
-Name $SourceContainerName
Wait for completion:
Wait-AsrJob `
-Job $SourceContainerJob `
-Operation "Create source protection container" `
-TimeoutMinutes 60
Retrieve the source container:
$SourceProtectionContainer = Get-AzRecoveryServicesAsrProtectionContainer `
-Fabric $SourceFabric `
-Name $SourceContainerName
Create the recovery protection container:
$RecoveryContainerJob = New-AzRecoveryServicesAsrProtectionContainer `
-InputObject $RecoveryFabric `
-Name $RecoveryContainerName
Wait for completion:
Wait-AsrJob `
-Job $RecoveryContainerJob `
-Operation "Create recovery protection container" `
-TimeoutMinutes 60
$RecoveryProtectionContainer = Get-AzRecoveryServicesAsrProtectionContainer `
-Fabric $RecoveryFabric `
-Name $RecoveryContainerName
23. Create the replication policy
$ReplicationPolicyJob = New-AzRecoveryServicesAsrPolicy `
-AzureToAzure `
-Name $ReplicationPolicyName `
-RecoveryPointRetentionInHours 24 `
-ApplicationConsistentSnapshotFrequencyInHours 4
Wait for completion:
Wait-AsrJob `
-Job $ReplicationPolicyJob `
-Operation "Create replication policy" `
-TimeoutMinutes 60
Retrieve the policy:
$ReplicationPolicy = Get-AzRecoveryServicesAsrPolicy `
-Name $ReplicationPolicyName
Display it:
$ReplicationPolicy |
Select-Object `
Name,
ProviderSpecificDetails |
Format-List
24. Create the forward protection-container mapping
$SourceToRecoveryMappingJob = New-AzRecoveryServicesAsrProtectionContainerMapping `
-Name $SourceToRecoveryMappingName `
-Policy $ReplicationPolicy `
-PrimaryProtectionContainer $SourceProtectionContainer `
-RecoveryProtectionContainer $RecoveryProtectionContainer
Wait for completion:
Wait-AsrJob `
-Job $SourceToRecoveryMappingJob `
-Operation "Create West Europe to North Europe protection mapping" `
-TimeoutMinutes 60
Retrieve the mapping:
$SourceToRecoveryMapping = Get-AzRecoveryServicesAsrProtectionContainerMapping `
-ProtectionContainer $SourceProtectionContainer `
-Name $SourceToRecoveryMappingName
25. Create the reverse protection-container mapping
The reverse mapping is required for reprotection and failback.
$RecoveryToSourceMappingJob = New-AzRecoveryServicesAsrProtectionContainerMapping `
-Name $RecoveryToSourceMappingName `
-Policy $ReplicationPolicy `
-PrimaryProtectionContainer $RecoveryProtectionContainer `
-RecoveryProtectionContainer $SourceProtectionContainer
Wait for completion:
Wait-AsrJob `
-Job $RecoveryToSourceMappingJob `
-Operation "Create North Europe to West Europe protection mapping" `
-TimeoutMinutes 60
Retrieve the reverse mapping:
$RecoveryToSourceMapping = Get-AzRecoveryServicesAsrProtectionContainerMapping `
-ProtectionContainer $RecoveryProtectionContainer `
-Name $RecoveryToSourceMappingName
26. Create the forward network mapping
$SourceToRecoveryNetworkJob = New-AzRecoveryServicesAsrNetworkMapping `
-AzureToAzure `
-Name $SourceToRecoveryNetworkMappingName `
-PrimaryFabric $SourceFabric `
-PrimaryAzureNetworkId $SourceVnet.Id `
-RecoveryFabric $RecoveryFabric `
-RecoveryAzureNetworkId $RecoveryVnet.Id
Wait for completion:
Wait-AsrJob `
-Job $SourceToRecoveryNetworkJob `
-Operation "Create source-to-recovery network mapping" `
-TimeoutMinutes 60
27. Create the reverse network mapping
$RecoveryToSourceNetworkJob = New-AzRecoveryServicesAsrNetworkMapping `
-AzureToAzure `
-Name $RecoveryToSourceNetworkMappingName `
-PrimaryFabric $RecoveryFabric `
-PrimaryAzureNetworkId $RecoveryVnet.Id `
-RecoveryFabric $SourceFabric `
-RecoveryAzureNetworkId $SourceVnet.Id
Wait for completion:
Wait-AsrJob `
-Job $RecoveryToSourceNetworkJob `
-Operation "Create recovery-to-source network mapping" `
-TimeoutMinutes 60
Display the network mappings:
Get-AzRecoveryServicesAsrNetworkMapping `
-Fabric $SourceFabric |
Select-Object `
Name,
PrimaryNetworkFriendlyName,
RecoveryNetworkFriendlyName |
Format-Table
Get-AzRecoveryServicesAsrNetworkMapping `
-Fabric $RecoveryFabric |
Select-Object `
Name,
PrimaryNetworkFriendlyName,
RecoveryNetworkFriendlyName |
Format-Table
28. Build the disk-replication configuration
Retrieve the source VM:
$SourceVm = Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName
Refresh the recovery resource-group object:
$RecoveryResourceGroupObject = Get-AzResourceGroup `
-Name $RecoveryResourceGroup
Create the OS-disk replication configuration:
$OsDiskStorageType = (
$SourceVm.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
)
$OsDiskReplicationConfiguration = New-AzRecoveryServicesAsrAzureToAzureDiskReplicationConfig `
-ManagedDisk `
-LogStorageAccountId $SourceCacheStorageAccount.Id `
-DiskId $SourceVm.StorageProfile.OsDisk.ManagedDisk.Id `
-RecoveryResourceGroupId $RecoveryResourceGroupObject.ResourceId `
-RecoveryReplicaDiskAccountType $OsDiskStorageType `
-RecoveryTargetDiskAccountType $OsDiskStorageType
Create replication configurations for all data disks:
$DataDiskReplicationConfigurations = @(
foreach ($DataDisk in $SourceVm.StorageProfile.DataDisks) {
$ReplicaDiskType = (
$DataDisk.ManagedDisk.StorageAccountType
)
if (
$ReplicaDiskType -in @(
"PremiumV2_LRS"
"Ultra_LRS"
)
) {
$ReplicaDiskType = "Premium_LRS"
}
New-AzRecoveryServicesAsrAzureToAzureDiskReplicationConfig `
-ManagedDisk `
-LogStorageAccountId $SourceCacheStorageAccount.Id `
-DiskId $DataDisk.ManagedDisk.Id `
-RecoveryResourceGroupId $RecoveryResourceGroupObject.ResourceId `
-RecoveryReplicaDiskAccountType $ReplicaDiskType `
-RecoveryTargetDiskAccountType $DataDisk.ManagedDisk.StorageAccountType
}
)
Combine the configurations:
$DiskReplicationConfigurations = @(
$OsDiskReplicationConfiguration
$DataDiskReplicationConfigurations
)
Display the protected disk IDs:
$DiskReplicationConfigurations |
Select-Object `
DiskId,
LogStorageAccountId,
RecoveryReplicaDiskAccountType,
RecoveryTargetDiskAccountType |
Format-List
29. Enable Site Recovery replication
Create the replication-protected item:
$EnableReplicationJob = New-AzRecoveryServicesAsrReplicationProtectedItem `
-AzureToAzure `
-AzureVmId $SourceVm.Id `
-Name (
New-Guid
).Guid `
-ProtectionContainerMapping $SourceToRecoveryMapping `
-AzureToAzureDiskReplicationConfiguration $DiskReplicationConfigurations `
-RecoveryResourceGroupId $RecoveryResourceGroupObject.ResourceId
Wait for the enable-protection job:
Wait-AsrJob `
-Job $EnableReplicationJob `
-Operation "Enable Azure-to-Azure replication" `
-TimeoutMinutes 180 `
-PollSeconds 30
The enable-protection job can complete before the initial disk synchronization is fully finished.
30. Wait for initial replication
Initial replication creates replicas of the protected disks in the recovery region. After that initial synchronization completes, the workload enters differential replication and its protection state becomes Protected.
$ProtectionDeadline = (
Get-Date
).AddHours(4)
do {
$ReplicationProtectedItem = Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $SourceProtectionContainer |
Where-Object {
$_.FriendlyName -eq $SourceVmName
} |
Select-Object -First 1
if ($ReplicationProtectedItem) {
Write-Host (
"{0:u} ProtectionState={1}, Health={2}, ActiveLocation={3}" -f `
(Get-Date),
$ReplicationProtectedItem.ProtectionState,
$ReplicationProtectedItem.ReplicationHealth,
$ReplicationProtectedItem.ActiveLocation
)
}
else {
Write-Host `
"Waiting for the replication-protected item to appear..."
}
$ReplicationReady = (
$ReplicationProtectedItem -and
$ReplicationProtectedItem.ProtectionState -eq "Protected" -and
$ReplicationProtectedItem.ReplicationHealth -eq "Normal"
)
if (-not $ReplicationReady) {
Start-Sleep -Seconds 60
}
}
until (
$ReplicationReady -or
(Get-Date) -ge $ProtectionDeadline
)
if (-not $ReplicationReady) {
throw @"
Initial replication did not reach the expected protected state.
Review the Site Recovery jobs and replication health.
"@
}
Display the protected item:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
ProtectionState,
ReplicationHealth,
ActiveLocation,
TestFailoverState,
AllowedOperations |
Format-List
31. Inspect the available recovery points
$RecoveryPoints = @(
Get-AzRecoveryServicesAsrRecoveryPoint `
-ReplicationProtectedItem $ReplicationProtectedItem |
Sort-Object RecoveryPointTime
)
if ($RecoveryPoints.Count -eq 0) {
throw "No recovery points are available."
}
Display them:
$RecoveryPoints |
Select-Object `
RecoveryPointType,
RecoveryPointTime,
Id |
Format-Table
Display the latest point:
$LatestRecoveryPoint = $RecoveryPoints[-1]
$LatestRecoveryPoint |
Select-Object `
RecoveryPointType,
RecoveryPointTime
32. Create a public-IP attachment helper
Site Recovery does not automatically provide a public IP for the test or recovered VM in this workshop.
The following helper creates a temporary Standard public IP and attaches it to the VM’s primary NIC.
function Add-AsrWorkshopPublicIp {
param(
[Parameter(Mandatory)]
[string]$VmResourceGroupName,
[Parameter(Mandatory)]
[string]$VmName,
[Parameter(Mandatory)]
[string]$PublicIpResourceGroupName,
[Parameter(Mandatory)]
[string]$PublicIpName,
[Parameter(Mandatory)]
[string]$Location
)
$Vm = Get-AzVM `
-ResourceGroupName $VmResourceGroupName `
-Name $VmName
$NicId = (
$Vm.NetworkProfile.NetworkInterfaces |
Where-Object {
$_.Primary -eq $true
} |
Select-Object -First 1
).Id
if (-not $NicId) {
$NicId = $Vm.NetworkProfile.NetworkInterfaces[0].Id
}
$NicIdParts = $NicId -split "/"
$NicResourceGroupName = $NicIdParts[4]
$NicName = $NicIdParts[-1]
$PublicIp = New-AzPublicIpAddress `
-Name $PublicIpName `
-ResourceGroupName $PublicIpResourceGroupName `
-Location $Location `
-Sku Standard `
-AllocationMethod Static `
-IpAddressVersion IPv4 `
-Tag @{
workshop = "site-recovery-deep-dive"
role = "temporary-validation"
}
$Nic = Get-AzNetworkInterface `
-ResourceGroupName $NicResourceGroupName `
-Name $NicName
$Nic.IpConfigurations[0].PublicIpAddress = $PublicIp
Set-AzNetworkInterface `
-NetworkInterface $Nic |
Out-Null
$Deadline = (
Get-Date
).AddMinutes(10)
do {
$PublicIp = Get-AzPublicIpAddress `
-ResourceGroupName $PublicIpResourceGroupName `
-Name $PublicIpName
if (
[string]::IsNullOrWhiteSpace(
$PublicIp.IpAddress
)
) {
Start-Sleep -Seconds 10
}
}
until (
-not [string]::IsNullOrWhiteSpace(
$PublicIp.IpAddress
) -or
(Get-Date) -ge $Deadline
)
if (
[string]::IsNullOrWhiteSpace(
$PublicIp.IpAddress
)
) {
throw "The public IP did not receive an address."
}
return $PublicIp
}
33. Capture the recovery-region VM inventory
This allows the test VM to be detected without relying solely on its generated name.
$RecoveryVmIdsBeforeTest = @(
Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Id
)
34. Run the isolated test failover
Start the test failover:
$TestFailoverJob = Start-AzRecoveryServicesAsrTestFailoverJob `
-ReplicationProtectedItem $ReplicationProtectedItem `
-AzureVMNetworkId $TestVnet.Id `
-Direction PrimaryToRecovery
Wait for completion:
Wait-AsrJob `
-Job $TestFailoverJob `
-Operation "Run isolated test failover" `
-TimeoutMinutes 180 `
-PollSeconds 30
A successful test failover creates a temporary VM while the source VM and normal replication remain active.
35. Detect the test-failover VM
$RecoveryVmsAfterTest = @(
Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Status
)
$TestVm = $RecoveryVmsAfterTest |
Where-Object {
$_.Id -notin $RecoveryVmIdsBeforeTest
} |
Select-Object -First 1
Use the conventional test name as a fallback:
if (-not $TestVm) {
$TestVm = Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name "$SourceVmName-Test" `
-Status `
-ErrorAction SilentlyContinue
}
Validate detection:
if (-not $TestVm) {
Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Status |
Select-Object `
Name,
Location,
PowerState |
Format-Table
throw "The test-failover VM could not be identified."
}
Store its name:
$TestVmName = $TestVm.Name
$TestVm |
Select-Object `
Name,
Location,
PowerState,
ProvisioningState |
Format-List
36. Validate the test VM network
Resolve the test VM NIC:
$TestVmObject = Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name $TestVmName
$TestVmNicId = $TestVmObject.NetworkProfile.NetworkInterfaces[0].Id
$TestVmNicParts = $TestVmNicId -split "/"
$TestVmNicResourceGroup = $TestVmNicParts[4]
$TestVmNicName = $TestVmNicParts[-1]
$TestVmNic = Get-AzNetworkInterface `
-ResourceGroupName $TestVmNicResourceGroup `
-Name $TestVmNicName
Display its network placement:
$TestVmNic |
Select-Object `
Name,
Location,
@{
Name = "PrivateIp"
Expression = {
$_.IpConfigurations[0].PrivateIpAddress
}
},
@{
Name = "SubnetId"
Expression = {
$_.IpConfigurations[0].Subnet.Id
}
} |
Format-List
The subnet ID should reference:
vnet-asr-test-neu
snet-workload
37. Attach a temporary public IP to the test VM
$TestPublicIp = Add-AsrWorkshopPublicIp `
-VmResourceGroupName $RecoveryResourceGroup `
-VmName $TestVmName `
-PublicIpResourceGroupName $RecoveryResourceGroup `
-PublicIpName $TestFailoverPublicIp `
-Location $RecoveryLocation
Display the validation URL:
$TestApplicationUrl = "http://$($TestPublicIp.IpAddress):8080"
$TestApplicationUrl
38. Validate the test-failover application
Wait for the application to become reachable:
$TestApplicationDeadline = (
Get-Date
).AddMinutes(10)
$TestApplicationResponse = $null
do {
try {
$TestApplicationResponse = Invoke-WebRequest `
-Uri $TestApplicationUrl `
-TimeoutSec 20
}
catch {
Write-Host `
"Waiting for the test-failover application..."
Start-Sleep -Seconds 15
}
}
until (
$TestApplicationResponse -or
(Get-Date) -ge $TestApplicationDeadline
)
if (-not $TestApplicationResponse) {
throw "The test-failover application did not become reachable."
}
Validate the replicated content:
if (
$TestApplicationResponse.Content -notmatch
"Azure Site Recovery Deep Dive"
) {
throw "The test-failover application content is incorrect."
}
if (
$TestApplicationResponse.Content -notmatch
"Data disk"
) {
throw "The replicated data-disk marker is missing."
}
Write-Host `
"PASS: Test failover restored the application and data-disk content." `
-ForegroundColor Green
39. Validate the test VM from inside the guest
$TestGuestValidation = Invoke-AzVMRunCommand `
-ResourceGroupName $RecoveryResourceGroup `
-Name $TestVmName `
-CommandId "RunShellScript" `
-ScriptString @'
set -e
echo "=== Hostname ==="
hostname
echo
echo "=== Region-independent application test ==="
curl -fsS http://127.0.0.1:8080
echo
echo "=== Replicated data-disk mount ==="
findmnt /srv/asrdata
echo
echo "=== Replicated data marker ==="
cat /srv/asrdata/site-recovery-marker.txt
'@
Display the result:
$TestGuestValidation.Value.Message
40. Confirm production remained available
The source VM should still be running during the isolated test failover.
$SourceVmStatus = Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-Status
$SourceVmStatus |
Select-Object `
Name,
PowerState,
ProvisioningState
Retest the source application:
$SourceRetest = Invoke-WebRequest `
-Uri $SourceApplicationUrl `
-TimeoutSec 30
if (
$SourceRetest.Content -notmatch
"Azure Site Recovery Deep Dive"
) {
throw "The source application is not available."
}
Write-Host `
"PASS: The source workload remained online during the drill." `
-ForegroundColor Green
41. Clean up the test failover
Start cleanup:
$TestCleanupJob = Start-AzRecoveryServicesAsrTestFailoverCleanupJob `
-ReplicationProtectedItem $ReplicationProtectedItem `
-Comment "Application and replicated data validated successfully."
Wait for cleanup:
Wait-AsrJob `
-Job $TestCleanupJob `
-Operation "Clean up test failover" `
-TimeoutMinutes 120 `
-PollSeconds 30
Delete the temporary public IP:
Remove-AzPublicIpAddress `
-ResourceGroupName $RecoveryResourceGroup `
-Name $TestFailoverPublicIp `
-Force `
-ErrorAction SilentlyContinue
Confirm that the test VM is gone:
Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Status `
-ErrorAction SilentlyContinue |
Select-Object `
Name,
PowerState |
Format-Table
The source replication remains protected.
42. Recheck replication health
Refresh the protected item:
$ReplicationProtectedItem = Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $SourceProtectionContainer |
Where-Object {
$_.FriendlyName -eq $SourceVmName
} |
Select-Object -First 1
Display its state:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
ProtectionState,
ReplicationHealth,
ActiveLocation,
TestFailoverState |
Format-List
Required values:
ProtectionState Protected
ReplicationHealth Normal
43. Prepare for the regional failover
A real regional failover changes the active workload location.
Before continuing:
confirm that the test failover succeeded;
confirm that the application was validated;
confirm that the replicated data disk was validated;
confirm that the source and recovery networks are correct;
confirm that the latest recovery point is acceptable.
Refresh the recovery points:
$RecoveryPoints = @(
Get-AzRecoveryServicesAsrRecoveryPoint `
-ReplicationProtectedItem $ReplicationProtectedItem |
Sort-Object RecoveryPointTime
)
$LatestRecoveryPoint = $RecoveryPoints[-1]
Display the selected point:
$LatestRecoveryPoint |
Select-Object `
RecoveryPointType,
RecoveryPointTime,
Id |
Format-List
44. Stop the source VM
Stopping the source VM before failover reduces the chance of additional writes occurring after the selected recovery point.
Stop-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-Force
Confirm the state:
Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-Status |
Select-Object `
Name,
PowerState
Expected state:
VM deallocated
45. Perform the regional failover
Start the failover to North Europe:
$RegionalFailoverJob = Start-AzRecoveryServicesAsrUnplannedFailoverJob `
-ReplicationProtectedItem $ReplicationProtectedItem `
-Direction PrimaryToRecovery `
-RecoveryPoint $LatestRecoveryPoint
Wait for completion:
Wait-AsrJob `
-Job $RegionalFailoverJob `
-Operation "Fail over workload to North Europe" `
-TimeoutMinutes 180 `
-PollSeconds 30
Do not cancel an active failover job. Failover stops normal replication while the operation runs, and cancelling it can leave the item without active replication.
46. Detect the recovered VM
$RecoveredVm = Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name $SourceVmName `
-Status `
-ErrorAction SilentlyContinue
Use a fallback search when required:
if (-not $RecoveredVm) {
$RecoveredVm = Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Status |
Where-Object {
$_.Name -notmatch "-Test$"
} |
Select-Object -First 1
}
Validate detection:
if (-not $RecoveredVm) {
throw "The recovered VM could not be identified."
}
Store the recovered name:
$RecoveredVmName = $RecoveredVm.Name
Display it:
$RecoveredVm |
Select-Object `
Name,
Location,
PowerState,
ProvisioningState |
Format-List
47. Validate the recovered VM network
$RecoveredVmObject = Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName
$RecoveredNicId = $RecoveredVmObject.NetworkProfile.NetworkInterfaces[0].Id
$RecoveredNicParts = $RecoveredNicId -split "/"
$RecoveredNicResourceGroup = $RecoveredNicParts[4]
$RecoveredNicName = $RecoveredNicParts[-1]
$RecoveredNic = Get-AzNetworkInterface `
-ResourceGroupName $RecoveredNicResourceGroup `
-Name $RecoveredNicName
Display its configuration:
$RecoveredNic |
Select-Object `
Name,
Location,
@{
Name = "PrivateIp"
Expression = {
$_.IpConfigurations[0].PrivateIpAddress
}
},
@{
Name = "SubnetId"
Expression = {
$_.IpConfigurations[0].Subnet.Id
}
} |
Format-List
The NIC should reference:
vnet-asr-recovery-neu
snet-workload
48. Attach a public IP to the recovered VM
$RecoveredPublicIp = Add-AsrWorkshopPublicIp `
-VmResourceGroupName $RecoveryResourceGroup `
-VmName $RecoveredVmName `
-PublicIpResourceGroupName $RecoveryResourceGroup `
-PublicIpName $FailoverPublicIp `
-Location $RecoveryLocation
Create the URL:
$RecoveredApplicationUrl = "http://$($RecoveredPublicIp.IpAddress):8080"
$RecoveredApplicationUrl
49. Validate the recovered application
$RecoveredApplicationDeadline = (
Get-Date
).AddMinutes(10)
$RecoveredApplicationResponse = $null
do {
try {
$RecoveredApplicationResponse = Invoke-WebRequest `
-Uri $RecoveredApplicationUrl `
-TimeoutSec 20
}
catch {
Write-Host `
"Waiting for the recovered application..."
Start-Sleep -Seconds 15
}
}
until (
$RecoveredApplicationResponse -or
(Get-Date) -ge $RecoveredApplicationDeadline
)
if (-not $RecoveredApplicationResponse) {
throw "The recovered application did not become reachable."
}
Validate the page:
if (
$RecoveredApplicationResponse.Content -notmatch
"Azure Site Recovery Deep Dive"
) {
throw "The recovered application content is incorrect."
}
if (
$RecoveredApplicationResponse.Content -notmatch
"Data disk"
) {
throw "The recovered data-disk marker is missing."
}
Write-Host `
"PASS: The workload is running in North Europe." `
-ForegroundColor Green
50. Validate the recovered data disk
$RecoveredGuestValidation = Invoke-AzVMRunCommand `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName `
-CommandId "RunShellScript" `
-ScriptString @'
set -e
echo "=== VM identity ==="
hostname
echo
echo "=== Azure Site Recovery application ==="
curl -fsS http://127.0.0.1:8080
echo
echo "=== Data-disk mount ==="
findmnt /srv/asrdata
echo
echo "=== Data-disk marker ==="
cat /srv/asrdata/site-recovery-marker.txt
echo
echo "=== Managed disks ==="
lsblk -f
'@
Display the result:
$RecoveredGuestValidation.Value.Message
51. Commit the failover
Only commit after the recovered workload has been validated.
$CommitFailoverJob = Start-AzRecoveryServicesAsrCommitFailoverJob `
-ReplicationProtectedItem $ReplicationProtectedItem
Wait for completion:
Wait-AsrJob `
-Job $CommitFailoverJob `
-Operation "Commit regional failover" `
-TimeoutMinutes 120 `
-PollSeconds 30
A commit accepts the selected recovery point and removes the previous failover recovery-point choices. Reprotection can begin after the failover has been committed.
52. Refresh the post-failover state
$ReplicationProtectedItem = Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $SourceProtectionContainer |
Where-Object {
$_.FriendlyName -eq $SourceVmName
} |
Select-Object -First 1
Display the state:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
ProtectionState,
ReplicationHealth,
ActiveLocation,
AllowedOperations |
Format-List
The active workload is now in North Europe.
53. Failback planning
Failback is not a single operation.
It consists of:
Commit the regional failover
↓
Confirm the original region is healthy
↓
Confirm the original VM is stopped
↓
Use a cache account in the current source region
↓
Reprotect from North Europe to West Europe
↓
Wait for reverse replication to become healthy
↓
Select a reverse recovery point
↓
Fail over from North Europe to West Europe
↓
Validate the original-region workload
↓
Commit the failback
↓
Reprotect again toward North Europe
The original source VM must remain stopped before a failback because Site Recovery synchronizes changes from the recovery-region VM back to the source-region disks.
54. Verify the failback prerequisites
Confirm the original VM remains stopped:
Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-Status |
Select-Object `
Name,
Location,
PowerState
Confirm the recovery-region cache account exists:
$RecoveryCacheStorageAccount = Get-AzStorageAccount `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveryCacheStorageName
$RecoveryCacheStorageAccount |
Select-Object `
StorageAccountName,
Location,
Kind,
Sku |
Format-List
Confirm the reverse container mapping:
$RecoveryToSourceMapping = Get-AzRecoveryServicesAsrProtectionContainerMapping `
-ProtectionContainer $RecoveryProtectionContainer `
-Name $RecoveryToSourceMappingName
$RecoveryToSourceMapping |
Select-Object `
Name,
PolicyFriendlyName,
State |
Format-List
Confirm the reverse network mapping:
Get-AzRecoveryServicesAsrNetworkMapping `
-Fabric $RecoveryFabric |
Where-Object {
$_.Name -eq $RecoveryToSourceNetworkMappingName
} |
Select-Object `
Name,
PrimaryNetworkFriendlyName,
RecoveryNetworkFriendlyName |
Format-List
55. Optional: start reverse replication
This operation can take as long as the initial replication.
$ReprotectJob = Update-AzRecoveryServicesAsrProtectionDirection `
-AzureToAzure `
-ReplicationProtectedItem $ReplicationProtectedItem `
-ProtectionContainerMapping $RecoveryToSourceMapping `
-LogStorageAccountId $RecoveryCacheStorageAccount.Id `
-RecoveryResourceGroupId $SourceResourceGroupObject.ResourceId
Wait for the reprotection job:
Wait-AsrJob `
-Job $ReprotectJob `
-Operation "Reprotect North Europe VM toward West Europe" `
-TimeoutMinutes 240 `
-PollSeconds 60
56. Optional: wait for reverse replication
After reprotection, refresh the protected item.
$ReverseReplicationDeadline = (
Get-Date
).AddHours(4)
do {
$ReplicationProtectedItem = Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $RecoveryProtectionContainer |
Where-Object {
$_.FriendlyName -eq $SourceVmName
} |
Select-Object -First 1
if ($ReplicationProtectedItem) {
Write-Host (
"{0:u} ProtectionState={1}, Health={2}, ActiveLocation={3}" -f `
(Get-Date),
$ReplicationProtectedItem.ProtectionState,
$ReplicationProtectedItem.ReplicationHealth,
$ReplicationProtectedItem.ActiveLocation
)
}
$ReverseReplicationReady = (
$ReplicationProtectedItem -and
$ReplicationProtectedItem.ProtectionState -eq "Protected" -and
$ReplicationProtectedItem.ReplicationHealth -eq "Normal"
)
if (-not $ReverseReplicationReady) {
Start-Sleep -Seconds 60
}
}
until (
$ReverseReplicationReady -or
(Get-Date) -ge $ReverseReplicationDeadline
)
if (-not $ReverseReplicationReady) {
throw "Reverse replication did not reach the protected state."
}
57. Optional: inspect reverse recovery points
$ReverseRecoveryPoints = @(
Get-AzRecoveryServicesAsrRecoveryPoint `
-ReplicationProtectedItem $ReplicationProtectedItem |
Sort-Object RecoveryPointTime
)
$ReverseRecoveryPoints |
Select-Object `
RecoveryPointType,
RecoveryPointTime |
Format-Table
58. Optional: execute the failback
Stop the recovery-region VM:
Stop-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName `
-Force
Select the latest reverse recovery point:
$LatestReverseRecoveryPoint = $ReverseRecoveryPoints[-1]
Start the reverse failover:
$FailbackJob = Start-AzRecoveryServicesAsrUnplannedFailoverJob `
-ReplicationProtectedItem $ReplicationProtectedItem `
-Direction RecoveryToPrimary `
-RecoveryPoint $LatestReverseRecoveryPoint
Wait for failback:
Wait-AsrJob `
-Job $FailbackJob `
-Operation "Fail back workload to West Europe" `
-TimeoutMinutes 180 `
-PollSeconds 30
Validate the source-region VM before committing:
Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName `
-Status |
Select-Object `
Name,
Location,
PowerState,
ProvisioningState
Validate the application:
$FailbackResponse = Invoke-WebRequest `
-Uri $SourceApplicationUrl `
-TimeoutSec 30
if (
$FailbackResponse.Content -notmatch
"Azure Site Recovery Deep Dive"
) {
throw "The failed-back application did not return the expected content."
}
Write-Host `
"PASS: The workload is running in West Europe again." `
-ForegroundColor Green
Commit the failback:
$CommitFailbackJob = Start-AzRecoveryServicesAsrCommitFailoverJob `
-ReplicationProtectedItem $ReplicationProtectedItem
Wait for the commit:
Wait-AsrJob `
-Job $CommitFailbackJob `
-Operation "Commit failback to West Europe" `
-TimeoutMinutes 120
After the failback commit, reprotect the workload again from West Europe toward North Europe to restore the original disaster recovery posture.
59. Monitor Site Recovery jobs
Display recent Site Recovery jobs:
Get-AzRecoveryServicesAsrJob `
-StartTime (
Get-Date
).AddDays(-1) |
Select-Object `
DisplayName,
JobType,
State,
StateDescription,
StartTime,
EndTime,
TargetObjectName |
Sort-Object StartTime -Descending |
Format-Table -AutoSize
Look for operations such as:
Enable protection
Initial replication
Test failover
Test failover cleanup
Failover
Commit
Reprotect
60. Troubleshooting
The Recovery Services vault cannot protect the source VM
Check the regions:
$SourceVm = Get-AzVM `
-ResourceGroupName $SourceResourceGroup `
-Name $SourceVmName
$Vault = Get-AzRecoveryServicesVault `
-ResourceGroupName $VaultResourceGroup `
-Name $VaultName
[pscustomobject]@{
SourceVmRegion = $SourceVm.Location
VaultRegion = $Vault.Location
}
The vault must not be in the same region as the protected source VM for this regional replication scenario.
Fabric creation fails
Display the Azure location names:
Get-AzLocation |
Where-Object {
$_.Location -in @(
$SourceLocation
$RecoveryLocation
)
} |
Select-Object `
Location,
DisplayName |
Format-Table
List existing fabrics:
Get-AzRecoveryServicesAsrFabric |
Select-Object `
FriendlyName,
Location,
FabricType |
Format-Table
Only one Azure fabric is required for each region inside the vault.
The replication-protected item does not appear
Check the enable-replication job:
Get-AzRecoveryServicesAsrJob `
-StartTime (
Get-Date
).AddHours(-6) |
Where-Object {
$_.JobType -match "Protection|Replication"
} |
Select-Object `
DisplayName,
JobType,
State,
StateDescription,
Errors |
Format-List
Check the source container:
Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $SourceProtectionContainer |
Select-Object `
FriendlyName,
ProtectionState,
ReplicationHealth |
Format-Table
Initial replication remains in progress
Check replication health:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
ProtectionState,
ProtectionStateDescription,
ReplicationHealth,
ActiveLocation |
Format-List
Check the cache account region:
$SourceCacheStorageAccount |
Select-Object `
StorageAccountName,
Location,
Kind,
Sku |
Format-List
The source cache account must be located in the source VM region.
Check the protected disks:
$SourceVm.StorageProfile |
Format-List
Common causes include:
an unsupported disk type;
the cache account being in the wrong region;
storage-account firewall restrictions;
insufficient subscription quota;
insufficient target-region compute capacity;
an unsupported VM configuration;
an Azure Policy blocking replica resources;
missing permissions.
No recovery points are available
Refresh the protected item:
$ReplicationProtectedItem = Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $SourceProtectionContainer |
Where-Object {
$_.FriendlyName -eq $SourceVmName
} |
Select-Object -First 1
Request recovery points:
Get-AzRecoveryServicesAsrRecoveryPoint `
-ReplicationProtectedItem $ReplicationProtectedItem |
Select-Object `
RecoveryPointType,
RecoveryPointTime |
Format-Table
Wait until initial replication and recovery-point generation are complete.
The test VM is created in the wrong subnet
Check the test VNet:
Get-AzVirtualNetwork `
-ResourceGroupName $TestResourceGroup `
-Name $TestVnetName |
Select-Object `
Name,
AddressSpace,
Subnets |
Format-List
The test VNet should contain a subnet named:
snet-workload
Site Recovery attempts to use a subnet with the same name. When no matching subnet exists, it can select the first subnet alphabetically.
The test or recovered VM has no public IP
This is expected.
Run the helper again:
Add-AsrWorkshopPublicIp `
-VmResourceGroupName $RecoveryResourceGroup `
-VmName $RecoveredVmName `
-PublicIpResourceGroupName $RecoveryResourceGroup `
-PublicIpName $FailoverPublicIp `
-Location $RecoveryLocation
Check the NIC:
$RecoveredNic |
Select-Object `
Name,
IpConfigurations |
Format-List
The application is unavailable after failover
Check the VM power state:
Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName `
-Status |
Select-Object `
Name,
PowerState,
ProvisioningState
Run an internal validation:
Invoke-AzVMRunCommand `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName `
-CommandId "RunShellScript" `
-ScriptString @'
systemctl is-active nginx
ss -lntp | grep ':8080' || true
curl -v http://127.0.0.1:8080
findmnt /srv/asrdata
cat /srv/asrdata/site-recovery-marker.txt
'@ |
Select-Object -ExpandProperty Value
Check the recovery NSG:
Get-AzNetworkSecurityGroup `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveryNsgName |
Select-Object `
Name,
SecurityRules |
Format-List
Check:
VM power state;
subnet placement;
NSG rules;
Nginx state;
data-disk mount;
public-IP association;
guest operating-system firewall.
The data disk is missing after failover
Inspect the recovered VM:
Get-AzVM `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName |
Select-Object -ExpandProperty StorageProfile |
Format-List
Inspect the guest disks:
Invoke-AzVMRunCommand `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveredVmName `
-CommandId "RunShellScript" `
-ScriptString @'
lsblk -f
cat /etc/fstab
findmnt /srv/asrdata || true
'@ |
Select-Object -ExpandProperty Value
Confirm that the data disk was included in:
$DiskReplicationConfigurations
PowerShell does not automatically include every VM disk unless each disk is represented in the replication configuration.
Test-failover cleanup fails
Check the active test-failover state:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
TestFailoverState,
AllowedOperations |
Format-List
List recent jobs:
Get-AzRecoveryServicesAsrJob `
-StartTime (
Get-Date
).AddHours(-6) |
Where-Object {
$_.JobType -match "Test"
} |
Select-Object `
DisplayName,
JobType,
State,
StateDescription,
Errors |
Format-List
Do not manually delete the test VM before attempting Site Recovery cleanup unless the cleanup workflow is already broken.
Regional failover cannot start
Check the protected-item operations:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
ProtectionState,
ReplicationHealth,
AllowedOperations |
Format-List
Check recovery points:
Get-AzRecoveryServicesAsrRecoveryPoint `
-ReplicationProtectedItem $ReplicationProtectedItem |
Select-Object `
RecoveryPointType,
RecoveryPointTime |
Format-Table
Check whether a Site Recovery job is already running:
Get-AzRecoveryServicesAsrJob `
-StartTime (
Get-Date
).AddHours(-2) |
Where-Object {
$_.State -in @(
"InProgress"
"NotStarted"
)
} |
Select-Object `
DisplayName,
JobType,
State,
TargetObjectName |
Format-Table
Reprotect fails
Confirm that the failover has been committed:
$ReplicationProtectedItem |
Select-Object `
FriendlyName,
ProtectionState,
AllowedOperations,
ActiveLocation |
Format-List
Confirm that the recovery-region cache account exists:
Get-AzStorageAccount `
-ResourceGroupName $RecoveryResourceGroup `
-Name $RecoveryCacheStorageName
Confirm the reverse mapping:
Get-AzRecoveryServicesAsrProtectionContainerMapping `
-ProtectionContainer $RecoveryProtectionContainer `
-Name $RecoveryToSourceMappingName
Confirm that the original source-region VM is stopped.
61. Production considerations
The workshop protects one Linux VM. A production recovery design requires additional planning.
Application dependencies
Document:
databases;
identity services;
DNS;
load balancers;
private endpoints;
storage dependencies;
certificates;
secrets;
monitoring agents;
external integrations.
A VM can start successfully while the application remains unavailable.
Recovery plans
Use recovery plans to:
group multiple VMs;
define startup order;
add manual actions;
call automation runbooks;
coordinate database and application tiers;
document validation gates.
Network recovery
Site Recovery does not automatically reproduce every networking component.
Pre-create and validate:
VNets;
subnets;
NSGs;
route tables;
firewalls;
load balancers;
application gateways;
private DNS zones;
private endpoints;
public IP addresses.
IP-address planning
Decide whether recovered workloads require:
preserved private IP addresses;
new regional private IP addresses;
DNS updates;
Traffic Manager changes;
Front Door origin changes;
load-balancer backend updates.
Vault placement
A vault in the recovery region remains manageable during a source-region outage.
A vault in a third region can continue replication during a vault-region outage, but failover and failback management operations are unavailable until the vault region recovers.
Recovery-point objectives
Recovery-point selection affects:
possible data loss;
recovery time;
application consistency;
recovery-point availability;
operational validation.
Use application-consistent recovery points for workloads that require coordinated in-guest consistency.
DR testing
Run test failovers regularly.
A DR test should validate:
VM startup
Operating-system health
Application startup
Data consistency
Network placement
DNS resolution
Authentication
Monitoring
Security controls
User access
Operational runbooks
Cleanup
Commit discipline
Do not commit a failover until:
the VM is running;
data has been validated;
the application is reachable;
dependent services are healthy;
security controls are active;
the business owner accepts the recovery result.
Commit removes the ability to choose another recovery point from that failover.
Failback planning
Before failback:
confirm the source region is stable;
confirm quotas and capacity;
stop the original source VM;
create a cache account in the current source region;
verify reverse mappings;
complete reprotection;
confirm reverse replication health;
schedule application downtime;
validate the original-region network;
prepare a rollback decision.
62. Cleanup
The cleanup section permanently removes the workshop.
First, ensure that no test failover is active.
Set-AzRecoveryServicesAsrVaultContext `
-Vault $Vault
Refresh the protected item from either container:
$ReplicationProtectedItem = @(
Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $SourceProtectionContainer `
-ErrorAction SilentlyContinue
Get-AzRecoveryServicesAsrReplicationProtectedItem `
-ProtectionContainer $RecoveryProtectionContainer `
-ErrorAction SilentlyContinue
) |
Where-Object {
$_.FriendlyName -eq $SourceVmName
} |
Select-Object -First 1
Disable replication when the protected item still exists:
if ($ReplicationProtectedItem) {
$DisableReplicationJob = Remove-AzRecoveryServicesAsrReplicationProtectedItem `
-ReplicationProtectedItem $ReplicationProtectedItem `
-Force
Wait-AsrJob `
-Job $DisableReplicationJob `
-Operation "Disable Site Recovery replication" `
-TimeoutMinutes 180 `
-PollSeconds 30
}
Refresh the vault:
$Vault = Get-AzRecoveryServicesVault `
-ResourceGroupName $VaultResourceGroup `
-Name $VaultName `
-ErrorAction SilentlyContinue
Remove the Site Recovery vault and its lab dependencies:
if ($Vault) {
Remove-AzRecoveryServicesVault `
-Vault $Vault `
-Confirm:$false
}
Require explicit confirmation before deleting the resource groups:
$DeleteConfirmation = Read-Host @"
Type DELETE-ASR-LAB to remove all workshop resource groups.
"@
if ($DeleteConfirmation -ne "DELETE-ASR-LAB") {
throw "Cleanup cancelled."
}
Delete the source resource group:
Remove-AzResourceGroup `
-Name $SourceResourceGroup `
-Force `
-AsJob
Delete the recovery resource group:
Remove-AzResourceGroup `
-Name $RecoveryResourceGroup `
-Force `
-AsJob
Delete the test resource group:
Remove-AzResourceGroup `
-Name $TestResourceGroup `
-Force `
-AsJob
Delete the vault resource group:
Remove-AzResourceGroup `
-Name $VaultResourceGroup `
-Force `
-AsJob
Monitor the deletions:
do {
$RemainingGroups = @(
foreach ($ResourceGroupName in $WorkshopResourceGroups) {
Get-AzResourceGroup `
-Name $ResourceGroupName `
-ErrorAction SilentlyContinue
}
)
Write-Host (
"{0:u} Remaining resource groups: {1}" -f `
(Get-Date),
$RemainingGroups.Count
)
if ($RemainingGroups.Count -gt 0) {
$RemainingGroups |
Select-Object `
ResourceGroupName,
ProvisioningState |
Format-Table
Start-Sleep -Seconds 60
}
}
until ($RemainingGroups.Count -eq 0)
Display completion:
Write-Host `
"The Azure Site Recovery workshop was deleted." `
-ForegroundColor Green
Summary
This workshop created a complete Azure-to-Azure Site Recovery configuration:
Source VM in West Europe
│
├── Operating-system disk
├── Managed data disk
├── Nginx application
└── Replicated data marker
│
▼
Azure Site Recovery
│
┌─────────┴─────────┐
│ │
▼ ▼
Isolated test failover Regional failover
North Europe North Europe
│ │
▼ ▼
Temporary test VM Recovered production VM
│ │
▼ ▼
Validation Validation and commit
│ │
▼ ▼
Cleanup Reprotect and failback plan
The complete workflow was:
Create two regional networks
↓
Create an isolated test network
↓
Deploy the source VM
↓
Configure the application and data disk
↓
Create cache storage
↓
Create the Recovery Services vault
↓
Create fabrics and protection containers
↓
Create forward and reverse mappings
↓
Configure OS and data-disk replication
↓
Wait for initial replication
↓
Run an isolated test failover
↓
Validate the application and replicated data
↓
Clean up the test failover
↓
Stop the source VM
↓
Perform regional failover
↓
Validate the recovered workload
↓
Commit the failover
↓
Prepare reprotection and failback
↓
Delete the workshop
The central design principles are:
Place the Recovery Services vault outside the source VM region.
Prefer the intended recovery region for the vault.
Use a cache storage account in the current replication-source region.
Configure every managed disk that must be protected.
Pre-create recovery networking and security controls.
Use matching subnet names for predictable test failover.
Keep the test-failover network isolated.
Validate application state, not only VM power state.
Validate data-disk content after recovery.
Run a test failover before a real regional failover.
Stop the source workload before a controlled failover.
Validate the recovered workload before committing.
Treat commit as an irreversible recovery-point decision.
Reprotect before attempting failback.
Keep the original source VM stopped during reverse synchronization.
Test the complete DR procedure regularly.
Delete the billable workshop resources after testing.
Comments