빠른 시작: 여러 공용 IP 주소를 사용하여 Azure Firewall 만들기 - Terraform

이 빠른 시작에서는 Terraform을 사용하여 공용 IP 주소 접두사의 여러 공용 IP 주소가 있는 Azure Firewall을 배포합니다. 배포된 방화벽에는 두 개의 Windows Server 2019 가상 머신에 대한 RDP 연결을 허용하는 NAT 규칙 수집 규칙이 있습니다.

Terraform은 클라우드 인프라의 정의, 미리 보기 및 배포를 사용합니다. Terraform을 사용하는 경우 HCL 구문를 사용하여 구성 파일을 만듭니다. HCL 구문을 사용하면 클라우드 공급자(예: Azure) 그리고 클라우드 인프라를 구성하는 요소를 지정할 수 있습니다. 구성 파일을 만든 후 배포되기 전에 인프라 변경을 미리 볼 수 있는 실행 계획를 만듭니다. 변경 내용을 확인 한 후에는 실행 계획을 적용하여 인프라를 배포합니다.

여러 공용 IP 주소를 사용하는 Azure 방화벽에 대한 자세한 내용은 Azure PowerShell을 사용하여 여러 공용 IP 주소로 Azure Firewall 배포를 참조하세요.

이 문서에서는 다음 방법을 설명합니다.

필수 조건

