Partager via


Créer un groupe de machines virtuelles identiques Azure à partir d’une image personnalisée Packer à l’aide de Terraform

Terraform active la définition, la préversion et le déploiement de l’infrastructure cloud. À l’aide de Terraform, vous créez des fichiers de configuration à l’aide de la syntaxe HCL. La syntaxe HCL vous permet de spécifier le fournisseur de cloud , tel qu’Azure, et les éléments qui composent votre infrastructure cloud. Après avoir créé vos fichiers de configuration, vous créez un plan d’exécution qui vous permet d’afficher un aperçu des modifications de votre infrastructure avant leur déploiement. Une fois que vous avez vérifié les modifications, vous appliquez le plan d’exécution pour déployer l’infrastructure.

Les groupes de machines virtuelles identiques Azure vous permettent de configurer des machines virtuelles identiques. Le nombre d’instances de machine virtuelle peut être ajusté en fonction de la demande ou d’un calendrier. Pour plus d’informations, consultez Mettre à l’échelle automatiquement un groupe de machines virtuelles identiques dans le portail Azure.

Dans cet article, vous allez apprendre à :

  • Configurer votre déploiement Terraform
  • Utiliser des variables et des sorties pour le déploiement de Terraform
  • Créer et déployer une infrastructure réseau
  • Créer une image de machine virtuelle personnalisée à l’aide de Packer
  • Créer et déployer un groupe de machines virtuelles identiques à l’aide de l’image personnalisée
  • Créer et déployer une jumpbox

1. Configurer votre environnement

  • Abonnement Azure : si vous n’avez pas d’abonnement Azure, créez un compte gratuit avant de commencer.

2. Créez une image Packer

  1. Installez Packer.

    Points clés :

    • Pour vérifier que vous avez accès à l’exécutable Packer, exécutez la commande suivante : packer -v.
    • En fonction de votre environnement, vous devrez peut-être définir votre chemin d’accès et rouvrir la ligne de commande.
  2. Run az group create pour créer un groupe de ressources destiné à contenir l’image Packer.

    az group create -n myPackerImages -l eastus
    
  3. Exécutez az ad sp create-for-rbac pour permettre à Packer de s’authentifier auprès d’Azure à l’aide d’un principal de service.

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

    Points clés :

    • Notez les valeurs de sortie (appId, client_secret, tenant_id).
  4. Exécutez az account show pour afficher l’abonnement Azure actuel.

    az account show --query "{ subscription_id: id }"
    
  5. Créez un fichier de variables de modèle Packer nommé ubuntu.pkr.hcl et insérez le code suivant. Mettez à jour les lignes en surbrillance avec votre principal de service et vos informations d’abonnement 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",
        ]
      }
    }
    

    Points clés :

    • Définissez les client_idchamps , client_secretet tenant_id sur les valeurs respectives de votre principal de service.
    • Définissez le champ sur votre subscription_id ID d’abonnement Azure.
  6. Générez l’image Packer.

    packer build ubuntu.json
    

3. Implémenter le code Terraform

  1. Créez un répertoire dans lequel tester l’exemple de code Terraform et définissez-le comme répertoire actuel.

  2. Créez un fichier nommé main.tf et insérez le code suivant :

    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. Créez un fichier nommé variables.tf pour contenir les variables de projet et insérez le code suivant :

    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. Créez un fichier nommé output.tf pour spécifier les valeurs affichées par Terraform et insérez le code suivant :

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

Exécutez terraform init pour initialiser le déploiement Terraform. Cette commande télécharge le fournisseur Azure requis pour gérer vos ressources Azure.

terraform init -upgrade

Points clés :

  • Le paramètre -upgrade met à niveau les plug-ins de fournisseur nécessaires vers la version la plus récente conforme aux contraintes de version de la configuration.

5. Créer un plan d’exécution Terraform

Exécutez le plan terraform pour créer un plan d’exécution.

terraform plan -out main.tfplan

Points clés :

  • La commande terraform plan crée un plan d’exécution, mais ne l’exécute pas. Au lieu de cela, il détermine les actions nécessaires pour créer la configuration spécifiée dans vos fichiers de configuration. Ce modèle vous permet de vérifier si le plan d’exécution correspond à vos attentes avant d’apporter des modifications aux ressources réelles.
  • Le paramètre -out facultatif vous permet de spécifier un fichier de sortie pour le plan. L’utilisation du paramètre -out garantit que le plan que vous avez examiné est exactement ce qui est appliqué.

6. Appliquer un plan d’exécution Terraform

Exécutez terraform pour appliquer le plan d’exécution à votre infrastructure cloud.

terraform apply main.tfplan

Points clés :

  • L’exemple terraform apply commande suppose que vous avez précédemment exécuté terraform plan -out main.tfplan.
  • Si vous avez spécifié un nom de fichier différent pour le paramètre -out, utilisez ce même nom de fichier dans l’appel à terraform apply.
  • Si vous n’avez pas utilisé le paramètre -out, appelez terraform apply sans aucun paramètre.

7. Vérifiez les résultats

  1. À partir de la sortie de la terraform apply commande, vous voyez les valeurs suivantes :

    • Nom de domaine complet de la machine virtuelle
    • Jumpbox FQDN
    • Adresse IP de la jumpbox
  2. Accédez à l’URL de la machine virtuelle pour confirmer une page par défaut avec le texte Bienvenue dans nginx !.

  3. Utilisez SSH pour vous connecter à la machine virtuelle de rebond à l’aide du nom d’utilisateur défini dans le fichier de variables et du mot de passe que vous avez spécifié lors de l’exécution terraform applyde . Par exemple : ssh azureuser@<ip_address>.

8. Nettoyer les ressources

Supprimer le groupe de machines virtuelles identiques

Lorsque vous n’avez plus besoin des ressources créées via Terraform, procédez comme suit :

  1. Exécutez terraform plan et spécifiez le flag destroy.

    terraform plan -destroy -out main.destroy.tfplan
    

    Points clés :

    • La commande terraform plan crée un plan d’exécution, mais ne l’exécute pas. Au lieu de cela, il détermine les actions nécessaires pour créer la configuration spécifiée dans vos fichiers de configuration. Ce modèle vous permet de vérifier si le plan d’exécution correspond à vos attentes avant d’apporter des modifications aux ressources réelles.
    • Le paramètre -out facultatif vous permet de spécifier un fichier de sortie pour le plan. L’utilisation du paramètre -out garantit que le plan que vous avez examiné est exactement ce qui est appliqué.
  2. Exécutez terraform apply pour appliquer le plan d’exécution.

    terraform apply main.destroy.tfplan
    

Supprimer l’image et le groupe de ressources de l’empaqueteur

Exécutez az group delete pour supprimer le groupe de ressources utilisé pour contenir l’image Packer. L’image Packer est également supprimée.

az group delete --name myPackerImages --yes

Résoudre les problèmes de Terraform sur Azure

Résoudre les problèmes courants lors de l’utilisation de Terraform sur Azure

Étapes suivantes