Azure Site Recovery を Terraform で作成

## ◎概要
Azure のプロジェクトで、DR対策として Azure Site Recoveryを使用して
仮想マシン（以降、VM）のバックアップをDRサイト側にレプリケーションを
する事にした。

Terraform で作成したので、Azure Site Recoveryを作成する際の注意点などを
含めて備忘録として残しておこうと思います。


## ◎VMのDR対策
Azure において VMの災害対策として

 Azure Backup と Azure Site Recoveryを使用した 2つの方法があります。


#### Azure Backup

Azure Backupにおいては、Recovery Service コンテナーをGRSで配置する事により
バックアップデータをリージョン間でレプリケーションする。

デメリットとしては、西側にレプリケーションされるのに最大で12時間程掛かるとの事。
システム復旧の RTO 短く定義されている場合には、Backup は災害対策ソリューションと
しては成り立たないと考えるべきである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689346/6c7442fb-ffdd-e7ea-bbca-47c5d9731a28.png)


#### Azure Site Recovery

Site Recoveryにおいては、クラッシュ整合性復旧ポイントが５分間隔で作成される為、
理論上の最小 RPO は５分であり、
復旧ポイントは最大で 12日間まで保持できる為、データの整合性や運用上の適切な状態で復旧することができる。

また、テストフェイルオーバーにより気軽に避難訓練や、メンテナンスの回避などを
目的としたフェイルオーバーを実施できる事もメリットである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689346/c7bcb271-a0ea-a156-36bc-34a0b3ca2d32.png)

<br>

ここでは Azure Backup と Azure Site Recovery 紹介しましたが、
災害対策においてコレらのサービスが必須というわけではありません。というより、
そもそもリストアやフェイルオーバーといったような「滅多にやらないオペレーション」を
被災時のような特殊な状況化で行うのは極力避けるべきです（そういった意味では、
Site Recovery の テストフェールオーバーは、ほぼ本番のフェールオーバーと同じ挙動なので
お勧めの機能です）

システムやサーバーの特性や要件に合わせて、より簡易な、あるいは低コストな、もしくは
高度なソリューションも検討してください。


## ◎アーキテクチャ

アーキテクチャは、下記の公式ドキュメントに掲載されている通りです。
今回は、プライベート エンドポイントを使用した レプリケーション方法で実装しています。
実際の要件には、さらにAzure Firewallをデフォルトゲートウェイにして、
直接インターネットへのアクセスを制限しています。
なので、Site Recoveryを使用する上で、Firewall Policyで開放しなければならない
サービスタグがあったりします。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689346/cc3f0cdf-075e-f79f-1886-bd9cf8453513.png)
https://learn.microsoft.com/ja-jp/azure/site-recovery/azure-to-azure-how-to-enable-replication-private-endpoints


## ◎Terraformのコード
ソースのVM関係（VM本体、インターフェイス、OSディスク、データディスク）のコードは省いていますので、適宜各自の環境に合わせて頂ければと思います。

メインに使用するリージョンとして東日本リージョン、
DR側で使用するリージョンとして西日本リージョン、
を使用します。また、下記に各リージョンで作成するリソースを記載します。


##### 東日本リージョンに作成するリソース
>・リソースグループ
・Vnet
・サブネット
・ソースのVM関係
・キャッシュ用ストレージアカウント
・プライベートエンドポイント（キャッシュストレージアカウント用）
・プライベートエンドポイント（西側のRecovery Service コンテナ用）


##### 西側リージョンに作成するリソース
>・リソースグループ
・Vnet
・サブネット
・Recovery Service コンテナ
・Azure Site Recovery

##### 注意事項
>・Recovery Service コンテナにプライベートリンク接続する際は、Recovery Service コンテナが作成したばかりものでがないとエラーになります。要は、元々作成してあり、一度でもバックアップデータを保存したりした場合、プライベートリンクの接続が出きませんので注意してください。保存データを全て削除した場合でもエラーになったので、この場合は再作成するしかないと思われます。

