Skapa en skalningsuppsättning för virtuella Azure-datorer från en anpassad Packer-avbildning med hjälp av Terraform

Terraform möjliggör definition, förhandsversion och distribution av molninfrastruktur. Med Terraform skapar du konfigurationsfiler med hjälp av HCL-syntax. Med HCL-syntaxen kan du ange molnleverantören – till exempel Azure – och de element som utgör din molninfrastruktur. När du har skapat konfigurationsfilerna skapar du en körningsplan som gör att du kan förhandsgranska ändringarna i infrastrukturen innan de distribueras. När du har verifierat ändringarna tillämpar du körningsplanen för att distribuera infrastrukturen.

Med skalningsuppsättningar för virtuella Azure-datorer kan du konfigurera identiska virtuella datorer. Antalet vm-instanser kan justeras baserat på efterfrågan eller ett schema. Mer information finns i Skala automatiskt en VM-skalningsuppsättning i Azure-portalen.

I den här artikeln kan du se hur du:

  • Konfigurera din Terraform-distribution
  • Använda variabler och utdata för Terraform-distribution
  • Skapa och distribuera en nätverksinfrastruktur
  • Skapa en anpassad avbildning av en virtuell dator med hjälp av Packer
  • Skapa och distribuera en VM-skalningsuppsättning med hjälp av den anpassade avbildningen
  • Skapa och distribuera en jumpbox

1. Konfigurera din miljö

  • Azure-prenumeration: Om du inte har någon Azure-prenumeration kan du skapa ett kostnadsfritt konto innan du börjar.

2. Skapa en Packer-avbildning

  1. Installera Packer.

    Viktiga punkter:

    • Kontrollera att du har åtkomst till den körbara Packer-filen genom att köra följande kommando: packer -v.
    • Beroende på din miljö kan du behöva ange sökvägen och öppna kommandoraden igen.
  2. Kör az group create för att skapa en resursgrupp som ska innehålla Packer-avbildningen.

    az group create -n myPackerImages -l eastus
    
  3. Kör az ad sp create-for-rbac för att göra det möjligt för Packer att autentisera till Azure med hjälp av tjänstens huvudnamn.

    az ad sp create-for-rbac --role Contributor --scopes /subscriptions/<subscription_id> --query "{ client_id: appId, client_secret: password, tenant_id: tenant }"
    

    Viktiga punkter:

    • Anteckna utdatavärdena (appId, client_secret, tenant_id).
  4. Kör az account show för att visa den aktuella Azure-prenumerationen.

    az account show --query "{ subscription_id: id }"
    
  5. Skapa en Packer-mallvariabelfil med namnet ubuntu.pkr.hcl och infoga följande kod. Uppdatera de markerade raderna med tjänstens huvudnamn och Azure-prenumerationsinformation.

    packer {
      required_plugins {
        azure = {
          source  = "github.com/hashicorp/azure"
          version = "~> 2"
        }
      }
    }
    
    variable client_id {
      type    = string
      default = null
    }
    variable client_secret {
      type    = string
      default = null
    }
    
    variable subscription_id {
      type    = string
      default = null
    }
    
    variable tenant_id {
      type    = string
      default = null
    }
    
    variable location {
      default = "eastus"
    }
    
    variable "image_resource_group_name" {
      description = "Name of the resource group in which the Packer image will be created"
      default     = "myPackerImages"
    }
    
    variable "oidc_request_url" {
      default = null
    }
    
    variable "oidc_request_token" {
      default = null
    }
    
    # arm builder
    source "azure-arm" "builder" {
      client_id                         = var.client_id
      client_secret                     = var.client_secret
      image_offer                       = "UbuntuServer"
      image_publisher                   = "canonical"
      image_sku                         = "16.04-LTS"
      location                          = var.location
      managed_image_name                = "myPackerImage"
      managed_image_resource_group_name = var.image_resource_group_name
      os_type                           = "Linux"
      subscription_id                   = var.subscription_id
      tenant_id                         = var.tenant_id
      oidc_request_url                  = var.oidc_request_url
      oidc_request_token                = var.oidc_request_token
      vm_size                           = "Standard_DS2_v2"
      azure_tags = {
        "dept" : "Engineering",
        "task" : "Image deployment",
      }
    }
    
    build {
      sources = ["source.azure-arm.builder"]
      provisioner "shell" {
        execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
        inline = [
          "apt-get update",
          "apt-get upgrade -y",
          "apt-get -y install nginx",
          "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync",
        ]
      }
    }
    

    Viktiga punkter:

    • Ange fälten client_id, client_secretoch tenant_id till respektive värden från tjänstens huvudnamn.
    • Ange fältet subscription_id till ditt Azure-prenumerations-ID.
  6. Skapa Packer-avbildningen.

    packer build ubuntu.json
    