Terraform 코드 구현

  1. 샘플 Terraform 코드를 테스트할 디렉터리를 만들고, 이를 현재 디렉터리로 만듭니다.

  2. providers.tf라는 파일을 만들고 다음 코드를 삽입합니다.

    terraform {
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>3.0"
        }
        random = {
          source  = "hashicorp/random"
          version = "~>3.0"
        }
      }
    }
    
    provider "azurerm" {
      features {
        virtual_machine {
          delete_os_disk_on_deletion     = true
          skip_shutdown_and_force_delete = true
        }
      }
    }
    
  3. main.tf라는 파일을 만들고 다음 코드를 삽입합니다.

    resource "random_pet" "rg_name" {
      prefix = var.resource_group_name_prefix
    }
    
    resource "random_password" "password" {
      count       = 2
      length      = 20
      min_lower   = 1
      min_upper   = 1
      min_numeric = 1
      min_special = 1
      special     = true
    }
    
    resource "azurerm_resource_group" "rg" {
      name     = random_pet.rg_name.id
      location = var.resource_group_location
    }
    
    resource "azurerm_public_ip_prefix" "pip_prefix" {
      name                = "pip-prefix"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      sku                 = "Standard"
      prefix_length       = 31
    }
    
    resource "azurerm_public_ip" "pip_azfw" {
      name                = "pip-azfw"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      sku                 = "Standard"
      allocation_method   = "Static"
      public_ip_prefix_id = azurerm_public_ip_prefix.pip_prefix.id
    }
    
    resource "azurerm_public_ip" "pip_azfw_2" {
      name                = "pip-azfw-1"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      sku                 = "Standard"
      allocation_method   = "Static"
      public_ip_prefix_id = azurerm_public_ip_prefix.pip_prefix.id
    }
    
    resource "azurerm_virtual_network" "azfw_vnet" {
      name                = "azfw-vnet"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      address_space       = ["10.10.0.0/16"]
    }
    
    resource "azurerm_subnet" "azfw_subnet" {
      name                 = "AzureFirewallSubnet"
      resource_group_name  = azurerm_resource_group.rg.name
      virtual_network_name = azurerm_virtual_network.azfw_vnet.name
      address_prefixes     = ["10.10.0.0/26"]
    }
    
    resource "azurerm_subnet" "backend_subnet" {
      name                 = "subnet-backend"
      resource_group_name  = azurerm_resource_group.rg.name
      virtual_network_name = azurerm_virtual_network.azfw_vnet.name
      address_prefixes     = ["10.10.1.0/24"]
    }
    
    resource "azurerm_network_interface" "backend_nic" {
      count               = 2
      name                = "nic-backend-${count.index + 1}"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
    
      ip_configuration {
        name                          = "ipconfig-backend-${count.index + 1}"
        subnet_id                     = azurerm_subnet.backend_subnet.id
        private_ip_address_allocation = "Dynamic"
      }
    }
    
    resource "azurerm_network_security_group" "backend_nsg" {
      name                = "nsg-backend"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      security_rule {
        name                       = "RDP"
        priority                   = 300
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "3389"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
      }
    }
    
    resource "azurerm_network_interface_security_group_association" "vm_backend_nsg_association" {
      count                     = 2
      network_interface_id      = azurerm_network_interface.backend_nic[count.index].id
      network_security_group_id = azurerm_network_security_group.backend_nsg.id
    }
    
    resource "azurerm_windows_virtual_machine" "vm_backend" {
      count                 = 2
      name                  = "vm-backend-${count.index + 1}"
      resource_group_name   = azurerm_resource_group.rg.name
      location              = azurerm_resource_group.rg.location
      size                  = var.virtual_machine_size
      admin_username        = var.admin_username
      admin_password        = random_password.password[count.index].result
      network_interface_ids = [azurerm_network_interface.backend_nic[count.index].id]
      os_disk {
        caching              = "ReadWrite"
        storage_account_type = "Standard_LRS"
      }
      source_image_reference {
        publisher = "MicrosoftWindowsServer"
        offer     = "WindowsServer"
        sku       = "2019-Datacenter"
        version   = "latest"
      }
    }
    
    resource "azurerm_firewall_policy" "azfw_policy" {
      name                     = "azfw-policy"
      resource_group_name      = azurerm_resource_group.rg.name
      location                 = azurerm_resource_group.rg.location
      sku                      = var.firewall_sku_tier
      threat_intelligence_mode = "Alert"
    }
    
    resource "azurerm_firewall_policy_rule_collection_group" "policy_rule_collection_group" {
      name               = "RuleCollectionGroup"
      firewall_policy_id = azurerm_firewall_policy.azfw_policy.id
      priority           = 300
      application_rule_collection {
        name     = "web"
        priority = 100
        action   = "Allow"
        rule {
          name = "wan-address"
          protocols {
            type = "Http"
            port = 80
          }
          protocols {
            type = "Https"
            port = 443
          }
          destination_fqdns = ["getmywanip.com"]
          source_addresses  = ["*"]
        }
        rule {
          name = "google"
          protocols {
            type = "Http"
            port = 80
          }
          protocols {
            type = "Https"
            port = 443
          }
          destination_fqdns = ["www.google.com"]
          source_addresses  = ["10.10.1.0/24"]
        }
        rule {
          name = "wupdate"
          protocols {
            type = "Http"
            port = 80
          }
          protocols {
            type = "Https"
            port = 443
          }
          destination_fqdn_tags = ["WindowsUpdate"]
          source_addresses      = ["*"]
        }
      }
      nat_rule_collection {
        name     = "Coll-01"
        action   = "Dnat"
        priority = 200
        rule {
          name                = "rdp-01"
          protocols           = ["TCP"]
          translated_address  = "10.10.1.4"
          translated_port     = "3389"
          source_addresses    = ["*"]
          destination_address = azurerm_public_ip.pip_azfw.ip_address
          destination_ports   = ["3389"]
        }
        rule {
          name                = "rdp-02"
          protocols           = ["TCP"]
          translated_address  = "10.10.1.5"
          translated_port     = "3389"
          source_addresses    = ["*"]
          destination_address = azurerm_public_ip.pip_azfw.ip_address
          destination_ports   = ["3389"]
        }
      }
    }
    
    resource "azurerm_firewall" "fw" {
      name                = "azfw"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      sku_name            = "AZFW_VNet"
      sku_tier            = var.firewall_sku_tier
      ip_configuration {
        name                 = "azfw-ipconfig"
        subnet_id            = azurerm_subnet.azfw_subnet.id
        public_ip_address_id = azurerm_public_ip.pip_azfw.id
      }
      ip_configuration {
        name                 = "azfw-ipconfig-2"
        public_ip_address_id = azurerm_public_ip.pip_azfw_2.id
      }
      firewall_policy_id = azurerm_firewall_policy.azfw_policy.id
    }
    
    resource "azurerm_route_table" "rt" {
      name                          = "rt-azfw-eus"
      location                      = azurerm_resource_group.rg.location
      resource_group_name           = azurerm_resource_group.rg.name
      disable_bgp_route_propagation = false
      route {
        name                   = "azfw"
        address_prefix         = "0.0.0.0/0"
        next_hop_type          = "VirtualAppliance"
        next_hop_in_ip_address = "10.10.0.4"
      }
    }
    
    resource "azurerm_subnet_route_table_association" "jump_subnet_rt_association" {
      subnet_id      = azurerm_subnet.backend_subnet.id
      route_table_id = azurerm_route_table.rt.id
    }
    
  4. variables.tf라는 파일을 만들고 다음 코드를 삽입합니다.

    variable "resource_group_location" {
      type        = string
      description = "Location for all resources."
      default     = "eastus"
    }
    
    variable "resource_group_name_prefix" {
      type        = string
      description = "Prefix for the Resource Group Name that's combined with a random id so name is unique in your Azure subcription."
      default     = "rg"
    }
    
    variable "firewall_sku_tier" {
      type        = string
      description = "Firewall SKU."
      default     = "Premium" # Valid values are Standard and Premium
      validation {
        condition     = contains(["Standard", "Premium"], var.firewall_sku_tier)
        error_message = "The SKU must be one of the following: Standard, Premium"
      }
    }
    
    variable "virtual_machine_size" {
      type        = string
      description = "Size of the virtual machine."
      default     = "Standard_D2_v3"
    }
    
    variable "admin_username" {
      type        = string
      description = "Value of the admin username."
      default     = "azureuser"
    }
    
  5. outputs.tf라는 파일을 만들고 다음 코드를 삽입합니다.

    output "resource_group_name" {
      value = azurerm_resource_group.rg.name
    }
    output "backend_admin_password" {
      sensitive = true
      value     = azurerm_windows_virtual_machine.vm_backend[*].admin_password
    }
    

