Az Terraform: 서로 다른 Azure 구독에 리소스 배포하는 방법 알아보기

Agenda

  1. 서로 다른 Azure 구독에 리소스를 배포하는 시나리오
  2. provider 지정으로 서로 다른 구독에 리소스를 배포하기

서로 다른 구독에 리소스를 배포하는 시나리오

Azure 허브 앤 스포크 아키텍처를 도입하면서, 허브 구독에서 공용 리소스를 중앙 집중식으로 관리하고 각 스포크 구독에서는 독립적인 리소스를 운용하는 시나리오가 존재하는 경우 서로 다른 구독에 리소스를 배포하는 Terraform 코드가 필요할 수 있다.

허브 VNET 및 스포크 VNET 피어링 구성

허브 VNET과 각 스포크 VNET 을 피어링하여 네트워크 트래픽의 중앙 집중식 관리를 고려하고 있다면, Terraform 을 사용하여 리소스를 관리하고 배포하는 것은 더 복잡해 진다. 이러한 시나리오를 처리하기 위해서 Terraform 에서는 다중 프로바이더 설정을 사용하여 각 구독에 대한 자격증명과 구성을 따로 관리할 수 있다.

Terraform 폴더 구성

C:.
│  .terraform.lock.hcl
│  main_hub.tf
│  main_spoke.tf
│  provider.tf
│  terraform.tfstate
│  terraform.tfstate.backup
│  variables.tf

├─.terraform
│  └─providers
│      └─registry.terraform.io
│          └─hashicorp
│              ├─azurerm
│              │  └─3.102.0
│              │      └─windows_386
│              │              LICENSE.txt
│              │
│              └─null
│                  └─3.2.2
│                      └─windows_386
│                              terraform-provider-null_v3.2.2_x5.exe

├─Azure CLI Peering Resync # Optional
│      main_hub_bak.tf
│      main_spoke_bak.tf

└─Replace Trigger Peering Resync # Optional
        main_hub_bak.tf
        main_spoke_bak.tf

다중 프로바이더 설정 ★

각 구독에 대해 별도의 azurerm 프로바이더 인스턴스를 구성한다. 이를 통해 각 구독의 리소스를 관리할 수 있다.

# Common provider.tf
provider "azurerm" {
  features {}
  subscription_id = "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx" # Hub subscription ID
  alias           = "hub" # Hub subscription Alias
}

provider "azurerm" {
  features {}
  subscription_id = "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx" # Spoke subscription ID
  alias           = "spoke"  # Spoke subscription Alias
}

프로바이더 별칭 사용 방법 ★

VNET 배포 설정

VNET 을 배포할 리소스 그룹을 설정하고, VNET 배포 설정 코드를 추가한다. 리소스 그룹이 생성되는 도중에 VNET 을 생성하려고 하면 리소스 그룹이 존재하지 않다는 에러가 발생하므로, VNET 배포 코드에 리소스 그룹에 대한 의존성 depends_on을 추가한다.

허브 구독에 있는 VNET과 스포크 구독에 있는 VNET을 구분하기 위해서, 프로바이더의 별칭provider = azurerm.<alias>를 사용하여 해당 구독의 프로바이더 인스턴스를 지정한다.

# Hub main.tf
resource "azurerm_resource_group" "hub_rg" {
  provider = azurerm.hub # Hub subscription provider instance
  location = var.location
  name     = var.resource_group_name
}

# Spoke main.tf
resource "azurerm_resource_group" "spoke_rg" {
  provider = azurerm.spoke # Spoke subscription provider instance
  location = var.location
  name     = var.resource_group_name
}

# Hub main.tf
resource "azurerm_virtual_network" "hub_vnet" {
  depends_on = [azurerm_resource_group.hub_rg]

  provider            = azurerm.hub
  name                = "hubVnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.hub_rg.location
  resource_group_name = azurerm_resource_group.hub_rg.name
}