3. Implementera Terraform-koden

  1. Skapa en katalog där du kan testa Terraform-exempelkoden och göra den till den aktuella katalogen.

  2. Skapa en fil med namnet main.tf och infoga följande kod:

    terraform {
    
      required_version = ">=0.12"
    
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>3.0"
        }
        azapi = {
          source  = "Azure/azapi"
          version = "~> 1.0"
        }
        local = {
          source  = "hashicorp/local"
          version = "2.4.0"
        }
        random = {
          source  = "hashicorp/random"
          version = "3.5.1"
        }
        tls = {
          source  = "hashicorp/tls"
          version = "4.0.4"
        }
      }
    }
    
    provider "azurerm" {
      features {
        resource_group {
          prevent_deletion_if_contains_resources = false
        }
      }
    }
    
    resource "random_pet" "id" {}
    
    resource "azurerm_resource_group" "vmss" {
      name     = coalesce(var.resource_group_name, "201-vmss-packer-jumpbox-${random_pet.id.id}")
      location = var.location
      tags     = var.tags
    }
    
    resource "random_string" "fqdn" {
      length  = 6
      special = false
      upper   = false
      numeric = false
    }
    
    resource "azurerm_virtual_network" "vmss" {
      name                = "vmss-vnet"
      address_space       = ["10.0.0.0/16"]
      location            = var.location
      resource_group_name = azurerm_resource_group.vmss.name
      tags                = var.tags
    }
    
    resource "azurerm_subnet" "vmss" {
      name                 = "vmss-subnet"
      resource_group_name  = azurerm_resource_group.vmss.name
      virtual_network_name = azurerm_virtual_network.vmss.name
      address_prefixes     = ["10.0.2.0/24"]
    }
    
    resource "azurerm_public_ip" "vmss" {
      name                = "vmss-public-ip"
      location            = var.location
      resource_group_name = azurerm_resource_group.vmss.name
      allocation_method   = "Static"
      domain_name_label   = random_string.fqdn.result
      tags                = var.tags
    }
    
    resource "azurerm_lb" "vmss" {
      name                = "vmss-lb"
      location            = var.location
      resource_group_name = azurerm_resource_group.vmss.name
    
      frontend_ip_configuration {
        name                 = "PublicIPAddress"
        public_ip_address_id = azurerm_public_ip.vmss.id
      }
    
      tags = var.tags
    }
    
    resource "azurerm_lb_backend_address_pool" "bpepool" {
      loadbalancer_id = azurerm_lb.vmss.id
      name            = "BackEndAddressPool"
    }
    
    resource "azurerm_lb_probe" "vmss" {
      loadbalancer_id = azurerm_lb.vmss.id
      name            = "ssh-running-probe"
      port            = var.application_port
    }
    
    resource "azurerm_lb_rule" "lbnatrule" {
      loadbalancer_id                = azurerm_lb.vmss.id
      name                           = "http"
      protocol                       = "Tcp"
      frontend_port                  = var.application_port
      backend_port                   = var.application_port
      backend_address_pool_ids       = [azurerm_lb_backend_address_pool.bpepool.id]
      frontend_ip_configuration_name = "PublicIPAddress"
      probe_id                       = azurerm_lb_probe.vmss.id
    }
    
    data "azurerm_resource_group" "image" {
      name = var.packer_resource_group_name
    }
    
    data "azurerm_image" "image" {
      name                = var.packer_image_name
      resource_group_name = data.azurerm_resource_group.image.name
    }
    
    resource "azapi_resource" "ssh_public_key" {
      type      = "Microsoft.Compute/sshPublicKeys@2022-11-01"
      name      = random_pet.id.id
      location  = azurerm_resource_group.vmss.location
      parent_id = azurerm_resource_group.vmss.id
    }
    
    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 "random_password" "password" {
      count  = var.admin_password == null ? 1 : 0
      length = 20
    }
    
    locals {
      admin_password = try(random_password.password[0].result, var.admin_password)
    }
    
    resource "azurerm_virtual_machine_scale_set" "vmss" {
      name                = "vmscaleset"
      location            = var.location
      resource_group_name = azurerm_resource_group.vmss.name
      upgrade_policy_mode = "Manual"
    
      sku {
        name     = "Standard_DS1_v2"
        tier     = "Standard"
        capacity = 2
      }
    
      storage_profile_image_reference {
        id = data.azurerm_image.image.id
      }
    
      storage_profile_os_disk {
        name              = ""
        caching           = "ReadWrite"
        create_option     = "FromImage"
        managed_disk_type = "Standard_LRS"
      }
    
      storage_profile_data_disk {
        lun           = 0
        caching       = "ReadWrite"
        create_option = "Empty"
        disk_size_gb  = 10
      }
    
      os_profile {
        computer_name_prefix = "vmlab"
        admin_username       = var.admin_user
        admin_password       = local.admin_password
      }
    
      os_profile_linux_config {
        disable_password_authentication = true
    
        ssh_keys {
          path     = "/home/azureuser/.ssh/authorized_keys"
          key_data = azapi_resource_action.ssh_public_key_gen.output.publicKey
        }
      }
    
      network_profile {
        name    = "terraformnetworkprofile"
        primary = true
    
        ip_configuration {
          name                                   = "IPConfiguration"
          subnet_id                              = azurerm_subnet.vmss.id
          load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.bpepool.id]
          primary                                = true
        }
      }
    
      tags = var.tags
    }
    
    resource "azurerm_public_ip" "jumpbox" {
      name                = "jumpbox-public-ip"
      location            = var.location
      resource_group_name = azurerm_resource_group.vmss.name
      allocation_method   = "Static"
      domain_name_label   = "${random_string.fqdn.result}-ssh"
      tags                = var.tags
    }
    
    resource "azurerm_network_interface" "jumpbox" {
      name                = "jumpbox-nic"
      location            = var.location
      resource_group_name = azurerm_resource_group.vmss.name
    
      ip_configuration {
        name                          = "IPConfiguration"
        subnet_id                     = azurerm_subnet.vmss.id
        private_ip_address_allocation = "Dynamic"
        public_ip_address_id          = azurerm_public_ip.jumpbox.id
      }
    
      tags = var.tags
    }
    
    resource "azurerm_virtual_machine" "jumpbox" {
      name                  = "jumpbox"
      location              = var.location
      resource_group_name   = azurerm_resource_group.vmss.name
      network_interface_ids = [azurerm_network_interface.jumpbox.id]
      vm_size               = "Standard_DS1_v2"
    
      storage_image_reference {
        publisher = "Canonical"
        offer     = "UbuntuServer"
        sku       = "16.04-LTS"
        version   = "latest"
      }
    
      storage_os_disk {
        name              = "jumpbox-osdisk"
        caching           = "ReadWrite"
        create_option     = "FromImage"
        managed_disk_type = "Standard_LRS"
      }
    
      os_profile {
        computer_name  = "jumpbox"
        admin_username = var.admin_user
        admin_password = local.admin_password
      }
    
      os_profile_linux_config {
        disable_password_authentication = true
    
        ssh_keys {
          path     = "/home/azureuser/.ssh/authorized_keys"
          key_data = azapi_resource_action.ssh_public_key_gen.output.publicKey
        }
      }
    
      tags = var.tags
    }
    
  3. Skapa en fil med namnet variables.tf för att innehålla projektvariablerna och infoga följande kod:

    variable "packer_resource_group_name" {
      description = "Name of the resource group in which the Packer image will be created"
      default     = "myPackerImages"
    }
    
    variable "packer_image_name" {
      description = "Name of the Packer image"
      default     = "myPackerImage"
    }
    
    variable "resource_group_name" {
      description = "Name of the resource group in which the Packer image  will be created"
      default     = null
    }
    
    variable "location" {
      default     = "eastus"
      description = "Location where resources will be created"
    }
    
    variable "tags" {
      description = "Map of the tags to use for the resources that are deployed"
      type        = map(string)
      default = {
        environment = "codelab"
      }
    }
    
    variable "application_port" {
      description = "Port that you want to expose to the external load balancer"
      default     = 80
    }
    
    variable "admin_user" {
      description = "User name to use as the admin account on the VMs that will be part of the VM scale set"
      default     = "azureuser"
    }
    
    variable "admin_password" {
      description = "Default password for admin account"
      default     = null
    }
    
  4. Skapa en fil med namnet output.tf för att ange vilka värden Terraform visar och infoga följande kod:

    output "vmss_public_ip_fqdn" {
      value = azurerm_public_ip.vmss.fqdn
    }
    
    output "jumpbox_public_ip_fqdn" {
      value = azurerm_public_ip.jumpbox.fqdn
    }
    
    output "jumpbox_public_ip" {
      value = azurerm_public_ip.jumpbox.ip_address
    }
    

