快速入门:使用 Terraform 在 Azure 中备份虚拟机

在本快速入门中,你将使用 Terraform 创建 Azure Windows 虚拟机(VM)和相关资源。 Azure Windows VM 是 Azure 提供的可缩放计算资源。 它是 Azure 云中的按需虚拟化 Windows 服务器。 可以使用它来部署、测试和运行应用程序,等等。 除了 VM,此代码还创建虚拟网络、子网、公共 IP、网络安全组、网络接口、存储帐户、Azure 备份恢复服务保管库和备份策略。

使用 Terraform 可以定义、预览和部署云基础结构。 使用 Terraform 时,请使用 HCL 语法来创建配置文件。 利用 HCL 语法,可指定 Azure 这样的云提供程序和构成云基础结构的元素。 创建配置文件后,请创建一个执行计划,利用该计划,可在部署基础结构更改之前先预览这些更改。 验证了更改后,请应用该执行计划以部署基础结构。

在这篇文章中,你将学会如何:

  • 创建具有唯一名称的 Azure 资源组。
  • 创建具有唯一名称和指定地址空间的虚拟网络。
  • 在虚拟网络中创建具有唯一名称和指定地址前缀的子网。
  • 创建具有唯一名称的公共 IP 地址。
  • 创建一个网络安全组,其中包含两个用于远程桌面协议和 Web 流量的安全规则。
  • 创建具有唯一名称的网络接口,并将其附加到子网和公共 IP 地址。
  • 将网络安全组与网络接口相关联。
  • 生成唯一存储帐户名称的随机 ID,并插入用于启动诊断的存储帐户。
  • 创建具有唯一名称的 Windows VM,并为 VM 生成随机密码。
  • 创建具有唯一名称的备份恢复服务保管库。
  • 为 VM 创建每日频率和保留期为 7 天的备份策略。
  • 使用创建的备份策略保护 VM。

先决条件

实现 Terraform 代码

本文中的示例代码位于 Azure Terraform GitHub 存储库中。 你可以查看包含当前和以前 Terraform 版本的测试结果的日志文件。 请参阅更多 文章和示例代码,了解如何使用 Terraform 管理 Azure 资源

  1. 创建用于测试和运行示例 Terraform 代码的目录,并将其设为当前目录。

  2. 创建名为 main.tf 的文件并插入以下代码:

resource "random_pet" "rg_name" {
  prefix = var.resource_group_name_prefix
}

resource "azurerm_resource_group" "rg" {
  location = var.resource_group_location
  name     = random_pet.rg_name.id
}

resource "random_string" "name" {
  length  = 12
  lower   = true
  upper   = false
  numeric = false
  special = false
}

# Create virtual network
resource "azurerm_virtual_network" "my_terraform_network" {
  name                = "${random_string.name.id}-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "my_terraform_subnet" {
  name                 = "${random_string.name.id}-subnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.my_terraform_network.name
  address_prefixes     = ["10.0.1.0/24"]
}

# Create public IPs
resource "azurerm_public_ip" "my_terraform_public_ip" {
  name                = "${random_string.name.id}-public-ip"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
}

# Create Network Security Group and rules
resource "azurerm_network_security_group" "my_terraform_nsg" {
  name                = "${random_string.name.id}-nsg"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "RDP"
    priority                   = 1000
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "web"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "my_terraform_nic" {
  name                = "${random_string.name.id}-nic"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "my_nic_configuration"
    subnet_id                     = azurerm_subnet.my_terraform_subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.my_terraform_public_ip.id
  }
}

# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
  network_interface_id      = azurerm_network_interface.my_terraform_nic.id
  network_security_group_id = azurerm_network_security_group.my_terraform_nsg.id
}

# Create storage account for boot diagnostics
resource "azurerm_storage_account" "my_storage_account" {
  name                     = "diag${random_id.random_id.hex}"
  location                 = azurerm_resource_group.rg.location
  resource_group_name      = azurerm_resource_group.rg.name
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# Create virtual machine
resource "azurerm_windows_virtual_machine" "main" {
  name                              = "${random_string.name.id}-vm"
  admin_username                    = "azureuser"
  admin_password                    = random_password.password.result
  location                          = azurerm_resource_group.rg.location
  resource_group_name               = azurerm_resource_group.rg.name
  network_interface_ids             = [azurerm_network_interface.my_terraform_nic.id]
  size                              = "Standard_DS1_v2"
  vm_agent_platform_updates_enabled = true

  os_disk {
    name                 = "myOsDisk"
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter-azure-edition"
    version   = "latest"
  }


  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.my_storage_account.primary_blob_endpoint
  }
}