>・データのレプリケーションをする際に、VMに付随してデータディスクを追加した場合、必ずデータディスクの初期化を行ってください。初期化をしないと、OSディスクだけレプリケーションされて、データディスクの方はレプリケーションされないのでご注意ください。
>
>初期化の仕方は、簡単に説明するとWindows VMにログインして、
スタートボタンを右クリックして、「ディスクの管理」をクリックします。
ディスクの管理が起動しますので、ここで オフライン で 未割り当て のディスクがあるのでこれをオンラインに変更すればいいだけです。
下記の公式ドキュメント等を参考にしてみて下さい。
https://learn.microsoft.com/ja-jp/azure/virtual-machines/windows/attach-managed-disk-portal

>・暗号化についてですが、OSディスク及びデータディスクは暗号化していると思います。Azure側で管理しているマネージドキーを使用している場合、特に考慮する事項はありません。しかし、ADE暗号化や顧客管理の暗号化キーでディスクを暗号化している場合は考慮する必要がありますので、そこは公式ドキュメントやAzureサポートに問い合わせてみる事を強くお勧めします。


<br>


```.tfvars
environment    = "dev"
main-site      = "main"
dr-site        = "dr"
```

```.variable.tf
variable "environment" {}
variable "main-site" {}
variable "dr-site" {}
```

<br>
<br>

>>リソースグループとVnetは、各リージョンで 1つずつ作成。
サブネットは、東側はプライベートリンクのインターフェイスを設置する為に作成。西側は、VMがフェールオバーする先の場所を指定する為に作成。

```.resource_group.tf
resource "azurerm_resource_group" "east" {
  name     = "${var.environment}-${var.main-site}-east-rg"
  location = "japaneast"
}

resource "azurerm_resource_group" "west" {
  name     = "${var.environment}-${var.dr-site}-west-rg"
  location = "japanwest"
}

resource "azurerm_virtual_network" "east" {
  name                = "east-network"
  resource_group_name = azurerm_resource_group.east.name
  address_space       = ["192.168.1.0/24"]
  location            = "japaneast"
}

resource "azurerm_virtual_network" "west" {
  name                = "west-network"
  resource_group_name = azurerm_resource_group.west.name
  address_space       = ["192.168.2.0/24"]
  location            = "japanwest"
}

resource "azurerm_subnet" "east" {
  name                 = "east-subnet"
  resource_group_name  = azurerm_resource_group.east.name
  virtual_network_name = azurerm_virtual_network.east.name
  address_prefixes     = ["192.168.1.0/24"]
}

resource "azurerm_subnet" "west" {
  name                 = "west-subnet"
  resource_group_name  = azurerm_resource_group.west.name
  virtual_network_name = azurerm_virtual_network.west.name
  address_prefixes     = ["192.168.2.0/24"]
}

```

<br>
<br>

>>・Recovery Service コンテナは、DR側である西リージョンに作成。
・storage_mode_typeは、ローカル冗長です。西リージョンはゾーン冗長にしか対応していないので必然的にこれしか選択出来ません。
・public_network_access_enabledは、セキュリティ観点からfalseがお勧めです。
公共のアクセスを禁止したからといって、Recovery Service コンテナとプライベートリンク接続させたり等はする必要ないのでご安心下さい。そもそも、レプリケーションデータ自体がRecovery Service コンテナ内に保存されている訳ではないので。
・マネージドIDを付与する必要があるので、type = "SystemAssigned"
使用用途は、東側で作成するキャッシュストレージアカウントへのアクセスを許可する為、このマネージドIDにロールを付与します。

