Membuat kumpulan skala mesin virtual Azure dari gambar kustom Packer dengan menggunakan Terraform

Terraform memungkinkan definisi, pratinjau, dan penyebaran infrastruktur cloud. Menggunakan Terraform, Anda membuat file konfigurasi menggunakan sintaksis HCL. Sintaksis HCL memungkinkan Anda menentukan penyedia cloud - seperti Azure - dan elemen yang membentuk infrastruktur cloud Anda. Setelah membuat file konfigurasi, Anda membuat rencana eksekusi yang memungkinkan Anda untuk melihat pratinjau perubahan infrastruktur Anda sebelum disebarkan. Setelah memverifikasi perubahan, Anda menerapkan rencana eksekusi untuk menyebarkan infrastruktur.

Set skala mesin virtual Azure memungkinkan Anda mengonfigurasi VM yang identik. Jumlah instans VM dapat menyesuaikan berdasarkan permintaan atau jadwal. Untuk informasi selengkapnya, lihat Skalakan set skala mesin virtual secara otomatis di portal Azure.

Dalam artikel ini, Anda akan mempelajari cara:

  • Menyiapkan penyebaran Terraform Anda
  • Gunakan variabel dan output untuk penyebaran Terraform
  • Membuat dan menyebarkan infrastruktur jaringan
  • Membuat gambar mesin virtual kustom dengan menggunakan Packer
  • Membuat dan menyebarkan skala mesin virtual yang ditetapkan dengan menggunakan gambar kustom
  • Membuat dan menyebarkan jumpbox

1. Mengonfigurasi lingkungan Anda

  • Langganan Azure: Jika Anda tidak memiliki langganan Azure, buat akun gratis sebelum memulai.

2. Buat gambar Packer

  1. Instal Packer.

    Poin utama:

    • Untuk mengonfirmasi bahwa Anda memiliki akses ke Packer yang dapat dieksekusi, jalankan perintah berikut: packer -v.
    • Bergantung pada lingkungan Anda, Anda mungkin perlu mengatur jalur dan membuka kembali baris perintah.
  2. Jalankan az group create untuk membuat grup sumber daya untuk menahan gambar Packer.

    az group create -n myPackerImages -l eastus
    
  3. Jalankan az ad sp create-for-rbac untuk memungkinkan Packer mengautentikasi ke Azure menggunakan service principal.

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

    Poin utama:

    • Catat nilai output (appId, client_secret, tenant_id).
  4. Jalankan az account show untuk menampilkan langganan Azure saat ini.

    az account show --query "{ subscription_id: id }"
    
  5. Buat file variabel templat Packer bernama ubuntu.pkr.hcl dan sisipkan kode berikut. Perbarui baris yang disorot dengan perwakilan layanan dan informasi langganan Azure Anda.

    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"
    }
    
    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
      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",
        ]
      }
    }
    

    Poin utama:

    • Atur bidang client_id, client_secret, dan tenant_id ke nilai masing-masing dari prinsip layanan Anda.
    • Atur bidang ke subscription_id ID langganan Azure Anda.
  6. Bangun gambar Packer.

    packer build ubuntu.json
    

3. Menerapkan kode Terraform

  1. Buat direktori untuk menguji dan menjalankan kode sampel Terraform dan menjadikannya direktori saat ini.

  2. Buat file bernama main.tf dan masukkan kode berikut:

    terraform {
    
      required_version = ">=0.12"
    
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>2.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 "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" {
      resource_group_name = azurerm_resource_group.vmss.name
      loadbalancer_id     = azurerm_lb.vmss.id
      name                = "ssh-running-probe"
      port                = var.application_port
    }
    
    resource "azurerm_lb_rule" "lbnatrule" {
      resource_group_name            = azurerm_resource_group.vmss.name
      loadbalancer_id                = azurerm_lb.vmss.id
      name                           = "http"
      protocol                       = "Tcp"
      frontend_port                  = var.application_port
      backend_port                   = var.application_port
      backend_address_pool_id        = 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 = jsondecode(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 = jsondecode(azapi_resource_action.ssh_public_key_gen.output).publicKey
        }
      }
    
      tags = var.tags
    }
    
  3. Buat file bernama variables.tf untuk memuat variabel proyek dan masukkan kode berikut:

    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. Buat file bernama output.tf untuk menentukan nilai apa yang ditampilkan Terraform dan masukkan kode berikut:

    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. Menginisialisasi Terraform

