快速入門:建立 Azure 防火牆和 IP 群組 - Terraform

在本快速入門中,您會使用 Terraform 部署 Azure 防火牆以及用在網路規則和應用程式規則中的範例 IP 群組。 IP 群組是最上層資源,可讓您定義 IP 位址、範圍和子網定義,並將其群組為單一物件。 IP 群組非常適合用來管理 Azure 防火牆規則中的 IP 位址。 您可手動輸入 IP 位址,或從檔案進行匯入。

Terraform 可讓您定義、預覽和部署雲端基礎結構。 使用 Terraform 時,您可以使用 HCL 語法來建立設定檔。 HCL 語法可讓您指定雲端提供者 (例如 Azure) 和構成雲端基礎結構的元素。 建立設定檔之後,您可以建立執行計畫,讓您先預覽基礎結構變更,之後再部署。 驗證變更之後,您可以套用執行計畫來部署基礎結構。

在本文中,您將學會如何:

必要條件

實作 Terraform 程式碼

注意

本文中的範例程式碼位於 Azure Terraform GitHub 存放庫 (英文)。 您可以檢視記錄檔,內含目前和舊版 Terraform 的測試結果 (英文)。

請參閱更多文章和範例程式碼,其中顯示如何使用 Terraform 來管理 Azure 資源

  1. 建立目錄,然後在目錄中測試範例 Terraform 程式碼,並將其設為目前的目錄。

  2. 建立名為 providers.tf 的檔案,並插入下列程式碼:

    terraform {
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>3.0"
        }
        random = {
          source  = "hashicorp/random"
          version = "~>3.0"
        }
        azapi = {
          source  = "azure/azapi"
          version = "~>1.5"
        }
      }
    }
    
    provider "azurerm" {
      features {}
    }
    
  3. 建立名為 ssh.tf 的檔案,並插入下列程式碼:

    resource "random_pet" "ssh_key_name" {
      prefix    = "ssh"
      separator = ""
    }
    
    resource "azapi_resource_action" "ssh_public_key_gen" {
      type        = "Microsoft.Compute/sshPublicKeys@2022-11-01"
      resource_id = azapi_resource.ssh_public_key.id
      action      = "generateKeyPair"
      method      = "POST"
    
      response_export_values = ["publicKey", "privateKey"]
    }
    
    resource "azapi_resource" "ssh_public_key" {
      type      = "Microsoft.Compute/sshPublicKeys@2022-11-01"
      name      = random_pet.ssh_key_name.id
      location  = azurerm_resource_group.rg.location
      parent_id = azurerm_resource_group.rg.id
    }
    
    output "key_data" {
      value = jsondecode(azapi_resource_action.ssh_public_key_gen.output).publicKey
    }
    
  4. 建立名為 main.tf 的檔案,並插入下列程式碼:

    resource "random_pet" "rg_name" {
      prefix = var.resource_group_name_prefix
    }
    
    resource "random_string" "storage_account_name" {
      length  = 8
      lower   = true
      numeric = false
      special = false
      upper   = false
    }
    
    resource "random_password" "password" {
      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" "pip_azfw" {
      name                = "pip-azfw"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      allocation_method   = "Static"
      sku                 = "Standard"
    }
    
    resource "azurerm_storage_account" "sa" {
      name                     = random_string.storage_account_name.result
      resource_group_name      = azurerm_resource_group.rg.name
      location                 = azurerm_resource_group.rg.location
      account_tier             = "Standard"
      account_replication_type = "LRS"
      account_kind             = "StorageV2"
    }
    
    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" "prcg" {
      name               = "prcg"
      firewall_policy_id = azurerm_firewall_policy.azfw_policy.id
      priority           = 300
      application_rule_collection {
        name     = "app-rule-collection-1"
        priority = 101
        action   = "Allow"
        rule {
          name = "someAppRule"
          protocols {
            type = "Https"
            port = 443
          }
          destination_fqdns = ["*bing.com"]
          source_ip_groups  = [azurerm_ip_group.ip_group_1.id]
        }
      }
      network_rule_collection {
        name     = "net-rule-collection-1"
        priority = 200
        action   = "Allow"
        rule {
          name                  = "someNetRule"
          protocols             = ["TCP", "UDP", "ICMP"]
          source_ip_groups      = [azurerm_ip_group.ip_group_1.id]
          destination_ip_groups = [azurerm_ip_group.ip_group_2.id]
          destination_ports     = ["90"]
        }
      }
    }
    
    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
      }
      firewall_policy_id = azurerm_firewall_policy.azfw_policy.id
    }
    
    resource "azurerm_ip_group" "ip_group_1" {
      name                = "ip-group_1"
      resource_group_name = azurerm_resource_group.rg.name
      location            = azurerm_resource_group.rg.location
      cidrs               = ["13.73.64.64/26", "13.73.208.128/25", "52.126.194.0/23"]
    }
    resource "azurerm_ip_group" "ip_group_2" {
      name                = "ip_group_2"
      resource_group_name = azurerm_resource_group.rg.name
      location            = azurerm_resource_group.rg.location
      cidrs               = ["12.0.0.0/24", "13.9.0.0/24"]
    }
    
    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" "server_subnet" {
      name                 = "subnet-server"
      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_subnet" "jump_subnet" {
      name                 = "subnet-jump"
      resource_group_name  = azurerm_resource_group.rg.name
      virtual_network_name = azurerm_virtual_network.azfw_vnet.name
      address_prefixes     = ["10.10.2.0/24"]
    }
    
    resource "azurerm_public_ip" "vm_jump_pip" {
      name                = "pip-jump"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      allocation_method   = "Static"
      sku                 = "Standard"
    }
    
    resource "azurerm_network_interface" "vm_server_nic" {
      name                = "nic-server"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
    
      ip_configuration {
        name                          = "ipconfig-workload"
        subnet_id                     = azurerm_subnet.server_subnet.id
        private_ip_address_allocation = "Dynamic"
      }
    }
    
    resource "azurerm_network_interface" "vm_jump_nic" {
      name                = "nic-jump"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
    
      ip_configuration {
        name                          = "ipconfig-jump"
        subnet_id                     = azurerm_subnet.jump_subnet.id
        private_ip_address_allocation = "Dynamic"
        public_ip_address_id          = azurerm_public_ip.vm_jump_pip.id
      }
    }
    
    resource "azurerm_network_security_group" "vm_server_nsg" {
      name                = "nsg-server"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
    }
    
    resource "azurerm_network_security_group" "vm_jump_nsg" {
      name                = "nsg-jump"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      security_rule {
        name                       = "Allow-SSH"
        priority                   = 1000
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "22"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
      }
    }
    
    resource "azurerm_network_interface_security_group_association" "vm_server_nsg_association" {
      network_interface_id      = azurerm_network_interface.vm_server_nic.id
      network_security_group_id = azurerm_network_security_group.vm_server_nsg.id
    }
    
    resource "azurerm_network_interface_security_group_association" "vm_jump_nsg_association" {
      network_interface_id      = azurerm_network_interface.vm_jump_nic.id
      network_security_group_id = azurerm_network_security_group.vm_jump_nsg.id
    }
    
    resource "azurerm_linux_virtual_machine" "vm_server" {
      name                = "server-vm"
      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_ssh_key {
        username   = var.admin_username
        public_key = jsondecode(azapi_resource_action.ssh_public_key_gen.output).publicKey
      }
      network_interface_ids = [azurerm_network_interface.vm_server_nic.id]
      os_disk {
        caching              = "ReadWrite"
        storage_account_type = "Standard_LRS"
      }
      source_image_reference {
        publisher = "Canonical"
        offer     = "UbuntuServer"
        sku       = "18.04-LTS"
        version   = "latest"
      }
      boot_diagnostics {
        storage_account_uri = azurerm_storage_account.sa.primary_blob_endpoint
      }
    }
    
    resource "azurerm_linux_virtual_machine" "vm_jump" {
      name                  = "jump-vm"
      resource_group_name   = azurerm_resource_group.rg.name
      location              = azurerm_resource_group.rg.location
      size                  = var.virtual_machine_size
      network_interface_ids = [azurerm_network_interface.vm_jump_nic.id]
      admin_username        = var.admin_username
      os_disk {
        caching              = "ReadWrite"
        storage_account_type = "Standard_LRS"
      }
      admin_ssh_key {
        username   = var.admin_username
        public_key = jsondecode(azapi_resource_action.ssh_public_key_gen.output).publicKey
      }
      source_image_reference {
        publisher = "Canonical"
        offer     = "UbuntuServer"
        sku       = "18.04-LTS"
        version   = "latest"
      }
      boot_diagnostics {
        storage_account_uri = azurerm_storage_account.sa.primary_blob_endpoint
      }
      computer_name = "JumpBox"
    
    }
    
    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                   = "azfwDefaultRoute"
        address_prefix         = "0.0.0.0/0"
        next_hop_type          = "VirtualAppliance"
        next_hop_in_ip_address = azurerm_firewall.fw.ip_configuration[0].private_ip_address
      }
    }
    
    resource "azurerm_subnet_route_table_association" "jump_subnet_rt_association" {
      subnet_id      = azurerm_subnet.server_subnet.id
      route_table_id = azurerm_route_table.rt.id
    }
    
  5. 建立名為 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"
    }
    
  6. 建立名為 outputs.tf 的檔案,並插入下列程式碼:

    output "resource_group_name" {
      value = azurerm_resource_group.rg.name
    }
    
    output "firewall_name" {
      value = azurerm_firewall.fw.name
    }
    

初始化 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 plan 並指定 destroy 旗標。

    terraform plan -destroy -out main.destroy.tfplan
    

    重點︰

    • terraform plan 命令會建立執行計畫,但不會執行。 相反地,其會決定要在您指定的設定檔中建立設定所需的動作。 此模式可讓您在對實際資源進行任何變更之前,先確認執行方案是否符合您的預期。
    • 選用的 -out 參數可讓您指定計畫的輸出檔。 使用 -out 參數可確保您所檢閱的方案就是所套用的方案。
  2. 執行 terraform apply 以套用執行方案。

    terraform apply main.destroy.tfplan
    

對 Azure 上的 Terraform 進行疑難排解

針對在 Azure 上使用 Terraform 時的常見問題進行疑難排解

下一步