```.recovery_vault.tf

// Create the Recovery Services Vault in JapanWest.
resource "azurerm_recovery_services_vault" "dr_vault" {

  name                = "${var.environment}-${var.dr-site}-recovery-vault"
  location            = "japanwest"
  resource_group_name = azurerm_resource_group.west.name
  sku                 = "Standard"
  storage_mode_type   = "LocallyRedundant"

  public_network_access_enabled = false
  soft_delete_enabled          = true
  cross_region_restore_enabled = false
  // マネージドID付与
  identity {
    type = "SystemAssigned"
  }
}
```

<br>
<br>

>>・こちらの、Site Recovery関係のリソースは全て西側のリソースグループで管理して作成するものになります。しかし、プライマリーのファブリックやコンテナは東側のリージョンで作成されるものになります。

>>Azureポータルで作成すると、ほぼ気にする必要はないのですが、terraform や powershellで作成する場合は、ファブリックやコンテナなど、なんだかよくわからないものがでてきます。ここで少し単語の説明をさせて下さい。
>>
>>・プライマリ/セカンダリ ファブリック
ファブリックとは、Azure Site Recoveryがレプリケーションする為に両リージョンに設置したエッジポイント（Azureリージョン）を指しています。つまり、プライマリファブリックオブジェクトは、保護対象となっている仮想マシンが属する Azure リージョンを表すために使用され、セカンダリファブリックオブジェクトはターゲットとなる Azure リージョンを表すために使用されるオブジェクトとなります。

>>・プライマリ/セカンダリ ファブリック内のプロジェクション コンテナー
プロテクションコンテナーは、ファブリック内でレプリケートされる項目 (仮想マシン) をグループ化するために使用されます。
>>
>>・レプリケーション ポリシー
復旧ポイントのリテンション履歴と、アプリ整合性スナップショットの頻度の設定を定義するために使用されます。つまり、データの保持期間等を決めるものです。
>>
>>・保護コンテナー マッピング
プライマリ保護コンテナーを、ターゲット保護コンテナーおよびレプリケーションポリシーと関連付けるために作成します。
>>
>>・ネットワーク マッピング
プライマリ リージョン内の仮想ネットワークを、レプリケーション先であるターゲットリージョン内の仮想ネットワークにマップするため使用されます。

>>注意点
・プライマリーファブリックリソースの depends_onは、今回の Site recoveryのリソースを纏めて作成しようとすると、セカンダリーのファブリックが先に作成できていないと、エラーになるようなので、セカンダリーファブリックが作成した後に作成されるようする為、入れています。