Terraform 초기화

terraform init를 실행하여 Terraform 배포를 초기화합니다. 이 명령은 Azure 리소스를 관리하는 데 필요한 Azure 공급자를 다운로드합니다.

terraform init -upgrade

주요 정보:

  • -upgrade 매개 변수는 필요한 공급자 플러그 인을 구성의 버전 제약 조건을 준수하는 최신 버전으로 업그레이드합니다.

Terraform 실행 계획 만들기

terraform plan을 실행하여 실행 계획을 만듭니다.

terraform plan -out main.tfplan

주요 정보:

  • terraform plan 명령은 실행 계획을 만들지만 실행하지는 않습니다. 대신 구성 파일에 지정된 구성을 만드는 데 필요한 작업을 결정합니다. 이 패턴을 사용하면 실제 리소스를 변경하기 전에 실행 계획이 예상과 일치하는지 확인할 수 있습니다.
  • 선택 사항인 -out 매개 변수를 사용하여 계획의 출력 파일을 지정할 수 있습니다. -out 매개 변수를 사용하면 검토한 계획이 정확하게 적용됩니다.

Terraform 실행 계획 적용

terraform apply를 실행하여 실행 계획을 클라우드 인프라에 적용합니다.

terraform apply main.tfplan

주요 정보:

  • 예시 terraform apply 명령은 이전에 terraform plan -out main.tfplan를 실행했다고 가정합니다.
  • -out 매개 변수에 다른 파일 이름을 지정한 경우 terraform apply에 대한 호출에서 동일한 파일 이름을 사용합니다.
  • -out 매개 변수를 사용하지 않은 경우 매개 변수 없이 terraform apply를 호출합니다.

결과 확인

  1. Azure 리소스 그룹 이름을 가져옵니다.

    resource_group_name=$(terraform output -raw resource_group_name)
    
  2. az network ip-group list를 실행하여 두 개의 새 IP 그룹을 표시합니다.

    az network ip-group list --resource-group $resource_group_name
    

리소스 정리

Terraform을 통해 리소스를 만들 필요가 더 이상 없으면 다음 단계를 수행합니다.

  1. terraform 플랜을 실행하고 destroy 플래그를 지정합니다.

    terraform plan -destroy -out main.destroy.tfplan
    

    주요 정보:

    • terraform plan 명령은 실행 계획을 만들지만 실행하지는 않습니다. 대신 구성 파일에 지정된 구성을 만드는 데 필요한 작업을 결정합니다. 이 패턴을 사용하면 실제 리소스를 변경하기 전에 실행 계획이 예상과 일치하는지 확인할 수 있습니다.
    • 선택 사항인 -out 매개 변수를 사용하여 계획의 출력 파일을 지정할 수 있습니다. -out 매개 변수를 사용하면 검토한 계획이 정확하게 적용됩니다.
  2. terraform apply를 실행하여 실행 계획을 적용합니다.

    terraform apply main.destroy.tfplan
    

Azure의 Terraform 문제 해결

Azure에서 Terraform을 사용할 때 일반적인 문제 해결

다음 단계