# Spoke main.tf
resource "azurerm_virtual_network" "spoke_vnet" {
  depends_on = [azurerm_resource_group.spoke_rg]

  provider            = azurerm.spoke
  name                = "spokeVnet"
  address_space       = ["10.1.0.0/16"]
  location            = azurerm_resource_group.spoke_rg.location
  resource_group_name = azurerm_resource_group.spoke_rg.name
}

VNET 피어링 배포 설정

허브 VNET 과 스포크 VNET 간에 피어링을 설정한다. VNET 과 마찬가지로 피어링 리소스는 각각의 구독의 프로바이더를 사용한다.

Tip. Lifecycle 추가한 이유
VNET 의 주소 공간의 변경이 있을 경우, 허브 와 스포크 VNET 간의 재동기가 필요하다. Lifecycle 로 각 VNET 의 address space 의 변경이 있을 경우, 피어링을 재설정하는 코드를 추가하여 자동으로 재동기하도록 코드를 구성하였다.

ref url : Vnet peering out of sync

# Hub main.tf
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
  depends_on = [
    azurerm_virtual_network.hub_vnet,
    azurerm_virtual_network.spoke_vnet
  ]

  provider                     = azurerm.hub
  name                         = "hub-to-spoke"
  resource_group_name          = azurerm_resource_group.hub_rg.name
  virtual_network_name         = azurerm_virtual_network.hub_vnet.name
  remote_virtual_network_id    = azurerm_virtual_network.spoke_vnet.id
  allow_virtual_network_access = true
  allow_forwarded_traffic      = true

  lifecycle {
    replace_triggered_by = [azurerm_virtual_network.hub_vnet.address_space, azurerm_virtual_network.spoke_vnet.address_space]
  }
}

# Spoke main.tf
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
  depends_on = [
    azurerm_virtual_network.spoke_vnet,
    azurerm_virtual_network.hub_vnet
  ]

  provider                     = azurerm.spoke
  name                         = "spoke-to-hub"
  resource_group_name          = azurerm_resource_group.spoke_rg.name
  virtual_network_name         = azurerm_virtual_network.spoke_vnet.name
  remote_virtual_network_id    = azurerm_virtual_network.hub_vnet.id
  allow_virtual_network_access = true
  allow_forwarded_traffic      = true

  lifecycle {
    replace_triggered_by = [azurerm_virtual_network.hub_vnet.address_space, azurerm_virtual_network.spoke_vnet.address_space]
  }
}

코드 배포 테스트

첫번째 배포 (리소스 그룹 + VNET + 피어링 생성)

Terraform 코드 작성이 완료 되었으니, 실제 배포 후 결과를 확인한다.

(1) 서로 다른 구독에 리소스 그룹 생성
(2) VNET Peering 설정 추가

두번째 배포 (VNET 피어링 Resync 테스트)

각 VNET의 피어링 리소스가 재생성 되는 것을 확인할 수 있다.

Tip. 피어링 재설정의 경우, 스포크 VNET 의 주소 공간을 변경하고 재배포하는 것으로 확인할 수 있다.

# Spoke main.tf
resource "azurerm_virtual_network" "spoke_vnet" {
  depends_on = [azurerm_resource_group.spoke_rg]

  provider            = azurerm.spoke
  name                = "spokeVnet"
  address_space       = ["10.1.0.0/16", "10.2.0.0/16"]
  location            = azurerm_resource_group.spoke_rg.location
  resource_group_name = azurerm_resource_group.spoke_rg.name
}

Terraform 로그 확인

# tf apply -auto-approve

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # null_resource.hub_peering_async is tainted, so must be replaced
-/+ resource "null_resource" "hub_peering_async" {
      ~ id       = "1557426316" -> (known after apply)
        # (1 unchanged attribute hidden)
    }

  # null_resource.spoke_peering_async is tainted, so must be replaced
-/+ resource "null_resource" "spoke_peering_async" {
      ~ id       = "576413854" -> (known after apply)
        # (1 unchanged attribute hidden)
    }