Azure Site Recovery 復旧プランをカスタマイズして既存のLoad Balancerをアタッチする

プライマリリージョンの仮想マシンをセカンダリリージョンにフェールオーバーする際、Azure Site Recoveryの復旧プランを作成します。
この復旧プランを作成することで、複数の仮想マシンのフェールオーバーをグループ化し、タスクを自動化することにより、RTO (目標復旧時間) の短縮に繋がります。

https://learn.microsoft.com/ja-jp/azure/site-recovery/recovery-plan-overview

今回、備忘録として残すのは仮想マシンのフェールオーバー後に、セカンダリリージョンにあらかじめ配置してあるAzure Load Balancerに仮想マシンのNICをアタッチする工程を自動化する方法についてです。結論、復旧計画をカスタマイズすることで可能になります。

例として以下のような構成を想定します。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/07246a8f-9de6-41fc-0718-a1e77dcd8bcb.png)

リカバリーサービスコンテナーのポータル画面「レプリケートされたアイテム」 > 「ネットワーク」 からぱっと見は設定できそうだが、ここで設定することはできず...
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/7c99ab7b-0b70-372a-1011-cd4f132aa19c.png)

復旧プランにAzure Automation Runbookを追加し、仮想マシンのフェールーバーが終わった後にPowerShellを実行させることで設定できるようです。

https://learn.microsoft.com/ja-jp/azure/site-recovery/site-recovery-runbook-automation

## 以下手順
### 前提
- 各リージョンに必要なリソース郡がデプロイ済みであること
- リカバリーサービスコンテナーとAutomationアカウントがデプロイ済みであること
- 対応している仮想マシンのレプリケーションが有効化されていること。※詳細は以下

https://learn.microsoft.com/ja-jp/azure/site-recovery/azure-to-azure-tutorial-enable-replication

<details><summary>Recovery Services vaultsとAzure AutomationのTerraformコード参考</summary>

```terraform
data "azurerm_client_config" "current" {}

variable "resource_group_name" {
  type        = string
}

variable "location" {
  type        = string
}

resource "azurerm_recovery_services_vault" "vault" {
  name                = "rsv-test"
  location            = var.location
  resource_group_name = var.resource_group_name
  sku                 = "Standard"
  cross_region_restore_enabled = true
  soft_delete_enabled = false
}

resource "azurerm_automation_account" "automation" {
  name                = "test-automation-account"
  location            = var.location
  resource_group_name = var.resource_group_name
  sku_name            = "Basic"
  identity {
    type = "SystemAssigned"
  }
}

# AutomationAccountの環境変数にサブスクリプションIDとセカンダリリージョンのRG名とLoad Balancer名を設定しておく
resource "azurerm_automation_variable_string" "azureSubscriptionId" {
  name                    = "AzureSubscriptionId"
  resource_group_name     = var.resource_group_name
  automation_account_name = azurerm_automation_account.automation.name
  value                   = data.azurerm_client_config.current.subscription_id
}

resource "azurerm_automation_variable_string" "loadbalancerResourceGrupName" {
  name                    = "LoadBalancerResourceGroup"
  resource_group_name     = var.resource_group_name
  automation_account_name = azurerm_automation_account.automation.name
  value                   = "<SecondaryRegionResourceGroupName>"
}

resource "azurerm_automation_variable_string" "loadbalancerName" {
  name                    = "LoadBalancerName"
  resource_group_name     = var.resource_group_name
  automation_account_name = azurerm_automation_account.automation.name
  value                   = "<SecondaryRegionLoadBalancerName>"
}
```
</details>