```site_recovery.tf

### Create Site Recovery Primary Fabric Object in Recovery Services container of DR Site.
## The fabric object represents the Azure region 
## to which the virtual machine being protected by the Recovery Services container belongs.
resource "azurerm_site_recovery_fabric" "primary" {
  name                = "${var.environment}-${var.dr-site}-primary-fabric"
  resource_group_name = azurerm_resource_group.west.name
  recovery_vault_name = azurerm_recovery_services_vault.dr_vault.name
  location            = "japaneast" # Primary Region

  depends_on = [
    azurerm_site_recovery_fabric.secondary,
    azurerm_site_recovery_protection_container.secondary,
    azurerm_site_recovery_replication_policy.vm_policy
  ]
}

## Create Site Recovery Secondary Fabric Object in Recovery Services container of DR Site.
resource "azurerm_site_recovery_fabric" "secondary" {

  name                = "${var.environment}-${var.dr-site}-secondary-fabric"
  resource_group_name = azurerm_resource_group.west.name
  recovery_vault_name = azurerm_recovery_services_vault.dr_vault.name
  location            = "japanwest"
}

# ### Create a Site Recovery Protection Container in the Primary Fabric.
# ## Site Recovery Protection Container is used as a container for Replicated VMs.
resource "azurerm_site_recovery_protection_container" "primary" {

  name                 = "${var.environment}-${var.dr-site}-primary-container"
  resource_group_name  = azurerm_resource_group.west.name
  recovery_vault_name  = azurerm_recovery_services_vault.dr_vault.name
  recovery_fabric_name = azurerm_site_recovery_fabric.primary.name
}

## Create a Site Recovery Protection Container in the Secondary Fabric.
resource "azurerm_site_recovery_protection_container" "secondary" {

  name                 = "${var.environment}-${var.dr-site}-secondary-container"
  resource_group_name  = azurerm_resource_group.west.name
  recovery_vault_name  = azurerm_recovery_services_vault.dr_vault.name
  recovery_fabric_name = azurerm_site_recovery_fabric.secondary.name
}

## Create a Aite Recovery Replication Policy for VMs.
resource "azurerm_site_recovery_replication_policy" "vm_policy" {

  name                                                 = "${var.environment}-${var.dr-site}-vm-policy"
  resource_group_name                                  = azurerm_resource_group.west.name
  recovery_vault_name                                  = azurerm_recovery_services_vault.dr_vault.name
  recovery_point_retention_in_minutes                  = 48 * 60 # two day
  application_consistent_snapshot_frequency_in_minutes = 4 * 60  # four hours
}

## Create Container-Maping the Primary Protection Container with the Recovery Services container and Replication Policy.
resource "azurerm_site_recovery_protection_container_mapping" "container-mapping" {

  name                                      = "${var.environment}-${var.dr-site}-container-mapping"
  resource_group_name                       = azurerm_resource_group.west.name
  recovery_vault_name                       = azurerm_recovery_services_vault.dr_vault.name
  recovery_fabric_name                      = azurerm_site_recovery_fabric.primary.name
  recovery_source_protection_container_name = azurerm_site_recovery_protection_container.primary.name
  recovery_target_protection_container_id   = azurerm_site_recovery_protection_container.secondary.id
  recovery_replication_policy_id            = azurerm_site_recovery_replication_policy.vm_policy.id
}


### Create Maping Virtual Network in the Primary Region to Virtual Network in the Secondary(DR) Region.
## Network-Mapping maps to Secondary Virtual nNtwork to which the virtual Machines in the Primary Virtual Network fail over
resource "azurerm_site_recovery_network_mapping" "network-mapping" {

  name                        = "${var.environment}-${var.dr-site}-network-mapping"
  resource_group_name         = azurerm_resource_group.west.name
  recovery_vault_name         = azurerm_recovery_services_vault.dr_vault.name
  source_recovery_fabric_name = azurerm_site_recovery_fabric.primary.name
  target_recovery_fabric_name = azurerm_site_recovery_fabric.secondary.name
  source_network_id           = azurerm_virtual_network.east.id
  target_network_id           = azurerm_virtual_network.west.id
}

```

<br>
<br>

>>・キャッシュ用ストレージアカウントは、メイン側である東側リージョンに作成。
・replication_typeなどの冗長化構成は好きなものを設定して下さい。キャッシュ用なので、個人的にはローカル冗長で十分かとは思います。

>>・Site Recoveryのレプリケーションのプロセスとしまして、以下のような流れで
レプリケーションデータが継続的にターゲットリージョンに送られています。
プロセスを知れば、キャッシュストレージの必要性が分かると思います。
>>
>>・初回レプリケーション時
1 ソース リージョン上の仮想マシン上に Site Recovery モビリティ サービス エージェント (以降 MSA) をインストール（こちらは、Site Recoveryを有効化にすると自動でVMにインストールされます。）
2 MSA によって仮想マシンのディスクから完全データが取得される
3 このデータをソース仮想マシンからソース リージョン上のキャッシュ ストーレジに送信
4 キャッシュ ストレージからターゲット リージョン上のレプリカ ディスクにデータを送信
>>
>>・差分レプリケーション時
1 MSA によって、ソース仮想マシンのディスクから変更分データを取得
2 このデータをソース仮想マシンからソース リージョン上のキャッシュ ストーレジに送信
3 キャッシュ ストレージからターゲット リージョン上のレプリカ ディスクにデータを送信

