다음을 통해 공유


Terraform을 사용하여 Packer 사용자 지정 이미지로부터 Azure 가상 머신 스케일 세트를 생성하십시오.

Terraform 을 사용하면 클라우드 인프라의 정의, 미리 보기 및 배포가 가능합니다. Terraform을 사용하여 HCL 구문을 사용하여 구성 파일을 만듭니다. HCL 구문을 사용하면 클라우드 공급자(예: Azure)와 클라우드 인프라를 구성하는 요소를 지정할 수 있습니다. 구성 파일을 만든 후에는 인프라 변경 내용을 배포하기 전에 미리 볼 수 있는 실행 계획을 만듭니다. 변경 내용을 확인하면 실행 계획을 적용하여 인프라를 배포합니다.

Azure 가상 머신 확장 집합 을 사용하면 동일한 VM을 구성할 수 있습니다. VM 인스턴스 수는 수요 또는 일정에 따라 조정할 수 있습니다. 자세한 내용은 Azure Portal에서 가상 머신 확장 집합의 자동 크기 조정을 참조하세요.

이 문서에서는 다음 방법을 알아봅니다.

  • Terraform 배포 설정
  • Terraform 배포에 변수 및 출력 사용
  • 네트워크 인프라 만들기 및 배포
  • Packer를 사용하여 사용자 지정 가상 머신 이미지 만들기
  • 사용자 지정 이미지를 사용하여 가상 머신 확장 집합 만들기 및 배포
  • jumpbox 만들기 및 배포

1. 환경 구성

  • Azure 구독: Azure 구독이 없는 경우 시작하기 전에 체험 계정을 만듭니다.

2. Packer 이미지 만들기

  1. Packer를 설치합니다.

    핵심 사항:

    • Packer 실행 파일에 액세스할 수 있는지 확인하려면 다음 명령을 packer -v실행합니다.
    • 환경에 따라 경로를 설정하고 명령줄을 다시 열어야 할 수 있습니다.
  2. az group create를 실행하여 Packer 이미지를 저장할 리소스 그룹을 만듭니다.

    az group create -n myPackerImages -l eastus
    
  3. az ad sp create-for-rbac를 실행하여 Packer가 서비스 주체를 사용하여 Azure에 인증할 수 있도록 합니다.

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

    핵심 사항:

    • 출력 값(appId, client_secret, tenant_id)을 기록해 둡니다.
  4. az account show를 실행하여 현재 Azure 구독을 표시합니다.

    az account show --query "{ subscription_id: id }"
    
  5. Packer 템플릿 변수 파일 이름을 ubuntu.pkr.hcl 만들고 다음 코드를 삽입합니다. 강조 표시된 줄을 서비스 주체 및 Azure 구독 정보로 업데이트합니다.

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

    핵심 사항:

    • 서비스 주체에서 client_id해당 값으로 , client_secrettenant_id 필드를 설정합니다.
    • 필드를 subscription_id Azure 구독 ID로 설정합니다.
  6. Packer 이미지를 빌드합니다.

    packer build ubuntu.json
    

3. Terraform 코드 구현

  1. 샘플 Terraform 코드를 테스트할 디렉터리를 만들고, 이를 현재 디렉터리로 만듭니다.

  2. main.tf라는 파일을 만들고 다음 코드를 삽입합니다.

    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. 프로젝트 변수를 포함하도록 명명된 variables.tf 파일을 만들고 다음 코드를 삽입합니다.

    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. Terraform이 표시하는 값을 지정하고 다음 코드를 삽입하는 명명 output.tf 된 파일을 만듭니다.

    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. Terraform 초기화

terraform init를 실행하여 Terraform 배포를 초기화합니다. 이 명령은 Azure 리소스를 관리하는 데 필요한 Azure 공급자를 다운로드합니다.

terraform init -upgrade

핵심 사항:

  • -upgrade 매개 변수는 필요한 공급자 플러그 인을 구성의 버전 제약 조건을 준수하는 최신 버전으로 업그레이드합니다.

5. Terraform 실행 계획 만들기

terraform 계획을 실행하여 실행 계획을 만듭니다.

terraform plan -out main.tfplan

핵심 사항:

  • terraform plan 명령은 실행 계획을 만들지만 실행하지는 않습니다. 대신 구성 파일에 지정된 구성을 만드는 데 필요한 작업을 결정합니다. 이 패턴을 사용하면 실제 리소스를 변경하기 전에 실행 계획이 예상과 일치하는지 확인할 수 있습니다.
  • 선택 사항인 -out 매개 변수를 사용하여 계획의 출력 파일을 지정할 수 있습니다. -out 매개 변수를 사용하면 검토한 계획이 정확하게 적용됩니다.

6. Terraform 실행 계획 적용

terraform 적용을 실행하여 클라우드 인프라에 실행 계획을 적용합니다.

terraform apply main.tfplan

핵심 사항:

  • 예시 terraform apply 명령은 이전에 terraform plan -out main.tfplan를 실행했다고 가정합니다.
  • -out 매개 변수에 다른 파일 이름을 지정한 경우 terraform apply에 대한 호출에서 동일한 파일 이름을 사용합니다.
  • -out 매개 변수를 사용하지 않은 경우 매개 변수 없이 terraform apply를 호출합니다.

7. 결과 확인

  1. 명령의 terraform apply 출력에서 다음 값이 표시됩니다.

    • 가상 머신 FQDN
    • 점프박스 FQDN
    • Jumpbox IP 주소
  2. 가상 머신 URL로 이동하여 nginx에 오신 것을 환영합니다!라는 텍스트가 있는 기본 페이지가 있는 것을 확인하세요.

  3. SSH를 사용하여 변수 파일에 정의된 사용자 이름과 실행할 terraform apply때 지정한 암호를 사용하여 jumpbox VM에 연결합니다. 예: ssh azureuser@<ip_address>.

8. 리소스 정리

가상 머신 확장 집합을 삭제합니다.

Terraform을 통해 만든 리소스가 더 이상 필요하지 않은 경우 다음 단계를 수행합니다.

  1. terraform 계획을 실행하고 플래그를 지정합니다destroy.

    terraform plan -destroy -out main.destroy.tfplan
    

    핵심 사항:

    • terraform plan 명령은 실행 계획을 만들지만 실행하지는 않습니다. 대신 구성 파일에 지정된 구성을 만드는 데 필요한 작업을 결정합니다. 이 패턴을 사용하면 실제 리소스를 변경하기 전에 실행 계획이 예상과 일치하는지 확인할 수 있습니다.
    • 선택 사항인 -out 매개 변수를 사용하여 계획의 출력 파일을 지정할 수 있습니다. -out 매개 변수를 사용하면 검토한 계획이 정확하게 적용됩니다.
  2. terraform을 실행하여 실행 계획을 적용합니다.

    terraform apply main.destroy.tfplan
    

Packer 이미지 및 리소스 그룹 삭제

az group delete를 실행하여 Packer 이미지를 포함하는 데 사용되는 리소스 그룹을 삭제합니다. Packer 이미지도 삭제됩니다.

az group delete --name myPackerImages --yes

Azure에서 Terraform 문제 해결

Azure에서 Terraform을 사용할 때 발생하는 일반적인 문제 해결

다음 단계