4. Initiera Terraform

Kör terraform init för att initiera Terraform-distributionen. Det här kommandot laddar ned den Azure-provider som krävs för att hantera dina Azure-resurser.

terraform init -upgrade

Viktiga punkter:

  • Parametern -upgrade uppgraderar nödvändiga provider-plugin-program till den senaste versionen som uppfyller konfigurationens versionsbegränsningar.

5. Skapa en Terraform-körningsplan

Kör terraform-planen för att skapa en körningsplan.

terraform plan -out main.tfplan

Viktiga punkter:

  • Kommandot terraform plan skapar en körningsplan, men kör den inte. I stället avgör den vilka åtgärder som krävs för att skapa den konfiguration som anges i konfigurationsfilerna. Med det här mönstret kan du kontrollera om körningsplanen matchar dina förväntningar innan du gör några ändringar i faktiska resurser.
  • Med den valfria -out parametern kan du ange en utdatafil för planen. Med hjälp av parametern -out ser du till att planen du granskade är exakt vad som tillämpas.

6. Tillämpa en Terraform-körningsplan

Kör terraform gäller för att tillämpa körningsplanen på din molninfrastruktur.

terraform apply main.tfplan

Viktiga punkter:

  • terraform apply Exempelkommandot förutsätter att du tidigare körde terraform plan -out main.tfplan.
  • Om du har angett ett annat filnamn för parametern -out använder du samma filnamn i anropet till terraform apply.
  • Om du inte använde parametern -out anropar terraform apply du utan några parametrar.