Jalankan terraform init untuk menginisialisasi penyebaran Terraform. Perintah ini mengunduh penyedia Azure yang diperlukan untuk mengelola sumber daya Azure Anda.

terraform init -upgrade

Poin utama:

  • Parameter -upgrade meningkatkan plugin penyedia yang diperlukan ke versi terbaru yang sesuai dengan batasan versi konfigurasi.

5. Buat rencana eksekusi Terraform

Jalankan terraform plan untuk membuat rencana eksekusi.

terraform plan -out main.tfplan

Poin utama:

  • Perintah terraform plan membuat rencana eksekusi, tetapi tidak menjalankannya. Perintah ini justru menentukan tindakan yang diperlukan untuk membuat konfigurasi yang ditentukan dalam file konfigurasi Anda. Pola ini memungkinkan Anda memastikan apakah rencana eksekusi telah sesuai dengan ekspektasi Anda sebelum membuat perubahan pada sumber daya aktual.
  • Parameter -out opsional memungkinkan Anda menentukan file output untuk rencana. Menggunakan parameter -out memastikan bahwa rencana yang Anda tinjau benar-benar sesuai dengan yang diterapkan.

6. Menerapkan rencana eksekusi Terraform

Jalankan terraform apply untuk menerapkan rencana eksekusi ke infrastruktur cloud Anda.

terraform apply main.tfplan

Poin utama:

  • Contoh terraform apply perintah mengasumsikan Anda sebelumnya menjalankan terraform plan -out main.tfplan.
  • Jika Anda menentukan nama file yang berbeda untuk parameter -out, gunakan nama file yang sama dalam panggilan ke terraform apply.
  • Jika Anda tidak menggunakan parameter -out, panggil terraform apply tanpa parameter apa pun.

7. Verifikasi hasilnya

  1. Dari output perintah terraform apply, Anda melihat nilai untuk berikut ini:

    • FQDN mesin virtual
    • FQDN Jumpbox
    • Alamat IP Jumpbox
  2. Jelajahi URL mesin virtual untuk mengonfirmasi halaman default dengan teks Selamat datang di nginx!.

  3. Gunakan SSH untuk menyambung ke VM jumpbox menggunakan nama pengguna yang ditentukan dalam file variabel dan kata sandi yang Anda tentukan saat menjalankan terraform apply. Misalnya: ssh azureuser@<ip_address>.

8. Membersihkan sumber daya

Hapus kumpulan skala mesin virtual

Ketika Anda tidak lagi membutuhkan sumber daya yang dibuat melalui Terraform, lakukan langkah-langkah berikut:

  1. Jalankan terraform plan dan tentukan bendera destroy.

    terraform plan -destroy -out main.destroy.tfplan
    

    Poin utama:

    • Perintah terraform plan membuat rencana eksekusi, tetapi tidak menjalankannya. Perintah ini justru menentukan tindakan yang diperlukan untuk membuat konfigurasi yang ditentukan dalam file konfigurasi Anda. Pola ini memungkinkan Anda memastikan apakah rencana eksekusi telah sesuai dengan ekspektasi Anda sebelum membuat perubahan pada sumber daya aktual.
    • Parameter -out opsional memungkinkan Anda menentukan file output untuk rencana. Menggunakan parameter -out memastikan bahwa rencana yang Anda tinjau benar-benar sesuai dengan yang diterapkan.
  2. Jalankan terraform apply untuk menerapkan rencana eksekusi.

    terraform apply main.destroy.tfplan
    

Menghapus gambar Packer dan grup sumber daya

Jalankan az group delete untuk menghapus grup sumber daya yang digunakan untuk berisi gambar Packer. Gambar Packer juga dihapus.

az group delete --name myPackerImages --yes

Memecahkan masalah Terraform pada Azure

Memecahkan masalah umum saat menggunakan Terraform di Azure

Langkah berikutnya