### 手順1 PowerShellの作成・Runbookの追加
デプロイしたAutomatinonアカウントにRunbookを手動で追加していく。
調べていくと、Site Recovery用のRunbook ([PowerShellのサンプル](https://github.com/Azure/azure-quickstart-templates/tree/master/demos/asr-automation-recovery)) があったのでこれをそのまま利用できるかと思ったが、そうでもなかった。

https://learn.microsoft.com/en-us/samples/azure/azure-quickstart-templates/asr-automation-recovery/

`AzureRm` モジュールが廃止され、`Az`モジュールに移行されたこと。
また、従来の認証方法(Azure AutomationにおけるRun As Account)は2023年9月30日に廃止されているため、現在はマネージドIDで認証をおこなうようにPowerShellを修正する必要がある。

そこで、認証方法にはマネージドIDを使用し、Azモジュールを使用するように修正をおこなった。

<details><summary>修正したPowerShell</summary>

```powershell
# AzureSiteRecoveryから提供されるリカバリープランの情報を持つオブジェクト
param ( 
    [Object]$RecoveryPlanContext 
)
# RecoveryPlanContextの内容を出力
Write-output $RecoveryPlanContext
# エラー発生時にスクリプトを停止する
$ErrorActionPreference = "Stop"
# フェイルオーバーの方向がPrimaryToSecondaryでなければスクリプトを停止
if ($RecoveryPlanContext.FailoverDirection -ne "PrimaryToSecondary") {
    Write-Output "Failover Direction is not Azure, and the script will stop."
}
else {
		# リカバリープラン内のVMの情報を取得（VMのGUID）
    $VMinfo = $RecoveryPlanContext.VmMap | Get-Member | Where-Object MemberType -EQ NoteProperty | select -ExpandProperty Name
    Write-Output ("Found the following VMGuid(s): `n" + $VMInfo)
     # 複数のVMが存在するか、単一のVMかを判定し、ログ出力
    if ($VMInfo -is [system.array]) {
        $VMinfo = $VMinfo[0]
        Write-Output "Found multiple VMs in the Recovery Plan"
    }
    else {
        Write-Output "Found only a single VM in the Recovery Plan"
    }

    Try {
        # マネージドIDを使ってAzureにログイン
        "Logging in to Azure using Managed Identity..."
        Connect-AzAccount -Identity
				# サブスクリプションIDを取得し、そのサブスクリプションに切り替える
        "Selecting Azure subscription..."
        $SubscriptionId = (Get-AzContext).Subscription.Id
        Select-AzSubscription -SubscriptionId $SubscriptionId
    }
    Catch {
		    # ログインに失敗した場合、エラーメッセージを出力してスクリプトを停止
        $ErrorMessage = 'Login to Azure subscription failed.'
        $ErrorMessage += " `n"
        $ErrorMessage += 'Error: '
        $ErrorMessage += $_
        Write-Error -Message $ErrorMessage `
                      -ErrorAction Stop
    }

    Try {
		    # Automationアカウントで設定した環境変数を取得
        $LBNameVariable = "LoadBalancerName"  
        $LBRgVariable = "LoadBalancerResourceGroup" 
        $LBName = Get-AutomationVariable -Name $LBNameVariable    
        $LBRgName = Get-AutomationVariable -Name $LBRgVariable
        
        # 取得したロードバランサー名とリソースグループ名を使ってロードバランサーの情報を取得
        $LoadBalancer = Get-AzLoadBalancer -Name $LBName -ResourceGroupName $LBRgName        
    }
    Catch {
		     # ロードバランサー情報の取得に失敗した場合、エラーメッセージを出力してスクリプトを停止
        $ErrorMessage = 'Failed to retrieve Load Balancer info from Automation variables.'
        $ErrorMessage += " `n"
        $ErrorMessage += 'Error: '
        $ErrorMessage += $_
        Write-Error -Message $ErrorMessage `
                       -ErrorAction Stop
    }

    Try {
		    # RecoveryPlanContext内のVM情報を再度取得し、VMごとに処理を行う
        $VMinfo = $RecoveryPlanContext.VmMap | Get-Member | Where-Object MemberType -EQ NoteProperty | select -ExpandProperty Name
        $VMs = $RecoveryPlanContext.VmMap
        $vmMap = $RecoveryPlanContext.VmMap
        
        # すべてのVMに対してループ処理を実行
        foreach ($VMID in $VMinfo) {
            $VM = $vmMap.$VMID
            Write-Output $VM.ResourceGroupName
            Write-Output $VM.RoleName
             # VMのリソースグループ名と名前を使ってVMの情報を取得
            $AzureVm = Get-AzVm -ResourceGroupName $VM.ResourceGroupName -Name $VM.RoleName    
            
            # VMにアベイラビリティセットがあるかを確認し、ログに出力
            If ($AzureVm.AvailabilitySetReference -eq $null) {
                Write-Output "No Availability Set is present for VM: `n" $AzureVm.Name
            }
            else {
                Write-Output "Availability Set is present for VM: `n" $AzureVm.Name
            }
            # VMのNICを取得し、ロードバランサーのバックエンドプールに追加
            $ARMNic = Get-AzResource -ResourceId $AzureVm.NetworkProfile.NetworkInterfaces[0].Id
            $Nic = Get-AzNetworkInterface -Name $ARMNic.Name -ResourceGroupName $ARMNic.ResourceGroupName
            $Nic.IpConfigurations[0].LoadBalancerBackendAddressPools.Add($LoadBalancer.BackendAddressPools[0]);        
            
            # NICの設定を更新する
            $Nic | Set-AzNetworkInterface    
            Write-Output "Done configuring Load Balancing for VM" $AzureVm.Name    
        }
    }
    Catch {
    # VMをロードバランサーに関連付ける処理に失敗した場合、エラーメッセージを出力してスクリプトを停止
        $ErrorMessage = 'Failed to associate the VM with the Load Balancer.'
        $ErrorMessage += " `n"
        $ErrorMessage += 'Error: '
        $ErrorMessage += $_
        Write-Error -Message $ErrorMessage `
                       -ErrorAction Stop
    }
}

```
</details>

Automatinonアカウントのシステム割り当てマージドIDに「仮想マシン共同作成者」ロールを割り当てとく。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/1d5d707e-e99b-ba8f-94ef-d238050c5a15.png)