```.storage_account.tf
## Create Storage for Cache in Primary Region.
resource "azurerm_storage_account" "cache_storage" {

  name                            = "${var.environment}${var.main-site}cachestorage"
  resource_group_name             = azurerm_resource_group.east.name
  location                        = "japaneast"
  account_tier                    = "Standard"
  account_kind                    = "StorageV2"
  account_replication_type        = "LRS"
  allow_nested_items_to_be_public = false
  public_network_access_enabled   = true

  cross_tenant_replication_enabled = false

  network_rules {
    default_action = "Deny"
    ip_rules       = "必要な許可するIPが必要ならば入力"
  }
}

```

<br>
<br>

>>・プライベートリンクは、2つ共メイン側である東側リージョンに作成。
・リンク先のリソースは、キャッシュ用ストレージは東側に作成したリソース、
Recovery Service コンテナは、西側で作成したリソースを指定する。

```.private_endpoint.tf
## Create Private Endpint for Azure Blob Cache Storage.
resource "azurerm_private_endpoint" "cache_endpoint" {

  name                = "${var.environment}-${var.main-site}-cache-endpoint"
  resource_group_name = azurerm_resource_group.east.name
  location            = "japaneast"
  subnet_id           = azurerm_subnet.east.id

  private_service_connection {
    name                           = "${var.main-site}-cache-private-connection"
    private_connection_resource_id = azurerm_storage_account.cache_storage.id
    is_manual_connection           = false
    subresource_names              = ["blob"]
  }
  private_dns_zone_group {
    name                 = "${var.main-site}-cache-pdzg"
    private_dns_zone_ids = [azurerm_private_dns_zone.cache_pdns.id]
  }

  depends_on = [
    azurerm_private_dns_zone.cache_pdns
  ]
}

resource "azurerm_private_dns_zone" "cache_pdns" {
  name                = "privatelink.blob.core.windows.net"
  resource_group_name = azurerm_resource_group.east.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "cache_vnetlink" {
  name                  = "${var.environment}-${var.main-site}-cache-vnetlink"
  resource_group_name   = azurerm_resource_group.east.name
  private_dns_zone_name = azurerm_private_dns_zone.cache_pdns.name
  virtual_network_id    = azurerm_virtual_network.east.id

  depends_on = [
    azurerm_private_dns_zone.cache_pdns
  ]
}



## Create Private Endpint for Recovery Services Vault.
resource "azurerm_private_endpoint" "rsv_endpoint" {

  name                = "${var.environment}-${var.main-site}-rsv-endpoint"
  resource_group_name = azurerm_resource_group.east.name
  location            = "japaneast"
  subnet_id           = azurerm_virtual_network.east.id

  private_service_connection {
    name                           = "${var.main-site}-rsv-private-connection"
    private_connection_resource_id = azurerm_recovery_services_vault.dr_vault.name
    is_manual_connection           = false
    subresource_names              = ["AzureSiteRecovery"]
  }
  private_dns_zone_group {
    name                 = "${var.main-site}-rsv-pdzg"
    private_dns_zone_ids = [azurerm_private_dns_zone.rsv_pdns.id]
  }
  
  depends_on = [
    azurerm_private_dns_zone.rsv_pdns
  ]
}

resource "azurerm_private_dns_zone" "rsv_pdns" {
  name                = "privatelink.siterecovery.windowsazure.com"
  resource_group_name = azurerm_resource_group.east.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "rsv_vnetlink" {
  name                  = "${var.environment}-${var.main-site}-rsv-vnetlink"
  resource_group_name   = azurerm_resource_group.east.name
  private_dns_zone_name = azurerm_private_dns_zone.rsv_pdns.name
  virtual_network_id    = azurerm_virtual_network.east.id
  
  depends_on = [
    azurerm_private_dns_zone.rsv_pdns
  ]
}

```

<br>
<br>

