使用 Terraform 佈建 Linux 虛擬機器

已完成

Terraform 透過使用描述其元件所需狀態的組態檔,實作及控制目標基礎結構。 不論雲端的選擇為何,檔案的基本格式及其一般語法 (以 Hashicorp Configuration Language (HCL) 表示) 都是相同的。 不過,個別元件描述是雲端相依的,由對應的 Terraform 提供者決定。

雖然有數個支援 Azure 基礎結構管理的 Terraform 提供者,但 AzureRM 具有特定的相關性。 AzureRM 提供者可協助佈建及設定常見的 Azure IaaS 資源,例如虛擬機器、儲存體帳戶和網路連線介面。 您可能還希望將其他非特定雲端提供者納入您的部署中。 其中包括隨機提供者 (其可藉由產生虛擬隨機字元字串來協助避免資源命名衝突),以及 tls 提供者 (其可簡化管理用於保護 Linux 驗證的非對稱金鑰)。

Terraform 會以單一的二進位檔形式來提供,您可以從 Hashicorp 網站下載。 此二進位檔案會實作 Terraform 的命令列介面 (CLI),然後您可以從命令介面工作階段叫用它,以初始化 Terraform 和處理組態檔。 您可以從任何支援 Azure CLI 的命令介面使用 Terraform CLI。

注意

使用 Azure Cloud Shell 時,請依照「在 Azure Cloud Shell 中使用 Bash 設定 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 部署,請從殼層提示字元中執行下列命令:

terraform init

此命令下載佈建和管理 Azure 資源所需的 Azure 模組。

產生執行計畫

初始化之後,您可藉由執行 Terraform 方案來建立執行計畫。 此命令會建立執行計畫,但不會執行。 相反地,它會決定要在您的組態檔中建立資源所需的動作。 選擇性的 -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 的值取代為<敏感性>標籤:

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

此時,您將能夠執行下列命令來連線到 Azure VM (將 <public_ip_address> 預留位置替換為您在 terraform apply-generated 的輸出中確認的 IP 地址後):

ssh -i id_rsa azureuser@<public_ip_address>