PowerShellをインポートする。ポータル画面の、[ Automatinonアカウント ] > [ Runbook ] > [ Runbookのインポート ] > [ PowerShellをインポート ]からインポートする。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/535d49b0-d909-fe2a-9136-2d504188143b.png)

インポートができたら、保存して「公開」をする。作成状態が「発行済」となれば準備完了。

### 手順2 復旧プランを作成し、フェールオーバーを実行する

それでは、復旧プランを作成し、フェールオーバーを実行してみる。
[ Recovery Services コンテナー ] > [ Recovery Plan (Site Recovery) ] > [ 復旧計画を作成 ]

https://learn.microsoft.com/ja-jp/azure/site-recovery/site-recovery-create-recovery-plans

[ 復旧プラン ] > [ カスタマイズ ] から`グループ1`の`事後アクションを追加`を選択する。

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/500d11b8-0e04-7864-f190-957404f3f367.png)

アクションの挿入で作成したAutomationアカウントとRunbook名を選択する。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/835565ab-5b7e-87db-bc22-748b9bae0bce.png)

以下のようにアクションが`事後ステップ`として追加されればOK👌
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/f57e827e-0753-1b09-de40-36dd5f2c4ca5.png)

 [ Recovery Plan (Site Recovery) ] > [ テストフェールオーバー or フェールオーバー ] を実行し、動作を確認する。
 
https://learn.microsoft.com/ja-jp/azure/site-recovery/site-recovery-test-failover-to-azure

全てのジョブが成功した事を確認。
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/e06ce78f-39ce-b5a3-6978-4b1b5c33d158.png)

 セカンダリリージョンに配置していたLoad Balancerのバックエンドプールにフェールオーバーした仮想マシンのNICがアタッチできていることを確認できた。
 ![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/f6509eb1-3a60-5cbc-6a5c-e3f72ecb5e61.png)

## さいごに
当初、ポータル画面から設定できるものだと思いこんでいました。しかし、カスタムアクションとしてPowerShellを実行できる事を覚えたので、フェールオーバー後の設定で何か他にも自動化できそうだなと思いました。

以上、拙い内容で恐縮ですが、どなたかの参考になりましたら幸いです。
最後までご覧頂きありがとうございました🙏