# Generate random text for a unique storage account name
resource "random_id" "random_id" {
  keepers = {
    # Generate a new ID only when a new resource group is defined
    resource_group = azurerm_resource_group.rg.name
  }

  byte_length = 8
}

resource "random_password" "password" {
  length      = 20
  min_lower   = 1
  min_upper   = 1
  min_numeric = 1
  min_special = 1
  special     = true
}

resource "azurerm_recovery_services_vault" "example" {
  name                = "${random_string.name.id}-vault"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  sku                 = "Standard"
  soft_delete_enabled = var.soft_delete_enabled
}

resource "azurerm_backup_policy_vm" "example" {
  name                = "${random_string.name.id}-policy"
  resource_group_name = azurerm_resource_group.rg.name
  recovery_vault_name = azurerm_recovery_services_vault.example.name

  backup {
    frequency = "Daily"
    time      = "23:00"
  }

  retention_daily {
    count = 7
  }
}

resource "azurerm_backup_protected_vm" "example" {
  resource_group_name = azurerm_resource_group.rg.name
  recovery_vault_name = azurerm_recovery_services_vault.example.name
  source_vm_id        = azurerm_windows_virtual_machine.main.id
  backup_policy_id    = azurerm_backup_policy_vm.example.id
}
  1. 创建名为 outputs.tf 的文件并插入以下代码:
output "resource_group_name" {
  value = azurerm_resource_group.rg.name
}

output "azurerm_recovery_services_vault_name" {
  value = azurerm_recovery_services_vault.example.name
}

output "azurerm_backup_policy_vm_name" {
  value = azurerm_backup_policy_vm.example.name
}

output "azurerm_windows_virtual_machine_name" {
  value = azurerm_windows_virtual_machine.main.name
}

output "public_ip_address" {
  value = azurerm_windows_virtual_machine.main.public_ip_address
}

output "admin_password" {
  sensitive = true
  value     = azurerm_windows_virtual_machine.main.admin_password
}
  1. 创建名为 providers.tf 的文件并插入以下代码:
terraform {
  required_version = ">=1.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~>3.0"
    }
  }
}

provider "azurerm" {
  features {
    recovery_service {
      vm_backup_stop_protection_and_retain_data_on_destroy = true
      purge_protected_items_from_vault_on_destroy          = true
    }
  }
}
  1. 创建名为 variables.tf 的文件并插入以下代码:
variable "resource_group_location" {
  type        = string
  default     = "chinanorth"
  description = "Location of the resource group."
}

variable "resource_group_name_prefix" {
  type        = string
  default     = "rg"
  description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."
}

variable "soft_delete_enabled" {
  type        = bool
  default     = false
  nullable    = false
  description = "Is soft delete enable for the recovery services vault?"
}

重要

如果使用 4.x azurerm 提供程序,则必须在运行 Terraform 命令之前 显式指定要向 Azure 进行身份验证的 Azure 订阅 ID

一种指定 Azure 订阅 ID 的方法是在名为 providers 的环境变量中指定订阅 ID,而不是将其放在 ARM_SUBSCRIPTION_ID 块中。

有关详细信息,请参阅 Azure 提供程序参考文档

初始化 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 outout -raw azurerm_resource_group_name)
    
  2. 获取备份恢复服务保管库名称。

    recovery_services_vault_name = $(terraform output -raw azurerm_recovery_services_vault_name)
    
  3. 获取 Windows VM 名称。

    windows_virtual_machine_name = $(terraform output -raw azurerm_windows_virtual_machine_name)
    
  4. 运行 az backup protection backup-now 以启动备份作业。

    az backup protection backup-now --resource-group $resource_group_name \
                                    --vault-name $recovery_services_vault_name \
                                    --container-name $windows_virtual_machine_name \
                                    --item-name $windows_virtual_machine_name \
                                    --backup-management-type AzureIaaSVM
    
  5. 运行 az backup job list 以监视备份作业。 当备份作业的“状态”报告“已完成”时,VM 将接受备份恢复服务的保护,并已存储完整的恢复点。

    az backup job list --resource-group $resource_group_name \
                       --vault-name $recovery_services_vault_name \
                       --output table
    

清理资源

不再需要通过 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 时遇到的常见问题

后续步骤