在本快速入門中,你將使用 Terraform 部署具有來自公共 IP 位址前綴的多個公共 IP 位址的 Azure 防火牆。 已部署的防火牆具有 NAT 規則集合規則,可允許 RDP 連線到兩部 Windows Server 2019 虛擬機器。
Terraform 允許對雲端基礎結構進行定義、預覽和部署。 使用 Terraform,您可以使用 HCL 語法建立組態檔。 HCL 語法可讓您指定雲端提供者,例如 Azure,以及構成雲端基礎結構的專案。 建立組態檔之後,您會建立一個 執行計劃 ,讓您在部署基礎結構變更之前先預覽這些變更。 驗證變更之後,您會套用執行計劃來部署基礎結構。
如需具有多個公用 IP 位址之 Azure 防火牆的詳細資訊,請參閱 使用 Azure PowerShell 部署具有多個公用 IP 位址的 Azure 防火牆。
在本文中,您將學會如何:
- 使用 random_pet 創建隨機值(將用於資源群組名稱)
- 使用 random_password 建立 Windows VM 的隨機密碼
- 使用 azurerm_resource_group 建立 Azure 資源群組
- 使用 azurerm_public_ip_prefix 創建 Azure 公共 IP 前置綴
- 使用 azurerm_public_ip 建立 Azure 公用 IP
- 使用 azurerm_virtual_network 建立 Azure 虛擬網路
- 使用 azurerm_subnet 建立 Azure 子網
- 使用 azurerm_network_interface 建立網路介面
- 使用azurerm_network_security_group建立網路安全組(以包含網路安全規則的清單 )
- 使用 azurerm_network_interface_security_group_association 在網路介面和網路安全組之間創建關聯
- 使用 azurerm_windows_virtual_machine 創建 Windows 虛擬機
- 使用 azurerm_firewall_policy 建立 Azure 防火牆原則
- 使用 azurerm_firewall_policy_rule_collection_group 建立 Azure 防火牆原則規則集合群組
- 使用 azurerm_firewall 建立 Azure 防火牆
- 使用azurerm_route_table建立路由表
- 使用 azurerm_subnet_route_table_association 建立路由表與子網之間的關聯
先決條件
實作 Terraform 程式碼
備註
本文的範例程式代碼位於 Azure Terraform GitHub 存放庫中。 您可以檢視包含 目前和舊版 Terraform 測試結果的記錄檔。
建立目錄,然後在目錄中測試範例 Terraform 程式碼,並將其設為目前的目錄。
建立名為
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 } } }
建立名為
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 }
建立名為
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" }
建立名為
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 計劃 以建立執行計劃。
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
。
確認結果
取得 Azure 資源群組名稱。
resource_group_name=$(terraform output -raw resource_group_name)
運行 az network ip-group list 以顯示兩個新的 IP 組。
az network ip-group list --resource-group $resource_group_name
清理資源
當您不再需要透過 Terraform 建立的資源時,請執行下列步驟:
執行 terraform 計劃 並指定
destroy
旗標。terraform plan -destroy -out main.destroy.tfplan
重點:
-
terraform plan
命令會建立執行計劃,但不會執行它。 然而,它會決定哪些動作是必要的,以建立您組態檔中所指定的設定。 此模式可讓您在對實際資源進行任何變更之前,先確認執行計劃是否符合您的預期。 - 選擇性
-out
參數可讓您指定計劃的輸出檔。 使用-out
參數可確保您查看的計劃確切地被套用。
-
執行 terraform apply 來應用執行計劃。
terraform apply main.destroy.tfplan
排除 Azure 上 Terraform 的故障
針對在 Azure 上使用 Terraform 時的常見問題進行疑難解答