7. Kontrollera resultatet

  1. Från kommandots terraform apply utdata ser du värden för följande:

    • FQDN för virtuell dator
    • Jumpbox FQDN
    • Jumpbox IP-adress
  2. Bläddra till den virtuella datorns URL för att bekräfta en standardsida med texten Välkommen till nginx!.

  3. Använd SSH för att ansluta till den virtuella jumpbox-datorn med det användarnamn som definierats i variabelfilen och lösenordet som du angav när du körde terraform apply. Exempel: ssh azureuser@<ip_address>.

8. Rensa resurser

Ta bort vm-skalningsuppsättning

Gör följande när du inte längre behöver de resurser som skapats via Terraform:

  1. Kör terraform-plan och ange destroy flaggan.

    terraform plan -destroy -out main.destroy.tfplan
    

    Viktiga punkter:

    • Kommandot terraform plan skapar en körningsplan, men kör den inte. I stället avgör den vilka åtgärder som krävs för att skapa den konfiguration som anges i konfigurationsfilerna. Med det här mönstret kan du kontrollera om körningsplanen matchar dina förväntningar innan du gör några ändringar i faktiska resurser.
    • Med den valfria -out parametern kan du ange en utdatafil för planen. Med hjälp av parametern -out ser du till att planen du granskade är exakt vad som tillämpas.
  2. Kör terraform tillämpa för att tillämpa körningsplanen.

    terraform apply main.destroy.tfplan
    

Ta bort Packer-avbildning och resursgrupp

Kör az group delete för att ta bort den resursgrupp som används för att innehålla Packer-avbildningen. Packer-avbildningen tas också bort.

az group delete --name myPackerImages --yes

Felsöka Terraform i Azure

Felsöka vanliga problem när du använder Terraform i Azure

Nästa steg