>>・Site Recoveryのレプリケーションの設定は、DR側である西リージョンに作成。
・レプリケーション元である東側に作成してあるVM関係のリソースは各自のIDを入れて下さい。
・ディスクに関しては、OS及びデータディスクを両方ともレプリケーションする場合は、managed_diskのオプションを別々に記載する必要があります。もし、データディスクが2つある場合は、さらに managed_diskのオプションを追加で記載する必要があります。
・テストフェールオーバーと実際のフェールオーバーのIPアドレスは異なるIPでなければなりません。同じIPアドレには設定できません。


```.replicated.tf
## Create Replicated Windows VM with Azure Site Recovery.

resource "azurerm_site_recovery_replicated_vm" "replicated_ad" {

  name                                      = "${var.environment}-${var.dr-site}-sr-replicated"
  resource_group_name                       = azurerm_resource_group.west.name
  recovery_vault_name                       = azurerm_recovery_services_vault.dr_vault.name
  source_recovery_fabric_name               = azurerm_site_recovery_fabric.primary.name
  source_vm_id                              = "東側のリージョンにあるソースのVMのIDを入力"
  source_recovery_protection_container_name = azurerm_site_recovery_protection_container.primary.name

  recovery_replication_policy_id = azurerm_site_recovery_replication_policy.vm_policy.id

  target_resource_group_id                = azurerm_resource_group.west.id
  target_recovery_fabric_id               = azurerm_site_recovery_fabric.secondary.id
  target_recovery_protection_container_id = azurerm_site_recovery_protection_container.secondary.id

  managed_disk {
    disk_id                    = "東側のリージョンにあるソースのVMのOSディスクのIDを入力"
    staging_storage_account_id = azurerm_storage_account.cache_storage.id
    target_resource_group_id   = azurerm_resource_group.west.id
    target_disk_type           = "StandardSSD_LRS"
    target_replica_disk_type   = "StandardSSD_LRS"
  }
  
  managed_disk {
    disk_id                    = "東側のリージョンにあるソースのVMのデータディスクのIDを入力"
    staging_storage_account_id = azurerm_storage_account.cache_storage.id
    target_resource_group_id   = azurerm_resource_group.west.id
    target_disk_type           = "StandardSSD_LRS"
    target_replica_disk_type   = "StandardSSD_LRS"
  }
  
  network_interface {
    source_network_interface_id = "東側のリージョンにあるソースのVMのインターフェイスのIDを入力"
    target_subnet_name          = azurerm_subnet.west.name
    target_static_ip            = "192.168.2.10" //好きなIPを入力
    failover_test_subnet_name   = azurerm_subnet.west.name
    failover_test_static_ip     = "192.168.2.15" //好きなIPを入力
  }

  target_network_id = azurerm_virtual_network.west.id
  test_network_id   = azurerm_virtual_network.west.id

  depends_on = [
    azurerm_site_recovery_protection_container_mapping.container-mapping,
    azurerm_site_recovery_network_mapping.network-mapping,
  ]
}

```

<br>
<br>



最後に、Recovery Serviceコンテナに付与したマネージド ID を、Recovery Serviceコンテナの種類に応じて、キャッシュ用ストレージアカウントで、[追加]-[ロールの割り当ての追加]から次のロールのアクセス許可が付与します。

>・Resource Manager ベースのストレージ アカウント (Standard タイプ):
Contributor
ストレージ BLOB データ共同作成者
>
>・Resource Manager ベースのストレージ アカウント (Premium タイプ):
Contributor
ストレージ BLOB データ所有者

付与の仕方は、下記のドキュメントのコンテナーに必要なアクセス許可を付与する
を参照してみてください。

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


<br>

## ◎最後に

terraform apply で　デプロイすると思いますが、
Azure Site Recoveryは、デプロイ（apply）が完了するまで
長いと 45分程は時間が掛かりますので、気長にデプロイが終了するのを待って頂ければと思います。