使用 Terraform 预配 Linux 虚拟机

已完成

Terraform 可实现和控制目标基础结构,方法是通过使用描述其组件所需状态的配置文件。 文件的基本格式及其一般语法(以 HashiCorp Configuration Language (HCL) 表示)是相同的,无论选择哪种云。 但是,各个组件描述是依赖于云的,由相应的 Terraform 提供程序确定。

尽管有多个支持 Azure 基础结构管理的 Terraform 提供程序,但 AzureRM 具有特别的相关性。 AzureRM 提供程序有助于预配和配置常见的 Azure IaaS 资源,例如虚拟机、存储帐户和网络接口。 还有一些其他非特定于云的提供程序,你可能希望将它合并到部署中。 其中包括随机提供程序(通过生成伪随机字符串来帮助避免资源命名冲突),以及可简化非对称密钥管理以保护 Linux 身份验证的 tls 提供程序。

Terraform 以单个二进制文件的形式提供,可从 Hashicorp 网站下载。 此二进制文件实现 Terraform 命令行接口 (CLI),你可以从 shell 会话调用该接口来初始化 Terraform 并处理配置文件。 可以从任何支持 Azure CLI 的 shell 中使用 Terraform CLI。

注意

使用 Azure Cloud Shell 时,请确保按照使用 Bash 在 Azure Cloud Shell 中配置 Terraform 中提供的说明运行当前版本的 Terraform。

使用 Terraform 部署 Linux VM

Terraform 提供定义、预览资源以及将资源部署到特定于提供程序的云基础结构的功能。 预配过程从创建使用 HCL 语法的配置文件开始,该语法使你能够指定目标云环境(例如 Azure)以及构成云基础结构的资源。 在所有相关配置文件都已就位(通常位于同一文件系统位置中)后,可以生成一个执行计划,使你能够在实际部署之前预览生成的基础结构更改。 这需要初始化 Terraform 以下载实现云资源所需的提供程序模块。 在验证更改后,请应用该执行计划以部署基础结构。

注意

生成执行计划是可选的,但我们建议这样做,因为它使你能够识别任何来自计划部署的影响,而不会影响目标环境。 当你以交互方式部署 Azure 资源时,Terraform 通过重用你的凭据访问目标 Azure 订阅,从而以透明方式支持 Azure CLI 身份验证。

使用 Terraform 预配运行 Linux 的 Azure VM 的过程通常涉及以下一系列主要步骤:

  • 确定合适的 VM 映像。
  • 确定合适的 VM 大小。
  • 创建配置文件,用于定义 Azure VM 资源及其依赖项。
  • 初始化 Terraform。
  • 生成 Terraform 执行计划。
  • 启动 Terraform 部署。

若要确定合适的 VM 映像和大小,请遵循本模块第 4 单元中所述的步骤。 本单元重点介绍特定于 Terraform 的任务。

创建配置文件

注意

为 Terraform 文件选择的文件名是任意的,不过最好选择反映文件内容或用途的名称。 应将“.tf”用于文件扩展名。

若要使用 Terraform 部署 Linux VM,请首先创建用于托管配置文件的目录。 接下来,创建一个名为 providers.tf 的文件,该文件强制实施 Terraform 版本,并指定在定义部署中包含的资源时将依赖的提供程序。 此文件的内容应显示在以下代码片段中:

terraform {
  required_version = ">=0.12"

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

provider "azurerm" {
  features {}
}

在同一目录中,使用以下代码创建名为 main.tf 的文件,该代码定义 Azure VM 配置及其依赖项:

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
}

# Create virtual network
resource "azurerm_virtual_network" "terraform_network" {
  name                = "lnx-tf-vnet"
  address_space       = ["10.1.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "terraform_subnet" {
  name                 = "subnet0"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.terraform_network.name
  address_prefixes     = ["10.1.0.0/24"]
}

# Create public IPs
resource "azurerm_public_ip" "terraform_public_ip" {
  name                = "lnx-tf-pip"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
}

# Create Network Security Group and rule
resource "azurerm_network_security_group" "terraform_nsg" {
  name                = "lnx-tf-nsg"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "ssh"
    priority                   = 300
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "terraform_nic" {
  name                = "lnx-tf-nic"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "nic_configuration"
    subnet_id                     = azurerm_subnet.terraform_subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.terraform_public_ip.id
  }
}

# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "lnx-tf-nic-nsg" {
  network_interface_id      = azurerm_network_interface.terraform_nic.id
  network_security_group_id = azurerm_network_security_group.terraform_nsg.id
}

# 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
}

# Create storage account for boot diagnostics
resource "azurerm_storage_account" "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 (and display) an SSH key
resource "tls_private_key" "lnx-tf-ssh" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

# Create virtual machine
resource "azurerm_linux_virtual_machine" "lnx-tf-vm" {
  name                  = "lnx-tf-vm"
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.terraform_nic.id]
  size                  = "Standard_F4s"

  os_disk {
    name                 = "lnx-tf-vm-osdisk"
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }

  computer_name                   = "lnx-tf-vm"
  admin_username                  = "azureuser"
  disable_password_authentication = true

  admin_ssh_key {
    username   = "azureuser"
    public_key = tls_private_key.lnx-tf-ssh.public_key_openssh
  }

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.storage_account.primary_blob_endpoint
  }
}

在同一目录中,使用以下代码创建名为 variables.tf 的另一个文件,该代码将值分配给 main.tf 文件中出现的变量

variable "resource_group_location" {
  default     = "eastus"
  description = "Location of the resource group"
}

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

最后,使用以下代码创建名为 outputs.tf 的文件,它决定了成功部署后显示的输出:

output "resource_group_name" {
  value = azurerm_resource_group.rg.name
}

output "public_ip_address" {
  value = azurerm_linux_virtual_machine.lnx-tf-vm.public_ip_address
}

output "tls_private_key" {
  value     = tls_private_key.lnx-tf-ssh.private_key_pem
  sensitive = true
}

初始化 Terraform

若要初始化 Terraform 部署,请在 shell 提示符下运行以下命令:

terraform init

此命令下载预配和管理 Azure 资源所需的 Azure 模块。

生成执行计划

初始化后,运行 terraform plan 即可创建执行计划。 此命令将创建一个执行计划,但不会运行它。 相反,它会确定创建配置文件中定义的资源需要执行哪些操作。 可选的 -out 参数使你能够指定计划的输出文件,可以在实际部署期间引用该文件。 使用此文件可确保查看的计划与确切的部署结果匹配。 使用以下命令生成执行计划:

terraform plan -out <terraform_plan>.tfplan

启动部署

准备好将执行计划应用到 Azure 环境时,请运行 terraform apply,包括上一步中生成的文件的名称。 你将有另一个机会查看预期结果。 Terraform 将提示你进行确认以继续,不过你可以通过添加 -auto-approve 开关来消除提示。 使用以下命令启动部署:

terraform apply <terraform_plan>.tfplan

Azure VM 很快将开始运行,通常在几分钟内即会运行。 terraform apply 命令输出将包含输出列表,但 terraform 会将 tls_private_key 的值替换为 sensitive 标签<>:

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

输出:

public_ip_address = "74.235.10.136"
resource_group_name = "rg-flexible-shark"
tls_private_key = <sensitive>

若要使用自动生成的私钥对 SSH 连接进行身份验证,请将它存储在文件中,然后设置文件的权限,以确保其他人无法访问该文件。 为此,请运行以下命令:

terraform output -raw tls_private_key > id_rsa
chmod 600 id_rsa

此时,你将能够通过运行以下命令(将 <public_ip_address> 占位符替换为在 terraform apply 生成的输出中标识的 IP 地址后)连接到 Azure VM:

ssh -i id_rsa azureuser@<public_ip_address>